Gestire le modifiche alla configurazione

Alcune configurazioni dei dispositivi possono cambiare mentre l'app è in esecuzione. Questi includono, a titolo esemplificativo:

  • Dimensioni di visualizzazione dell'app
  • Orientamento dello schermo
  • Dimensioni e spessore dei caratteri
  • Impostazioni internazionali
  • Confronto tra modalità Buio e modalità Luce
  • Disponibilità della tastiera

La maggior parte di queste modifiche alla configurazione avviene a causa di alcune interazioni degli utenti. Ad esempio, la rotazione o la piegatura del dispositivo comporta la modifica della quantità di spazio sullo schermo disponibile per l'app. Analogamente, la modifica di impostazioni del dispositivo quali dimensioni del carattere, lingua o tema preferito cambia i rispettivi valori nell'oggetto Configuration.

Questi parametri di solito richiedono modifiche all'interfaccia utente dell'applicazione sufficientemente grandi che la piattaforma Android disponga di un meccanismo specifico per la modifica. Questo meccanismo è Activity ricreazione.

Attività ricreative

Il sistema ricrea un Activity quando si verifica una modifica alla configurazione. Per farlo, il sistema chiama onDestroy() ed elimina l'istanza Activity esistente. Crea quindi una nuova istanza utilizzando onCreate() e questa nuova istanza di Activity viene inizializzata con la nuova configurazione aggiornata. Questo significa anche che il sistema ricrea anche l'interfaccia utente con la nuova configurazione.

Il comportamento di ricreazione consente all'applicazione di adattarsi a nuove configurazioni mediante il ricaricamento automatico dell'applicazione con risorse alternative che corrispondono alla nuova configurazione del dispositivo.

Esempio di attività ricreative

Considera un TextView che mostra un titolo statico utilizzando android:text="@string/title", come definito in un file XML di layout. Quando viene creata, la vista imposta il testo esattamente una volta, in base alla lingua corrente. Se la lingua cambia, il sistema ricrea l'attività. Di conseguenza, il sistema ricrea anche la vista e la inizializza al valore corretto in base alla nuova lingua.

Lo svago cancella anche qualsiasi stato mantenuto come campo in Activity o nei suoi contenuti Fragment, View o altri oggetti. Il motivo è che la ricreazione di Activity crea un'istanza completamente nuova di Activity e della UI. Inoltre, il precedente Activity non è più visibile o valido, pertanto tutti i riferimenti rimanenti all'oggetto o agli oggetti che contiene sono inattivi. Possono causare bug, perdite di memoria e arresti anomali.

Aspettative degli utenti

L'utente di un'app si aspetta che lo stato venga mantenuto. Se un utente compila un modulo e apre un'altra app in modalità multi-finestra per fare riferimento alle informazioni, l'esperienza utente non è soddisfacente se torna in un modulo cancellato o in un'altra parte dell'app. In qualità di sviluppatore, devi fornire un'esperienza utente coerente mediante le modifiche alla configurazione e la ricreazione dell'attività.

Per verificare se lo stato viene mantenuto nell'applicazione, puoi eseguire azioni che causano modifiche alla configurazione sia quando l'app è in primo piano sia mentre è in background. Queste azioni includono:

  • Rotazione del dispositivo
  • Attivazione della modalità multi-finestra
  • Ridimensionamento dell'applicazione in modalità multi-finestra o in formato libero
  • Piegare un dispositivo pieghevole con più display
  • Modifica del tema del sistema, ad esempio modalità Buio e modalità Luce
  • Modifica delle dimensioni del carattere
  • Modificare la lingua del sistema o dell'app
  • Collegare o scollegare una tastiera hardware
  • Collegamento o scollegamento di un dock

Esistono tre approcci principali che puoi adottare per preservare lo stato pertinente attraverso il divertimento Activity. L'utilizzo dipende dal tipo di stato che vuoi conservare:

  • Persistenza locale per gestire l'interruzione del processo per dati complessi o di grandi dimensioni. L'archiviazione locale permanente include database o DataStore.
  • Oggetti conservati, come le istanze ViewModel, per gestire lo stato relativo all'interfaccia utente in memoria mentre l'utente utilizza attivamente l'app.
  • Stato dell'istanza salvata per gestire l'interruzione del processo avviato dal sistema e mantenere lo stato temporaneo che dipende dall'input o dalla navigazione dell'utente.

Per ulteriori informazioni sulle API relative a ciascuna di queste opzioni in dettaglio e per l'utilizzo appropriato, consulta Salvare gli stati dell'interfaccia utente.

Limita ricreazione attività

Puoi impedire la ricreazione automatica dell'attività per determinate modifiche alla configurazione. La ricreazione di Activity comporta la nuova creazione dell'intera UI e di tutti gli oggetti derivati da Activity. Potresti avere buoni motivi per evitarlo. Ad esempio, l'app potrebbe non dover aggiornare le risorse durante una modifica della configurazione specifica o potresti avere una limitazione delle prestazioni. In questo caso, puoi dichiarare che la tua attività gestisce la modifica della configurazione e impedisce al sistema di riavviare l'attività.

Per disattivare la ricreazione dell'attività per determinate modifiche alla configurazione, aggiungi il tipo di configurazione a android:configChanges nella voce <activity> del file AndroidManifest.xml. I valori possibili sono riportati nella documentazione relativa all'attributo android:configChanges.

Il seguente codice manifest disabilita la ricreazione di Activity per MyActivity quando l'orientamento dello schermo e la disponibilità della tastiera cambiano:

<activity
    android:name=".MyActivity"
    android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
    android:label="@string/app_name">

Alcune modifiche alla configurazione causano sempre il riavvio dell'attività. Non puoi disabilitarle. Ad esempio, non puoi disattivare la cambio dinamica dei colori introdotta in Android 12L (livello API 32).

Reagire alle modifiche alla configurazione nel sistema Visualizza

Nel sistema View, quando si verifica una modifica della configurazione per la quale hai disattivato la ricreazione di Activity, l'attività riceve una chiamata a Activity.onConfigurationChanged(). Tutte le viste allegate ricevono anche una chiamata a View.onConfigurationChanged(). Per le modifiche alla configurazione che non hai aggiunto a android:configChanges, il sistema ricrea l'attività come di consueto.

Il metodo di callback onConfigurationChanged() riceve un oggetto Configuration che specifica la nuova configurazione del dispositivo. Leggi i campi nell'oggetto Configuration per determinare la nuova configurazione. Per apportare le modifiche successive, aggiorna le risorse che utilizzi nell'interfaccia. Quando il sistema chiama questo metodo, l'oggetto Resources dell'attività viene aggiornato in modo che restituisca risorse in base alla nuova configurazione. In questo modo puoi reimpostare gli elementi dell'interfaccia utente senza che il sistema riavvii l'attività.

Ad esempio, la seguente implementazione di onConfigurationChanged() verifica se è disponibile una tastiera:

Kotlin

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)

    // Checks whether a keyboard is available
    if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show()
    } else if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_NO) {
        Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show()
    }
}

Java

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks whether a keyboard is available
    if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show();
    } else if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO){
        Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show();
    }
}

Se non devi aggiornare l'applicazione in base a queste modifiche alla configurazione, puoi invece non implementare onConfigurationChanged(). In questo caso, tutte le risorse utilizzate prima della modifica della configurazione vengono comunque utilizzate e hai solo evitato il riavvio dell'attività. Ad esempio, un'app per TV potrebbe non voler reagire quando una tastiera Bluetooth viene collegata o scollegata.

Stato di conservazione

Quando utilizzi questa tecnica, devi comunque mantenere lo stato durante il normale ciclo di vita dell'attività. Ciò è dovuto ai seguenti motivi:

  • Modifiche non evitabili: le modifiche alla configurazione che non puoi impedire possono riavviare l'applicazione.
  • Terminazione dei processi: l'applicazione deve essere in grado di gestire la morte dei processi avviati dal sistema. Se l'utente abbandona l'applicazione e l'app passa in background, il sistema potrebbe distruggerla.

Reagisci alle modifiche alla configurazione in Jetpack Compose

Jetpack Compose consente alla tua app di reagire più facilmente alle modifiche alla configurazione. Tuttavia, se disattivi la ricreazione di Activity per tutte le modifiche alla configurazione in cui è possibile farlo, l'app deve comunque gestire correttamente le modifiche alla configurazione.

L'oggetto Configuration è disponibile nella gerarchia dell'interfaccia utente di Scrivi con la composizione locale di LocalConfiguration. Ogni volta che cambia, le funzioni componibili leggono da LocalConfiguration.current la ricomposizione. Per informazioni su come funziona la composizione locale, consulta Dati con ambito locale con ComposizioneLocal.

Esempio

Nell'esempio seguente, un oggetto componibile mostra una data con un formato specifico. L'oggetto componibile reagisce alle modifiche alla configurazione delle impostazioni internazionali del sistema chiamando ConfigurationCompat.getLocales() con LocalConfiguration.current.

@Composable
fun DateText(year: Int, dayOfYear: Int) {
    val dateTimeFormatter = DateTimeFormatter.ofPattern(
        "MMM dd",
        ConfigurationCompat.getLocales(LocalConfiguration.current)[0]
    )
    Text(
        dateTimeFormatter.format(LocalDate.ofYearDay(year, dayOfYear))
    )
}

Per evitare la ricreazione di Activity quando le impostazioni internazionali cambiano, il Activity che ospita il codice di scrittura deve disattivare le modifiche alla configurazione internazionale. Per farlo, imposta android:configChanges su locale|layoutDirection.

Modifiche alla configurazione: concetti chiave e best practice

Ecco i concetti chiave che devi conoscere quando lavori alle modifiche alla configurazione:

  • Configurazioni: le configurazioni dei dispositivi definiscono la modalità di visualizzazione dell'interfaccia utente all'utente, ad esempio le dimensioni di visualizzazione dell'app, le impostazioni internazionali o il tema di sistema.
  • Modifiche alla configurazione: le configurazioni cambiano tramite l'interazione dell'utente. Ad esempio, l'utente potrebbe modificare le impostazioni del dispositivo o il modo in cui interagisce fisicamente con il dispositivo. Non c'è modo di impedire le modifiche alla configurazione.
  • Ricreazione Activity: le modifiche alla configurazione comportano la ricreazione di Activity per impostazione predefinita. Si tratta di un meccanismo integrato per reinizializzare lo stato dell'app per la nuova configurazione.
  • Distruzione di Activity: la ricreazione di Activity causa l'eliminazione della vecchia istanza Activity da parte del sistema e la creazione di una nuova istanza al suo posto. La vecchia istanza ora è obsoleta. Eventuali riferimenti rimanenti comportano perdite di memoria, bug o arresti anomali.
  • Stato: lo stato nella vecchia istanza Activity non è presente nella nuova istanza Activity perché si tratta di due istanze di oggetti diverse. Mantieni l'app e lo stato dell'utente come descritto in Salvare gli stati dell'interfaccia utente.
  • Disattiva: la disattivazione della ricreazione dell'attività per un tipo di modifica alla configurazione rappresenta una potenziale ottimizzazione. Richiede che l'app venga aggiornata correttamente in base alla nuova configurazione.

Per offrire una buona esperienza utente, attieniti alle seguenti best practice:

  • Preparati a modifiche frequenti della configurazione: non dare per scontato che le modifiche alla configurazione siano rare o non avvengano mai, indipendentemente dal livello API, dal fattore di forma o dal toolkit dell'interfaccia utente. Quando un utente causa una modifica alla configurazione, si aspetta che le app vengano aggiornate e continuano a funzionare correttamente con la nuova configurazione.
  • Mantieni stato: non perdere lo stato dell'utente quando si verifica la ricreazione di Activity. Mantieni lo stato come descritto in Salvare gli stati dell'interfaccia utente.
  • Evita la disattivazione come soluzione rapida: non disattivare la ricreazione di Activity come scorciatoia per evitare la perdita di stato. Se disattivi la ricreazione delle attività, devi mantenere la promessa di gestire la modifica e potresti comunque perdere lo stato a causa della ricreazione di Activity di altre modifiche alla configurazione, della disattivazione dell'app o della chiusura dell'app. Non è possibile disattivare completamente la ricreazione di Activity. Mantieni lo stato come descritto in Salvare gli stati dell'interfaccia utente.
  • Non evitare modifiche alla configurazione: non applicare limitazioni relative a orientamento, proporzioni o ridimensionabilità per evitare modifiche alla configurazione e ricreazione di Activity. Questo influisce negativamente sugli utenti che vogliono utilizzare la tua app nel modo che preferiscono.

Gestire le modifiche alla configurazione basate sulle dimensioni

Le modifiche alla configurazione in base alle dimensioni possono essere applicate in qualsiasi momento e hanno maggiori probabilità di essere eseguita su un dispositivo con schermo di grandi dimensioni, in cui gli utenti possono attivare la modalità multi-finestra. Si aspettano che la tua app funzioni bene in quell'ambiente.

Esistono due tipi generali di modifiche delle dimensioni: significativo e non significativo. Una modifica delle dimensioni significativa si verifica quando alla nuova configurazione viene applicato un insieme diverso di risorse alternative a causa di differenze nelle dimensioni dello schermo, ad esempio larghezza, altezza o larghezza minima. Queste risorse includono quelle definite dall'app e quelle di qualsiasi sua libreria.

Limita la ricreazione delle attività per le modifiche alla configurazione basate sulle dimensioni

Quando disattivi la ricreazione di Activity per le modifiche alla configurazione basata sulle dimensioni, il sistema non ricrea il Activity. ma riceve una chiamata al numero Activity.onConfigurationChanged(). Tutte le viste allegate ricevono una chiamata a View.onConfigurationChanged().

La ricreazione di Activity è disattivata per modifiche alla configurazione basata sulle dimensioni se è presente android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout" nel file manifest.

Consenti la ricreazione dell'attività per le modifiche alla configurazione in base alle dimensioni

Su Android 7.0 (livello API 24) e versioni successive, la ricreazione di Activity avviene solo per le modifiche alla configurazione basata sulle dimensioni se la variazione delle dimensioni è significativa. Quando il sistema non ricrea un Activity a causa delle dimensioni insufficienti, potrebbe chiamare Activity.onConfigurationChanged() e View.onConfigurationChanged().

Occorre tenere conto di alcune avvertenze in merito ai callback Activity e View quando Activity non viene ricreato:

  • Da Android 11 (livello API 30) ad Android 13 (livello API 33), Activity.onConfigurationChanged() non viene chiamato.
  • Esiste un problema noto per cui View.onConfigurationChanged() potrebbe non essere chiamato in alcuni casi su Android 12L (livello API 32) e nelle versioni precedenti di Android 13 (livello API 33). Per ulteriori informazioni, consulta questo problema pubblico. Il problema è stato risolto nelle successive release di Android 13 e Android 14.

Per il codice che dipende dall'ascolto delle modifiche alla configurazione basate sulle dimensioni, consigliamo di utilizzare un'utilità View con un valore View.onConfigurationChanged() con override invece di fare affidamento sulla ricreazione Activity o Activity.onConfigurationChanged().