Znělo to jednoduše: mám napsaný další program pro Android a zákazník si přál mít ještě možnost stáhnout a spustit PDF v externím prohlížeči.
Všechny aplikace pro Android ladím jako Win32 aplikace s pár IFDEF a pak to jen přeložím pro Android a upravím sem tam design a pár speciálních funkcí, velmi výrazně to zrychluje vývoj. A tak jsem na to šel i při zobrazení PDF.
A jako bonus se zmíníme o AndroidManifest.xml a AndroidManifest.template.xml .
Stáhnutí PDF bylo pár řádků díky THTTPClient a pro zobrazení na windows z VCL jsem věděl, že nejjednodušší je ShellExecute.
Napíši nejprve finální stav (jako metodu formuláře) a pak si k tomu něco řekneme, ať se taky zasmějete.
uses
….
Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.Net
…
procedure TForm1.mStartPdf(const sFile: string);
{$IFDEF ANDROID}
var
Intent: JIntent;
LUri: Jnet_Uri;
{$ENDIF}
begin
{$IFDEF ANDROID}
LUri := TAndroidHelper.JFileToJURI(TJFile.JavaClass.init(StringToJString(sFile)));
Intent := TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_VIEW);
intent.setDataAndType(LUri, StringToJstring('application/pdf'));
Intent.setFlags(TJIntent.JavaClass.FLAG_ACTIVITY_NO_HISTORY or
TJIntent.JavaClass.FLAG_ACTIVITY_CLEAR_TOP or
TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION);
TAndroidHelper.Activity.startActivity(Intent);
{$ELSE}
if IsDebuggerPresent then
ShowMessage('Running under debugger:'+sFile)
else
ShellExecute(0{WindowHandleToPlatform(Handle).Wnd}, nil, PChar(sFile), nil, nil, SW_SHOW);
{$ENDIF}
end;
Pro Android ještě v Project Options - Application - User Permission zkontrolujte práva, nemělo by tam být třeba nic speciálního, ale v Entitlement List zkontrolujte Secure File Sharing. Pokud tam tuto volbu nemáte, máte staré delphi a musíte si pomoci manuálně třeba Opening a PDF on Android with Delphi.
No těchto pár řádků mne stálo přes několik hodin převážně Androidího pekla. Ale nejprve windows, tam je to skoro normální ShellExecute, až na to, že autor těchto řádků zapomněl, že Acrobat Reader se nemá rád s debuggerem a většinou vytuhne, pokud se spustí z IDE. Mea culpa. To bylo první tlučení hlavou o stůl.
Aplikace pod windows fungovala ok, přeložil jsem pod Android, upravil pár cest za pomoci System.IOUtils.TPath.GetPublicPath nebo podobné (připomínám, že System.IOUtils.TPath.Combine(System.IOUtils.TPath.GetPublicPath, 'demo.pdf') je bezpečné, zapomeňte na / místo toho PathDelim nebo funkce), a jal se hledat jak spustit pdf.
Dělá se to přes Intent, s tím souhlasí všichni (myslím v různých jazycích). Nejenže se to liší dle verze androidu (krásně to odstiňuje Delphi funkce TAndroidHelper.JFileToJURI, takže nemusíte řešit opičky s FileProvider od jisté verze Androidu), ale např. nastavení flagů se liší dle verze taky. Navíc někdo doporučuje ACTION_VIEW, někdo něco jiného atd. Někdo pro změnu preferuje to zobrazit v prohlížeči internetu.
No nebudu to napínat, výsledek je nahoře. Uvedený kód by mohl fungovat i pro jiné dokumenty, jen ten typ se liší (existuje na to API).
AndroidManifest.xml
Uvedený soubor je soubor Androidu, který je součástí každé apk a dá se tím specifikovat chování. Delphi to převážně obsluhují sami, ale dá se to měnit. V adresáři projektu je AndroidManifest.template.xml, ze kterého je daný soubor vytvářen. Jedná se o XML (v obou případech), vysledný AndroidManifest.xml je pak v adresáři debug nebo release. Uvedená volba na screenshotu způsobí, že do manifestu jsou přigenerovány "providery", tj. cesty, která pak jsou známy.
Důležité: template pro AndroidManifest.template.xml (tj. šablona pro šablonu) je v adresáři c:\Users\{user}\AppData\Roaming\Embarcadero\BDS\22.0\ a při aktualizacích si nejsem jistý zda se maže. Důležité je, že pokud si ho poškodíte, tak ho normálně smažte a IDE ho při startu vygeneruje znovu.
Zajímavé kódy pro PDF, Android a Delphi
github.com/AlessandroMartini/Delphi-Android-GeraPDF - generování a zobrazení PDF na Androidu, nutno upravit to zobrazení (to je jedna z variant co dříve fungovala), ale generování je OK
github.com/DelphiWorlds/Kastri/tree/master/Features/AndroidPDF - control na zobrazování PDF na Androidu
No dnes to bylo takové jednostranné, ale myslím, že to někomu v budoucnosti pomůže. A určitě mi v komentářích napíšete, že to jde řešit mnohem jednodušeji, já jsem to pro aktuální android lépe nedokázal.