Warm Start è uno scenario di avvio di un'app Android in cui il processo dell'app esiste già in memoria (ad esempio, dopo essere stata minimizzata), ma l'Activity è stata distrutta dal sistema per risparmiare risorse. Application.onCreate è già stato eseguito, le classi sono caricate, ma l'UI viene creata di nuovo. Secondo Google, 2024, Warm Start richiede 200–800 ms e costituisce circa il 40% di tutti gli avvii su dispositivi con 4 GB di RAM.
Punti chiave
Warm Start è uno stato tra Cold Start e Hot Start: il processo dell'app esiste in memoria (a volte nella cache di background di Linux), ma l'Activity non è attiva e verrà creata di nuovo. Quando Android esaurisce la RAM, può rimuovere l'Activity dallo stack, lasciando vivo il processo. Quando l'utente torna all'app, si verifica un Warm Start: viene creata una nuova istanza di Activity, i metodi del ciclo di vita onCreate → onStart → onResume vengono eseguiti, ma Application.onCreate e il caricamento delle classi vengono saltati.
Android decide di rimuovere l'Activity in base alla priorità del processo (rango di importanza). Un'Activity in background (livello PROCESS_STATE_IMPORTANT_FOREGROUND o PROCESS_STATE_TOP_SLEEPING) può essere distrutta 5–30 minuti dopo la minimizzazione dell'app, a seconda della RAM disponibile. Su dispositivi con 3 GB di RAM, l'Activity può essere rimossa entro 10 minuti; su dispositivi con 8 GB di RAM, dopo diverse ore. Importante: durante Warm Start, onSaveInstanceState viene chiamato prima che l'Activity venga distrutta e lo sviluppatore può salvare lo stato dell'UI.
L'utente non vede la differenza tra Warm e Cold Start — tocca semplicemente l'icona dell'app e aspetta. Tuttavia, durante Warm Start, potrebbe apparire una schermata bianca se l'app non ha impostato un tema personalizzato per la finestra di avvio. Google consiglia di impostare un tema personalizzato nel manifesto (Theme.AppCompat.Light o Theme.Material3.DayNight) per l'Activity di avvio, per evitare lo sfarfallio dello schermo bianco/nero durante Warm Start. Su Android 12+, l'API SplashScreen nasconde anche questo effetto.
Comprendere la differenza tra i tre tipi di avvio è essenziale per scegliere la giusta strategia di profilazione e ottimizzazione. Ogni tipo ha la propria durata, i propri colli di bottiglia e i propri strumenti di misurazione.
| Criterio | Cold Start | Warm Start | Hot Start |
|---|---|---|---|
| Processo | Creato da zero | Esiste in memoria | Esiste in memoria |
| Application.onCreate | Eseguito | Non eseguito | Non eseguito |
| Activity | Creata da zero | Creata da zero | Ripristinata dallo stack |
| Tempo | 1–5 secondi | 200–800 ms | < 200 ms |
| Activity.onCreate | Completo | Completo (con ripristino) | Saltato |
In pratica, Warm Start rappresenta dal 30% al 60% di tutti gli avvii delle app, a seconda delle abitudini dell'utente e della RAM del dispositivo. Gli utenti che tengono molte app aperte (multitasking) incontrano Warm Start più spesso. Per i social network e i messaggeri, Warm Start è lo scenario più comune poiché l'app è sempre in background. Per le app bancarie, al contrario, predomina Cold Start (pulizia forzata del processo per motivi di sicurezza).
Warm Start consiste in tre fasi, ciascuna può essere misurata e ottimizzata. A differenza di Cold Start, non c'è fase di fork o caricamento delle classi, ma c'è una fase di ripristino dello stato che può essere costosa.
Il sistema verifica se l'app ha un tema per la finestra di avvio. Se il tema non è impostato, viene visualizzata una schermata bianca (o nera, a seconda del sistema). Se il tema è impostato, viene mostrato lo sfondo del tema. Questa fase richiede 10–30 ms, ma è visivamente percepibile se il tema non corrisponde all'UI reale dell'app. Utilizza Theme.Material3.DayNight con un windowBackground personalizzato il cui colore corrisponde allo sfondo del primo schermo — questo crea un effetto di caricamento istantaneo.
Il sistema chiama onCreate passando il Bundle savedInstanceState che è stato salvato in onSaveInstanceState prima che l'Activity fosse distrutta. Se l'app ha salvato correttamente lo stato (testo dei campi, posizione di scorrimento, dati ViewModel), il ripristino avviene rapidamente. In caso contrario, l'Activity inizia da zero e l'utente vede un caricatore mentre i dati vengono caricati. Punto chiave: gli oggetti ViewModel sopravvivono a Warm Start solo se il processo non è stato distrutto — durante Warm Start, il ViewModel rimane in memoria.
Dopo onCreate, onStart → onResume vengono eseguiti e il sistema attiva il primo disegno. TTFD (Time To First Draw) per Warm Start dovrebbe essere inferiore a 300 ms su un dispositivo di fascia media. Se il primo schermo contiene una RecyclerView complessa con Views pesanti o carica immagini dalla rete, TTFD può superare la soglia. Utilizza Placeholder e Shimmer per un caricamento fluido del contenuto dopo il primo fotogramma.
Misurare Warm Start è più complesso di Cold Start perché è necessario simulare lo stato in cui il processo è vivo ma l'Activity è distrutta. Il comando ADB standard con il flag -S non funziona — uccide il processo. Utilizza approcci diversi per Warm Start.
Prima, avvia l'app tramite adb shell monkey o tocca l'icona, poi minimizzala (adb shell input keyevent 3 keyevent HOME). Aspetta 5–10 secondi per consentire al sistema di rimuovere l'Activity, quindi esegui adb shell am start -W (senza -S). Il comando restituirà un tempo di avvio più breve rispetto a Cold Start. Per riproducibilità, usa uno script: avvia → attendi → home → attendi → avvia.
# Simulazione di Warm Start tramite ADB
$ adb shell am start -W \
com.example.app/.MainActivity
# Output (Warm Start):
# ThisTime: 412 ms
# TotalTime: 412 ms
# WaitTime: 423 ms
La libreria androidx.benchmark.macro supporta la misurazione di Warm Start. Nel test, imposta startupMode = StartupMode.WARM — la libreria avvierà l'app, la minimizzerà, attenderà (ritardo configurabile) e poi misurerà il riavvio. Macrobenchmark esegue 10–20 iterazioni e calcola i percentili. In CI/CD, puoi impostare una soglia: se P50 Warm Start supera 600 ms, il test fallisce. Questo permette di tracciare le regressioni ad ogni commit.
Firebase distingue automaticamente tra Cold e Warm Start in base al tempo dall'ultima chiusura dell'app. Se l'app è stata aperta negli ultimi 30 minuti, Firebase classifica l'avvio come Warm. Nella console Firebase, vedrai grafici separati per ogni tipo di avvio, permettendoti di valutare l'efficacia delle ottimizzazioni. Ad esempio, dopo aver implementato la preservazione dello stato in ViewModel, potresti vedere una riduzione del 30% del tempo di Warm Start.
L'ottimizzazione di Warm Start si concentra su due aree: accelerare Activity.onCreate e il corretto ripristino dello stato. Poiché Application.onCreate e il caricamento delle classi sono già stati completati, il principale collo di bottiglia è il codice UI del primo schermo.
Se lo stato salvato (savedInstanceState) contiene dati che necessitano di deserializzazione (Bitmap, String, JSON), fallo su un thread in background. Invece di leggere direttamente dal Bundle in onCreate, avvia una coroutine e mostra una schermata shimmer. In pratica, la deserializzazione del Bundle su un dispositivo di fascia media richiede 20–100 ms — sembra poco, ma per Warm Start è il 10–50% del tempo totale. Utilizza il Saved State Module di Jetpack, che salva e ripristina automaticamente lo stato del ViewModel nel Bundle o nel database.
L'inflazione del layout XML è una delle fasi più costose di Warm Start. Se il primo schermo utilizza un CoordinatorLayout complesso con AppBar, CollapsingToolbar, NestedScrollView più tre RecyclerView, il tempo di inflazione può raggiungere 300 ms. Soluzioni: usa ConstraintLayout per una gerarchia piatta, applica ViewStub per le sezioni non visibili all'avvio (bottom sheet, dialog), abilita l'inflazione asincrona per i fragment pesanti tramite AsyncLayoutInflater. In Jetpack Compose, l'inflazione non è necessaria, ma la compilazione dell'albero Compose durante Warm Start può richiedere un tempo simile.
Durante Warm Start, i dati che l'app ha caricato nella sessione precedente potrebbero essere già in cache: database Room, SharedPreferences, cache in memoria in ViewModel. Se il tuo primo schermo mostra un elenco dal server, controlla la cache all'avvio e aggiorna i dati in background. Usa la strategia cache-then-network: prima mostra i dati in cache (istantaneamente), poi aggiorna dal server (asincrono). Questo riduce il tempo percepito di Warm Start a 100–200 ms.
// ViewModel con caching per Warm Start
class FeedViewModel : ViewModel() {
private val cache = MutableStateFlow<List<Item>>(emptyList())
init {
// Prima cache, poi rete
viewModelScope.launch {
cache.emit(db.getItems()) // Warm Start: dati già nel DB
cache.emit(api.fetchItems()) // Aggiornamento in background
}
}
}
La corretta preservazione dello stato è il fattore chiave che distingue un buon Warm Start da uno cattivo. L'utente si aspetta di tornare all'app e vedere esattamente ciò che ha lasciato — inclusa la posizione di scorrimento, il testo nei campi e le schede selezionate.
Il sistema chiama onSaveInstanceState quando l'Activity viene distrutta, ma PRIMA che il processo possa essere ucciso. Solo i tipi di dati semplici (String, Int, Parcelable, Serializable) vengono salvati nel Bundle. Per dati complessi, usa SavedStateHandle in ViewModel — salva e ripristina automaticamente i campi durante Warm Start. A differenza di onSaveInstanceState, SavedStateHandle funziona anche se il processo sopravvive a Warm Start (ViewModel non viene distrutto). Esempio: per il testo in EditText, usa SavedStateHandle.getLiveData(“text”) — il testo verrà automaticamente salvato e ripristinato.
Se il processo non è stato ucciso durante Warm Start, il ViewModel rimane in memoria e onCleared non viene chiamato. Ciò significa che tutti i dati caricati nella sessione precedente sono immediatamente disponibili. Tuttavia, se il processo è stato ucciso (dispositivo in sospensione profonda per più di 30 minuti), il ViewModel viene distrutto e ricreato con SavedStateHandle. Per un corretto comportamento del ViewModel durante Warm Start, usa SavedStateHandle con i campi che devono essere ripristinati in qualsiasi scenario. Differenza: ViewModel con @HiltViewModel supporta SavedStateHandle automaticamente.
| Meccanismo | Processo vivo | Processo ucciso |
|---|---|---|
| ViewModel | Dati in memoria | Distrutto, ricreato |
| SavedStateHandle | Dati in memoria | Ripristinato da Bundle |
| onSaveInstanceState | Chiamato alla rimozione dell'Activity | Non chiamato |
| Room DB | Cache disponibile | Cache disponibile (disco) |
Uno dei problemi più comuni di Warm Start — perdere la posizione di scorrimento. L'utente ha scrollato fino al 50° elemento, minimizzato l'app, è tornato — e vede l'inizio della lista. Soluzione: salva layoutManager.onSaveInstanceState (salva la posizione e l'offset del primo elemento visibile) e ripristinalo in onRestoreInstanceState. Puoi anche salvare l'ultima posizione visibile in SharedPreferences con una chiave data/ora per ripristinare rapidamente la posizione durante Warm Start.
// Salvare la posizione di scorrimento di RecyclerView
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(
"rv_state", binding.recyclerView
.layoutManager?.onSaveInstanceState()
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedInstanceState?.getParcelable<Parcelable>("rv_state")
?.let { binding.recyclerView.layoutManager?.onRestoreInstanceState(it) }
}
Due esempi pratici di ottimizzazione di Warm Start: uso di SavedStateHandle in ViewModel e ripristino asincrono di dati complessi dopo l'avvio.
SavedStateHandle salva automaticamente i campi nel Bundle e li ripristina durante Warm Start. Il campo del profilo utente (String, JSON) verrà ripristinato senza richieste superflue al server. Se il processo è stato ucciso, SavedStateHandle carica l'ultimo stato salvato dal Bundle.
class ProfileViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val profile: StateFlow<Profile?>
get() = savedStateHandle
.getStateFlow("profile", null)
fun loadProfile(id: String) {
viewModelScope.launch {
savedStateHandle["profile"] =
api.getProfile(id)
}
}
}
// Warm Start: profile non è null, UI senza caricatore
// Dopo il caricamento: profile si aggiorna in SavedStateHandle
Se il primo schermo contiene un layout complesso (mappa, gradiente, più elenchi), usa AsyncLayoutInflater per gonfiare elementi pesanti in background. Mentre il layout viene gonfiato, mostra un placeholder con effetto shimmer. Questo è particolarmente importante per Warm Start, dove ogni millisecondo conta. AsyncLayoutInflater viene eseguito su un thread in background e passa la View pronta a un callback sul thread principale.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Layout placeholder per rendering istantaneo
setContentView(R.layout.placeholder_shimmer)
// Caricamento asincrono del layout pesante
AsyncLayoutInflater(this).inflate(
R.layout.activity_main_complex,
findViewById(R.id.container)
) { view, resId, parent ->
parent?.removeAllViews()
parent?.addView(view)
}
}
}
Domande frequenti
Sì, se al momento di Warm Start il sistema decide di uccidere il processo dell'app (ad esempio, per liberare memoria per un'altra app), l'avvio diventa Cold Start da zero. Ciò accade su dispositivi con 2–3 GB di RAM con più app in esecuzione simultanea. In effetti, Warm Start è garantito solo per 10–20 minuti dopo la minimizzazione su dispositivi di fascia media.
Sì, se il processo non è stato ucciso, il ViewModel rimane in memoria e onCleared non viene chiamato. Questo è un vantaggio chiave di Warm Start: tutti i dati caricati tramite richieste di rete, la cache in ViewModel — tutto immediatamente disponibile. Se il processo è stato ucciso, il ViewModel viene ricreato tramite ViewModelProvider.Factory o @HiltViewModel e SavedStateHandle ripristina i campi salvati.
Teoricamente, Warm Start è sempre più veloce di Cold Start, ma in pratica ci sono scenari in cui la differenza è minima: se Application.onCreate era leggero (50 ms) e Activity.onCreate è pesante (800 ms), allora Warm Start (800 ms) è quasi uguale a Cold Start (850 ms). In questo caso, dovresti ottimizzare non Application, ma Activity.onCreate — diventa il collo di bottiglia per Warm Start.
L'API SplashScreen su Android 12+ mostra un splash di sistema (icona su sfondo colorato) immediatamente all'avvio — sia per Cold che per Warm Start. Per Warm Start, lo splash viene mostrato solo per 100–300 ms, dopodiché viene sostituito dall'UI dell'app. SplashScreen non accelera l'avvio in sé, ma maschera il tempo di creazione dell'Activity, migliorando la percezione.
Sì, perché Warm Start si verifica 2–3 volte più spesso di Cold Start. Se Cold Start richiede 1.2 secondi e Warm Start 600 ms, allora il 40% degli avvii (Warm) richiede ancora 0.6 secondi, che è percepibile. Ottimizzare Warm Start a 200–300 ms dà all'utente una sensazione di ritorno istantaneo. Su dispositivi con 6+ GB di RAM, Warm Start può rappresentare fino all'80% di tutti gli avvii, rendendo la sua ottimizzazione una priorità.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche