Esplorando i processi Android

Qualche settimana fa stavo risolvendo un bug in Android Studio e mi sono imbattuto nel tag “android:process”. (Sapevo che il tag permette di specificare il processo su cui verrà eseguito il componente Android. Tuttavia, molti dei dettagli e del ragionamento dietro i processi Android mi erano sconosciuti. Così mi sono documentato e ho scritto questo articolo per condividere ciò che ho imparato.

Come sempre, sono felice di essere corretto su qualsiasi cosa su cui sono male informato.

Una panoramica sui processi Android

Android è basato su Linux, quindi “il sistema Android avvia un nuovo processo Linux per l’applicazione con un singolo thread di esecuzione. Per impostazione predefinita, tutti i componenti della stessa applicazione vengono eseguiti nello stesso processo e thread (chiamato thread “principale”).” (da: Process and Threads Overview)

Generalmente il thread “principale” è anche il thread “UI” o il thread “eventi”, poiché qualsiasi modifica dell’UI o gestione degli eventi avviene su questo thread. (eccezione: y̵o̵u̵ ̵c̵a̵n̵ ̵c̵h̵a̵n̵g̵e̵ ̵t̵h̵i̵s̵ ̵b̵y̵ ̵u̵s̵i̵n̵g̵ ̵T̵h̵r̵e̵a̵d̵ ̵A̵n̵n̵o̵t̵a̵t̵i̵o̵n̵s̵.̵ Ho sbagliato, è possibile solo per le app di sistema avere MainThread e UIThread diversi: “Gli strumenti di compilazione trattano le annotazioni @MainThread e @UiThread come intercambiabili, quindi è possibile chiamare metodi @UiThread da metodi @MainThread e viceversa. Tuttavia, è possibile che un thread UI sia diverso dal thread principale nel caso di app di sistema con più viste su thread diversi.”)

Di default il processo principale (e il thread principale che avvia) viene eseguito con un nome di processo che è lo stesso di applicationId dell’applicazione (dichiarato nel build.gradle della tua app). Quindi se tu dovessi creare un progetto di default con Android Studio, otterresti un’applicazione Android che verrà eseguita sul thread: com.example.<username>.myapplication e il corrispondente pacchetto di default per il tuo codice.

Cosa fa il tag “android:process”

Di default un’applicazione Android avrà un solo processo. Tutti i componenti saranno eseguiti sullo stesso processo. Quando dichiari nuovi componenti nel manifest della tua applicazione, puoi usare il tag “android:process” per dichiarare un nuovo processo per questo componente. In questo modo:

<activity
android:name=".SettingsActivity"
android:process=":setting"
android:label="@string/title_activity_settings">
</activity>

Nello snippet qui sopra dichiaro una SettingActivity che verrà eseguita sul processo locale com.example.kelvinma.myapplication:setting. (più avanti in questo articolo parleremo di locale e globale)

Il tag “android:process” può essere applicato ai 4 componenti android:

  • Activity
  • Service
  • Receiver
  • Provider

e anche al tag Application. Quando viene applicato al tag Application, qualsiasi componente dell’applicazione eredita anche l’android:process a meno che non sia sovrascritto dal proprio tag android:process.

Non sono sicuro di quale sia il caso d’uso per usare questo tag sulla tua dichiarazione di applicazione quando invece puoi semplicemente cambiare il tuo applicationId. Se ne conosci uno, per favore dimmelo.

Un breve esempio visivo

Per illustrare un’applicazione multi-processo, supponiamo che io avvii l’attività predefinita sulla mia applicazione Pomodoro, poi usando lo snippet di cui sopra, cambio la mia SettingsActivity per eseguire un altro processo. Quindi dopo l’avvio della mia applicazione e l’avvio della SettingsActivity, la mia applicazione avrà 2 processi:

$ ./adb shell ps | grep kelvin
u0_a61 32175 1669 1292180 47600 SyS_epoll_ b738f2b5 S com.example.kelvinhanma.pomodoro
u0_a61 32223 1669 1284796 43736 SyS_epoll_ b738f2b5 S com.example.kelvinhanma.pomodoro:setting

Tuttavia, tutti i componenti vengono implicitamente eseguiti sul processo di default a meno che non sia specificato diversamente dal tag process. Quindi se aggiungi android:process=":setting"all’attività predefinita e poi avvii un’altra attività, la tua applicazione avrà 2 processi:

com.example.kelvinhanma.pomodoro:setting
com.example.kelvinhanma.pomodoro

Perché l’attività avviata si avvierà sul processo predefinito poiché non ha un tag process impostato.

Quando rendo la mia applicazione multi-processo?

Ci sono 2 ragioni che conosco per creare un’app multi-processo:

  1. La tua app ha azioni separate che devono essere eseguite indipendentemente, l’esempio comune è un’app che riproduce musica avrebbe il processo predefinito per gestire l’interfaccia utente e un altro processo per gestire la riproduzione della musica.
  2. La tua app sta incontrando limiti di memoria. Android limita l’uso della RAM su una base per processo. Quindi puoi aumentare il limite totale di RAM della tua app eseguendo più processi. Tuttavia, poiché i processi non condividono la memoria, non puoi condividere Singleton tra i processi.

Processo locale o globale

Il tipo di processo dipende dal valore che inserisci nel tag process. Se iniziate con i due punti (“:”) allora il processo è locale e privato della vostra applicazione (il nome completo del processo sarebbe applicationId:processName). Se inizi con una lettera minuscola allora è un processo globale.

Un processo globale può essere condiviso tra le applicazioni. Spesso una libreria verrà eseguita su un processo globale in modo che più applicazioni possano condividere il processo per ridurre l’uso della memoria.

android:process quiz

Finiamo con alcuni scenari, vedi se riesci a prevedere la risposta.

Q: Cosa succede se usi un processo globale per impostare il tuo processo al tuo id di pacchetto? android:process="com.example.kelvinhanma.pomodoro"

A: Il tuo processo rimane invariato e questo è permesso. Anche se è una cosa inutile e stupida da fare.

Q: Cosa succede se lo usi per impostare il tuo processo sul pacchetto di un’altra app? Supponiamo che io abbia un com.example.kelvinhanma.pomodoro in esecuzione sul mio dispositivo. Imposto il processo globale di un’altra app con lo stesso nome e lo distribuisco.

Mi ritroverei con 2 processi con lo stesso nome!

./adb shell ps | grep kelvin
u0_a181 28008 630 4324212 66060 SyS_epoll_wait 0 S com.example.kelvinhanma.pomodoro
u0_a179 28287 630 4327384 66200 SyS_epoll_wait 0 S com.example.kelvinhanma.pomodoro

Ma va bene. I nomi dei processi non devono essere unici. Notate che gli utenti sono diversi (u0_a181, u0_a179) e gli id dei processi sono unici.

Spero che l’articolo vi sia piaciuto e abbiate imparato qualcosa. Sentitevi liberi di farmi sapere se qualcosa non è corretto!