Processi e ciclo di vita dell'app

Nella maggior parte dei casi, ogni applicazione Android viene eseguita nel proprio processo Linux. Questo processo viene creato per l'applicazione quando è necessario eseguire parte del codice e rimane in esecuzione finché il sistema non deve recuperare la memoria per l'utilizzo da parte di altre applicazioni e non è più necessario.

Una funzionalità insolita e fondamentale di Android è che il ciclo di vita di un processo dell'applicazione non è controllato direttamente dall'applicazione stessa. Viene invece determinata dal sistema tramite una combinazione delle parti dell'applicazione che il sistema sa essere in esecuzione, dell'importanza di queste cose per l'utente e della quantità di memoria complessiva disponibile nel sistema.

È importante che gli sviluppatori di applicazioni comprendano in che modo i diversi componenti dell'applicazione (in particolare Activity, Service e BroadcastReceiver) influiscono sul ciclo di vita del processo dell'applicazione. Se non utilizzi questi componenti correttamente, il sistema potrebbe interrompere il processo dell'applicazione mentre sta svolgendo un'attività importante.

Un esempio comune di bug del ciclo di vita del processo è un BroadcastReceiver che avvia un thread quando riceve un Intent nel suo metodo BroadcastReceiver.onReceive() e poi ritorna dalla funzione. Una volta restituito, il sistema considera che BroadcastReceiver non sia più attivo e che il relativo processo di hosting non sia più necessario, a meno che non siano attivi altri componenti dell'applicazione.

Di conseguenza, il sistema può interrompere il processo in qualsiasi momento per recuperare la memoria e, con questa operazione, termina il thread generato in esecuzione nel processo. La soluzione a questo problema solitamente consiste nel pianificare un JobService da BroadcastReceiver in modo che il sistema sappia che nel processo è in corso un'attività.

Per determinare quali processi terminare quando la memoria è in esaurimento, Android colloca ogni processo in una gerarchia di importanza in base ai componenti in esecuzione e allo stato di questi componenti. In ordine di importanza, questi tipi di processo sono:

  1. Un processo in primo piano è necessario per ciò che l'utente sta facendo al momento. Vari componenti dell'applicazione possono far sì che il processo contenitore venga considerato in primo piano in modi diversi. Un processo è considerato in primo piano se è soddisfatta una delle seguenti condizioni:
  2. Nel sistema sono presenti solo alcuni di questi processi e vengono terminati solo come ultima risorsa se la memoria è così ridotta che nemmeno questi processi possono continuare a funzionare. In genere, se ciò accade, il dispositivo ha raggiunto uno stato di paging della memoria, pertanto questa azione è necessaria per mantenere reattiva l'interfaccia utente.

  3. Un processo visibile esegue un'operazione di cui l'utente è attualmente a conoscenza, pertanto l'interruzione ha un impatto negativo significativo sull'esperienza utente. Un processo è considerato visibile nelle seguenti condizioni:
    • È in esecuzione un Activity visibile all'utente sullo schermo, ma non in primo piano (è stato chiamato il suo metodo onPause()). Ciò potrebbe accadere, ad esempio, se il Activity in primo piano viene visualizzato come finestra di dialogo che consente di vedere il Activity precedente dietro.
    • Ha un Service in esecuzione come servizio in primo piano tramite Service.startForeground() (che chiede al sistema di trattare il servizio come qualcosa di noto all'utente o, in sostanza, come se fosse visibile).
    • Ospita un servizio utilizzato dal sistema per una determinata funzionalità nota all'utente, ad esempio uno sfondo animato o un servizio di metodo di inserimento.

    Il numero di questi processi in esecuzione nel sistema è meno limitato rispetto ai processi in primo piano, ma comunque relativamente controllato. Queste procedure sono considerate estremamente importanti e non vengono interrotte, a meno che non sia necessario per mantenere in esecuzione tutte le procedure in primo piano.

  4. Un processo di servizio è un processo che contiene un Service avviato con il metodo startService(). Anche se questi procedimenti non sono direttamente visibili all'utente, in genere svolgono attività che interessano l'utente (ad esempio il caricamento o il download di dati di rete in background), pertanto il sistema mantiene sempre in esecuzione questi procedimenti a meno che non sia disponibile memoria sufficiente per conservare tutti i procedimenti in primo piano e visibili.

    I servizi in esecuzione da molto tempo (ad esempio 30 minuti o più) potrebbero essere svalutati in termini di importanza per consentire il passaggio del relativo processo all'elenco memorizzato nella cache.

    Le procedure che devono essere eseguite per un lungo periodo possono essere create con setForeground. Se si tratta di un processo periodico che richiede un'ora di esecuzione rigorosa, può essere pianificato tramite AlarmManager. Per ulteriori informazioni, consulta la sezione Supporto per i worker a lungo termine. In questo modo si evitano situazioni in cui i servizi a lungo termine che utilizzano risorse eccessive, ad esempio con perdite di memoria, impediscono al sistema di offrire un'esperienza utente ottimale.

  5. Un processo memorizzato nella cache è un processo che al momento non è necessario, pertanto il sistema è libero di interromperlo in base alle necessità quando risorse come la memoria sono necessarie altrove. In un sistema con comportamento normale, questi sono gli unici processi coinvolti nella gestione delle risorse.

    Un sistema ben funzionante ha sempre a disposizione più processi memorizzati nella cache, per passare in modo efficiente da un'applicazione all'altra e termina regolarmente le app memorizzate nella cache in base alle esigenze. Solo in situazioni molto critiche il sistema arriva a un punto in cui tutti i processi memorizzati nella cache vengono interrotti e deve iniziare a interrompere i processi di servizio.

    Poiché i processi memorizzati nella cache possono essere interrotti dal sistema in qualsiasi momento, le app devono interrompere tutte le attività mentre sono nello stato memorizzato nella cache. Se l'app deve eseguire operazioni fondamentali per l'utente, deve utilizzare una delle API sopra indicate per eseguire le operazioni da uno stato di processo attivo.

    I processi memorizzati nella cache spesso contengono una o più istanze Activity che non sono attualmente visibili all'utente (il loro metodo Activity è stato chiamato e ha restituito un valore).onStop() A condizione che implementino correttamente il ciclo di vita di Activity quando il sistema interrompe queste procedure, l'esperienza dell'utente non viene influenzata quando torna nell'app. Può ripristinare lo stato salvato in precedenza quando l'attività associata viene ricreata in una nuova procedura. Tieni presente che non è garantito che onDestroy() viene chiamato nel caso in cui un processo venga interrotto dal sistema. Per maggiori dettagli, vedi Activity.

    A partire da Android 13, un processo dell'app potrebbe ricevere un tempo di esecuzione limitato o nullo finché non entra in uno degli stati di ciclo di vita attivi sopra indicati.

    I processi memorizzati nella cache vengono conservati in un elenco. Il criterio di ordinamento esatto per questo elenco è un dettaglio di implementazione della piattaforma. In genere, cerca di mantenere le attività più utili, ad esempio quelle che ospitano l'applicazione Home dell'utente o l'ultima attività visualizzata dall'utente, prima di altri tipi di attività. È possibile applicare anche altri criteri per l'interruzione dei processi, ad esempio l'impostazione di limiti rigidi sul numero di processi consentiti o la limitazione del tempo di memorizzazione nella cache di un processo.

Quando decide come classificare un processo, il sistema basa la sua decisione sul livello più importante trovato tra tutti i componenti attualmente attivi nel processo. Consulta la documentazione di Activity, Service e BroadcastReceiver per ulteriori dettagli su come ciascuno di questi componenti contribuisce al ciclo di vita complessivo di un processo e dell'applicazione.

La priorità di un processo può essere aumentata anche in base ad altre dipendenze. Ad esempio, se il processo A è associato a un Service con il flag Context.BIND_AUTO_CREATE o utilizza un ContentProvider nel processo B, la classificazione del processo B è sempre almeno importante quanto quella del processo A.