Sviluppare app per Android è una delle competenze oggi più richieste nell'ambito dell'IT, e sta raggiungendo una complessità non indifferente. Sono infatti numerosi i dettagli da curare nella produzione di un'applicazione mobile di alta qualità, e ciò è particolarmente vero per Android, caratterizzato dall'essere il sistema operativo di un larghissimo numero di dispositivi (smartphone e tablet per lo più, ma anche dispositivi di diversa forma e natura).
La crescita di Android e la diversificazione degli ambienti in cui è eseguito hanno comportato la necessità di eseguire app su dispositivi con processori Intel x86, architetturalmente differenti e non compatibili con le CPU ARM per le quali questo sistema operativo era originariamente nato. Eseguire un'app su un dispositivo con architettura Intel x86 implica, su Android, la necessità di virtualizzarne l'esecuzione, con un conseguente degrado in termini di performance. Per evitare questo, esistono diversi strumenti che consentono l'ottimizzazione delle app per l'esecuzione sulle architetture x86. In questo articolo vedremo proprio quali espedienti adottare per raggiungere questo scopo.
Android e le architetture x86
Sin dall'uscita di Android, la maggior parte dei dispositivi supportati si basava sull'architettura ARM, ed ancora oggi quest'ultima continua ad essere la soluzione più diffusa tra gli smartphone ed i tablet che adottano questo sistema operativo. Con la crescita di questo sistema operativo e la sua diffusione, però, sono stati prodotti anche un certo numero di device che utilizzano processori Intel, basati su architettura x86. Il numero di questi dispositivi è certamente inferiore a quelli basati su ARM, ma non può essere ritenuto esiguo; al contrario, le architetture x86 potrebbero riscuotere un discreto e crescente successo anche tra i dispositivi mobile. Basti pensare che il Samsung Galaxy Tab 3, che è il tablet Android più venduto al mondo, è dotato di un processore Intel x86.
Perché è importante essere a conoscenza di questa nuova tendenza? Che cosa comporta per uno sviluppatore il fatto che le architetture x86 sono, oggi, una realtà non trascurabile del mondo Android?
Le risposte a queste domande hanno principalmente a che fare con le performance. Se non ci occupiamo esplicitamente di ottimizzare i nostri prodotti per l'esecuzione sulle architetture x86, le conseguenze possono essere due:
- riduzione delle performance dell'app: la mancata ottimizzazione delle app può comportare un overhead computazionale non indifferente, rallentando l'esecuzione del codice nativo. Ciò può infastidire l'utente, ed indurlo a disinstallare l'app;
- riduzione delle performance dell'intero dispositivo: anche ammettendo che l'utente si accontenti delle performance dell'app, la mancata ottimizzazione può implicare un aumento dei consumi di memoria e batteria. Esistono (e sono sempre più diffuse) molte app per l'analisi delle prestazioni e dei consumi, che suggeriscono all'utente quali applicazioni utilizzano maggiormente le risorse. Tramite questi tool è molto probabile che la nostra app venga segnalata come "troppo dispendiosa", e ciò aumenta il rischio che l'utente scelga di disinstallarla.
Per minimizzare le possibilità che l'utente decida di disinstallare la nostra app (cosa che, nella maggior parte dei casi, si traduce in perdite o mancati guadagni), non resta che ottimizzarla. Per nostra fortuna, gli accorgimenti da attuare sono abbastanza semplici, e nel seguito vedremo insieme come fare.
Compilare le app per x86
Prima di spiegare come abilitare la compilazione per le architetture x86 bisogna chiarire il processo di compilazione delle app per Android. Con le impostazioni di default, gli SDK creano un pacchetto .apk della nostra applicazione, che contiene le versioni binarie del codice nativo (quello, cioè, scritto in C o C++, per esempio utilizzando l'NDK di Android) ed il bytecode risultante dalla compilazione del codice Java.
Mentre il bytecode viene necessariamente virtualizzato (ed è quindi indipendente dall'architettura sottostante), il codice nativo viene compilato generalmente per le sole architetture ARM, ed il codice binario è incluso nel pacchetto .apk che rappresenta la nostra app.
È quindi chiaro che, se scriviamo un'app utilizzando soltanto Java, non avremo la necessità di compilare per le diverse architetture, perché il bytecode deve necessariamente essere eseguito in virtualizzazione sulla Dalvik Virtual Machine. Ma per app che richiedono un certo livello di performance (come nel caso di giochi o applicazioni che fanno uso di rendering in 3D) scrivere codice nativo è inevitabile. Per queste ultime situazioni, quindi, abbiamo bisogno di ottimizzare la compilazione.
Se eseguiamo un'app (nativa) compilata solo per ARM, gli scenari possibili sono due:
- se il dispositivo è basato su architetture ARM, l'eventuale codice nativo sarà eseguito direttamente, senza alcun layer intermedio di virtualizzazione;
- se il dispositivo utilizza un processore Intel basato su architettura x86, il codice (non essendo stato compilato per quest'ultima architettura) dovrà necessariamente essere virtualizzato, con un conseguente degrado delle prestazioni.
La procedura che consente di compilare un'app per le architetture x86 è, fortunatamente, molto semplice. Utilizzando proprio il sopra citato NDK di Android, tutto ciò che dobbiamo fare è aprire il file jni/Application.mk all'interno di un qualsiasi editor, e modificare la variabile APP_ABI
. Quest'ultima, di norma, sarà valorizzata come segue:
APP_ABI := armeabi armeabi-v7a
In questo modo, le architetture di destinazione sono soltanto due: quelle ARM. Se vogliamo aggiungere il supporto alle architetture x86, basta aggiungere la stringa x86 in coda a quella precedente:
APP_ABI := armeabi armeabi-v7a x86
A questo punto, ad ogni compilazione, il file .apk generato conterrà tre versioni dello stesso codice: due compilate per ARM, ed una terza per le architetture x86. La presenza di quest'ultima versione binaria è ciò che consente di aggirare la virtualizzazione dell'app in fase di esecuzione, risultando in un miglioramento (spesso evidente) delle prestazioni.
Prima di presentare qualche ulteriore consiglio per l'ottimizzazione, ci sono però due ulteriori punti da espletare:
-
utilizzo di librerie esterne: se vogliamo utilizzare qualche libreria precompilata, avremo bisogno anche della versione per le architetture x86. È quindi sempre consigliabile l'utilizzo di librerie multipiattaforma o open source, che possano quindi essere compilate anche per l'architettura x86. Tuttavia, se vogliamo gestire la possibilità di compilare librerie diverse in funzione dell'architettura, possiamo sempre modificare il file Android.mk, aggiungendo una condizione simile alla seguente:
ifeq ($(TARGET_ARCH_ABI), x86)
- limite sulle dimensioni dell'app: è bene tenere presente che il limite per l'upload delle app su Google Play è di 50 MB. Aggiungere una terza versione del codice può tradursi nel rischio di superare questo limite, a meno che non abbiamo curato attentamente l'aspetto relativo alle dimensioni degli asset grafici e dei contenuti multimediali. È possibile, ad esempio, possibile utilizzare file di espansione che consentono di arrivare fino a 2GB, nei quali possiamo inserire tutti gli assets condivisi e pesanti.
Altri consigli per l'ottimizzazione
Oltre alla compilazione per la specifica architettura che si vuole utilizzare, ci sono alcune regole di massima che è bene seguire per ottimizzare la nostra app. Vedremo, infatti, che molto del carico computazionale superfluo può essere evitato seguendo alcune linee guida molto semplici.
Multithreading e Parallellismo
Oggi molti dispositivi Android utilizzano processori multi-core. Sfruttare al meglio questa caratteristica hardware non solo migliora le prestazioni, ma riduce anche il consumo della batteria.
Un altro aspetto interessante risiede nell'elevata frequenza con cui vengono effettuate le system call. Molte di essere sono dovute alla transizione (troppo spesso non necessaria) da uno stato attivo ad uno stato di idle di un thread o un processo. Combinare diverse azioni periodiche in un'unica azione, consente di evitare inutili system call, consentendo un risparmio in termini di consumo energetico. Inoltre, meno transizioni vengono tradotte, a basso livello, in un maggiore grado di parallelismo (incrementato altresì dall'utilizzo del multithreading e dei pattern di programmazione concorrente).
Ottimizzare il codice usato di frequente
In generale, il codice utilizzato più di frequente è quello che necessita di maggiori ottimizzazioni. Di conseguenza, il punto di inizio per un buon processo di ottimizzazione dev'essere questo.
Ci sono molte tecniche per ottimizzare il codice, la maggior parte delle quali dipende dalle singole situazioni. Un modo per sfruttare le potenzialità dell'architettura Intel x86 è l'utilizzo di istruzioni vettoriali come SSE, AVX, ecc. Queste istruzioni consentono di eseguire azioni comuni in meno tempo, e richiedono meno energia (riducendo i consumi).
Ridurre l'utilizzo della memoria
Anche ridurre l'utilizzo delle risorse di memoria diminuisce il consumo di energia delle applicazioni attive. Di conseguenza, è sempre buona norma tenere in considerazione le indicazioni seguenti:
- evitare le conversioni di formati grafici non necessarie (per esempio da YUV ad RGB e viceversa);
- cercare di mantenere in cache le strutture dati usate più di frequente;
- limitare lo spostamento dei dati tra il kernel space o lo user space.
I vantaggi di un'app nativa per Intel x86
Abbiamo già accennato al fatto che su Android un'app nativa consuma meno energia di una virtualizzata. Questo aspetto, così come tutti quelli relativi alle performance di cui abbiamo ampiamente discusso, non è il solo vantaggio che un'app ottimizzata per le architetture Intel x86 può sfruttare. L'utilizzo di processori Intel x86, infatti, consente di sfruttare alcune interessanti tecnologie che si prestano a numerose applicazioni. Una di essere è Intel WiDi (acronimo che sta per Wireless Display), tramite la quale possiamo connettere un dispositivo ad un monitor esterno senza l'utilizzo di alcun cavo. Un esempio di utilizzo di questa tecnologia è l'app Grace, che permette di effettuare uno streaming video da uno smartphone ad una TV, consentendo al tempo stesso di prendere appunti sul dispositivo mobile. Il video seguente mostra un ulteriore esempio di come tale tecnologia può essere applicata.
Un altro vantaggio non indifferente riguarda il debug delle app. Tutti sappiamo quanto sia pesante il simulatore di Android fornito da Google, ma le alternative a disposizione sono poche e limitate. Una di queste, però, è una soluzione di Intel chiama HAXM (Hardware Accelerated Execution Manager). Si tratta di un hypervisor che utilizza la tecnologia Intel VT-x (integrata praticamente in tutti i processori moderni) per migliorare la virtualizzazione dell'emulatore, ed aumentarne le prestazioni. Possiamo scaricarlo dall'apposita pagina del sito ufficiale di Intel, ed installarlo. Escludendo l'eventualità che tale tecnologia non sia supportata dal processore del nostro PC, l'unico problema che potremmo riscontrare può derivare dal fatto che Intel VT-x non è abilitata. Per risolvere questo imprevisto dobbiamo accedere al BIOS, abilitare Intel VT-x e riavviare l'installazione dopo averlo fatto. Infine, quando creeremo un AVD (Android Virtual Device) per la simulazione, dovremo selezionare un'immagine tra quelle disponibili sul sito di Intel, che possiamo anche scaricare tramite l'Android SDK Manager. In quest'ultimo caso, però, dobbiamo ricordarci di impostare la voce CPU/ABI a Intel Atom (x86), ed otterremo un emulatore eseguito con architettura x86.
Intel INDE
Prima di concludere, è bene introdurre anche un interessante tool prodotto da Intel e reso disponibile da poco tempo, pensato proprio per aiutare gli sviluppatori ad adattare le proprie app a più architetture. Si tratta di Intel INDE (acronimo che sta per Integrated Native Developer Experience), uno strumento integrato che consente, con una singola interfaccia, di effettuare rapidamente il setup dell'ambiente di sviluppo, includendo automaticamente compilatori per le architetture ARM ed x86, librerie integrabiliche possono essere compilate per le specifiche architetture, e tutte le componenti necessarie allo sviluppo per Android.
L'installazione di INDE è molto semplice. È sufficiente scaricare il software dalla pagina di riferimento del sito di Intel (previa una registrazione gratuita), installarlo ed avviarlo. L'interfaccia vista nell'immagine precedente mostra come le fasi di installazione di tutti i tool, dai compilatori all'ambiente di sviluppo, passando per le librerie, siano semplificate da un'interfaccia molto intuitiva.
Intel INDE provvede automaticamente ad installare i seguenti componenti:
- Google Android SDK per Java
- Android NDK per C++
- Android Design
- Apache ANT
Inoltre, esso supporta l'integrazione con tre diverse IDE, ovvero:
- Microsoft Visual Studio (2012 o successivo)
- Eclipse
- Android Studio
Dopo l'installazione di questo componenti, potremo immediatamente iniziare lo sviluppo delle nostre app utilizzando l'IDE che preferiamo. Potremo altresì sfruttare le potenzialità di Intel HAXM per il debugging sull'emulatore, dal momento che esso viene incluso nell'installazione, riducendo i tempi di testing.
Link utili
Per ottenere maggiori dettagli sull'installazione di HAXM ed il debugging su emulatore sfruttando questa tecnologia, suggeriamo un interessante articolo direttamente dal sito di Intel, comprensivo delle modalità di installazione su Windows, OSX e Linux.
Infine, per problemi o approfondimenti relativi all'installazione di Intel INDE, è possibile consultare l'apposita pagina sul sito di Intel.