lunedì 1 dicembre 2014

Workaround per l'errore EBitmapLoadingFailed su Android Lollipop

Introduzione

C'è un bug di Delphi XE7 per cui le applicazioni Android non si avviano su Lollipop (Android 5.0). In particolare, vanno in crash subito dopo aver visualizzato lo splashscreen, a causa dell'eccezione EBitmapLoadingFailed che viene sollevata durante il caricamento delle immagini contenute nel file .fmx .
Lo stessa applicazione (stesso APK) esegue senza problemi su versioni di Android precedenti alla 5.

Ci sono un paio di discussioni a riguardo, sui social-network: 

Viene spiegato anche un workaround (che io ho applicato alla versione beta di ColorMapp stamattina) che consiste nel caricare le risorse grafiche (TBitmap) a runtime invece che lasciarle embedded nelle risorse dell'applicazione (evitando così che vengano caricate attraverso una chiamata a TBitmap.LoadFromStream, che sembra essere il punto critico del malfunzionamento).

Passi per applicare il workaround:

1) Salvare su disco tutte le bitmap di ogni form

(comodo se avete già caricato le immagini a design time, meno utile se state scrivendo una applicazione da zero)

Per semplificare il processo, ho scritto un pezzetto di codice che itera sui componenti di una form e salva tutte le bitmap delle TImage che trova.
Potete guardare la funzione SaveAllImagesToDisk della unit ImageDumpUnit.pas, che trovate in un mio repository GitHub:


Per esempio, nell'evento OnCreate della form ho aggiunto:

  {$IFDEF MSWINDOWS}
  SaveAllImagesToDisk(Self);
  {$ELSE}
  LoadAllImagesFromDisk(Self);
 {$ENDIF}

Eseguendo l'applicazione sulla platform Win32, vi verrà generata una cartella images_dump con delle sottocartelle (una per ogni form) con le immagini salvate in files PNG.
Il ramo $ELSE della direttiva di compilazione viene eseguito per esempio su Android, così l'applicazione Win32 fa il dump e l'applicazione Android cerca di caricare dinamicamente le immagini dallo storage del dispositivo.

2) Rimuovere tutte le bitmap dalle risorse (.fmx)

Il modo più semplice (ma richiede di agire manualmente su ogni singolo componente delle vostre form con una bitmap) consiste nel:
  • svuotare tutte le voci delle MultiResBitmap (eliminando tutte le versioni delle bitmap);
  • svuotare le TBitmap (es. le proprietà StartValue / StopValue delle TBitmapAnimation), aprendo il component editor e premendo il tasto Clear;
Se avete molte TBitmap e svuotarle manualmente vi sembra improponibile, potreste agire direttamente sul DFM, cercando tutte le occorrenze di Bitmap.

3) Caricare a run-time le immagini corrispondenti

Anche in questo caso potete sfruttare la funzione LoadAllImagesFromDisk della unit ImageDumpUnit.pas, avendo cura di chiamarla per esempio nell'evento OnCreate delle vostre form.

4) Aggiungere al deploy tutti i file PNG generati al passo 1

Attraverso il deployment manager di Delphi, aggiungete i file PNG in modo che vengano distribuiti con la vostra applicazione. Abbiate cura di indicare come RemotePath, il valore  'assets/internal/images_dump/' seguito dal nome della form corrispondente.


5) controllate di non avere altre TBitmap

Per esempio mi sono accorto che il problema si presenta anche con le TBitmap (non solo le TMultiResBitmap) presenti in molti componenti (es. le TBitmapAnimation). Per queste io ho risolto manualmente (mimando il comportamento di LoadAllImagesFromDisk ) ma potrebbe aver senso automatizzare il dump e il load dinamico anche di quelle.

Conclusione

E' tutto, seguendo questi passi io ho risolto il crash della mia applicazione XE7 ColorMapp su Android 5.0 Lollipop e attualmente ho una beta funzionante sul mio Nexus 5.
Conto di promuovere la versione beta in produzione appena fatti i test su qualche altro dispositivo.

Buon lavoro e a presto,
Andrea

1 commento:

  1. Beta hotfix per questo bug:
    http://cc.embarcadero.com/item/30110

    RispondiElimina