Ottimizzazione in background

I processi in background possono richiedere molta memoria e batteria. Ad esempio, una trasmissione implicita potrebbe avviare molti processi in background che si sono registrati per ascoltarla, anche se questi processi potrebbero non funzionare molto. Ciò può avere un impatto sostanziale sia sulle prestazioni del dispositivo sia sull'esperienza utente.

Per ovviare a questo problema, Android 7.0 (livello API 24) applica le seguenti limitazioni:

Se la tua app utilizza uno di questi intent, devi rimuovere le dipendenze al più presto in modo da poter scegliere come target i dispositivi con Android 7.0 o versioni successive. Il framework Android fornisce diverse soluzioni per ridurre la necessità di queste trasmissioni implicite. Ad esempio, JobScheduler e il nuovo WorkManager forniscono meccanismi solidi per pianificare le operazioni di rete quando vengono soddisfatte condizioni specifiche, ad esempio una connessione a una rete senza misurazione. Ora puoi utilizzare JobScheduler anche per reagire alle modifiche ai fornitori di contenuti. Gli oggetti JobInfo incapsulano i parametri utilizzati da JobScheduler per pianificare il job. Quando le condizioni del job sono soddisfatte, il sistema lo esegue sul JobService della tua app.

In questa pagina scopriremo come utilizzare metodi alternativi, come JobScheduler, per adattare la tua app a queste nuove limitazioni.

Restrizioni avviate dall'utente

Nella pagina Utilizzo batteria delle impostazioni del sistema, l'utente può scegliere tra le seguenti opzioni:

  • Senza restrizioni:consenti tutte le attività in background, il che potrebbe comportare un maggiore consumo della batteria.
  • Ottimizzata (predefinita): ottimizza la capacità di un'app di eseguire attività in background in base al modo in cui l'utente interagisce con l'app.
  • Con restrizioni:impedisce completamente a un'app di essere eseguita in background. Le app potrebbero non funzionare come previsto.

Se un'app presenta alcuni dei comportamenti dannosi descritti in Android vitals, il sistema potrebbe chiedere all'utente di limitare l'accesso dell'app alle risorse di sistema.

Se il sistema rileva un consumo eccessivo di risorse, il sistema invia una notifica all'utente, offrendogli la possibilità di limitare le azioni dell'app. I comportamenti che possono attivare la notifica includono:

  • Wakelock eccessivi: 1 wakelock parziale mantenuto per un'ora quando lo schermo è spento
  • Servizi in background eccessivi: se l'app ha come target livelli API inferiori a 26 e ha servizi in background eccessivi

Le precise limitazioni imposte sono stabilite dal produttore del dispositivo. Ad esempio, nelle build AOSP che eseguono Android 9 (livello API 28) o versioni successive, le app in esecuzione in background nello stato "con restrizioni" presentano le seguenti limitazioni:

  • Impossibile avviare i servizi in primo piano
  • I servizi in primo piano esistenti vengono rimossi dal primo piano
  • Le sveglie non si attivano
  • I job non vengono eseguiti

Inoltre, se un'app ha come target Android 13 (livello API 33) o versioni successive ed è in stato "con restrizioni", il sistema non pubblica la trasmissione BOOT_COMPLETED o la trasmissione LOCKED_BOOT_COMPLETED finché l'app non viene avviata per altri motivi.

Le restrizioni specifiche sono elencate in Restrizioni per la gestione dell'alimentazione.

Limitazioni alla ricezione delle trasmissioni delle attività di rete

Le app che hanno come target Android 7.0 (livello API 24) non ricevono le emittenti CONNECTIVITY_ACTION se si registrano per riceverle nel manifest e i processi che dipendono da questa emittente non verranno avviati. Questo potrebbe rappresentare un problema per le app che vogliono ascoltare le modifiche di rete o eseguire attività di rete in blocco quando il dispositivo si connette a una rete unmetered. Esistono già diverse soluzioni per aggirare questa limitazione nel framework Android, ma la scelta della soluzione giusta dipende da ciò che vuoi che la tua app realizzi.

Nota:un BroadcastReceiver registrato con Context.registerReceiver() continua a ricevere queste trasmissioni mentre l'app è in esecuzione.

Pianificare i job di rete su connessioni non conteggiate

Quando utilizzi la classe JobInfo.Builder per creare l'oggetto JobInfo, applica il metodo setRequiredNetworkType() e passa JobInfo.NETWORK_TYPE_UNMETERED come parametro del job. Il seguente esempio di codice pianifica l'esecuzione di un servizio quando il dispositivo si connette a una rete senza misuramento ed è in carica:

Kotlin

const val MY_BACKGROUND_JOB = 0
...
fun scheduleJob(context: Context) {
    val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    val job = JobInfo.Builder(
            MY_BACKGROUND_JOB,
            ComponentName(context, MyJobService::class.java)
    )
            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
            .setRequiresCharging(true)
            .build()
    jobScheduler.schedule(job)
}

Java

public static final int MY_BACKGROUND_JOB = 0;
...
public static void scheduleJob(Context context) {
  JobScheduler js =
      (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
  JobInfo job = new JobInfo.Builder(
    MY_BACKGROUND_JOB,
    new ComponentName(context, MyJobService.class))
      .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
      .setRequiresCharging(true)
      .build();
  js.schedule(job);
}

Quando le condizioni per il job sono soddisfatte, l'app riceve un callback per eseguire il metodo onStartJob() nel JobService.class specificato. Per vedere altri esempi di implementazione di JobScheduler, consulta l'app di esempio JobScheduler.

Una nuova alternativa a JobScheduler è WorkManager, un'API che consente di pianificare le attività in background che richiedono il completamento garantito, indipendentemente dal fatto che il processo dell'app sia attivo o meno. WorkManager sceglie il modo appropriato per eseguire il lavoro (direttamente su un thread nel processo dell'app, nonché utilizzando JobScheduler, FirebaseJobDispatcher o AlarmManager) in base a fattori come il livello dell'API del dispositivo. Inoltre, WorkManager non richiede Play Services e fornisce diverse funzionalità avanzate, come l'accostamento di attività o il controllo dello stato di un'attività. Per scoprire di più, consulta WorkManager.

Monitorare la connettività di rete durante l'esecuzione dell'app

Le app in esecuzione possono comunque ascoltare CONNECTIVITY_CHANGE con un BroadcastReceiver registrato. Tuttavia, l'API ConnectivityManager fornisce un metodo più affidabile per richiedere un callback solo quando sono soddisfatte condizioni di rete specifiche.

Gli oggetti NetworkRequest definiscono i parametri del callback di rete in termini di NetworkCapabilities. Hai creato oggetti NetworkRequest con la classe NetworkRequest.Builder. registerNetworkCallback() poi passa l'oggetto NetworkRequest al sistema. Quando le condizioni di rete sono soddisfatte, l'app riceve un callback per eseguire il metodo onAvailable() definito nella classe ConnectivityManager.NetworkCallback.

L'app continua a ricevere callback finché non esce o non chiama unregisterNetworkCallback().

Limitazioni alla ricezione di trasmissioni di immagini e video

In Android 7.0 (livello API 24), le app non possono inviare o ricevere annunci ACTION_NEW_PICTURE o ACTION_NEW_VIDEO. Questa limitazione contribuisce a ridurre l'impatto sul rendimento e sull'esperienza utente quando diverse app devono riattivarsi per elaborare una nuova immagine o un nuovo video. Android 7.0 (livello API 24) estende JobInfo e JobParameters per fornire una soluzione alternativa.

Attivare i job in base alle modifiche agli URI dei contenuti

Per attivare i job in base alle modifiche degli URI dei contenuti, Android 7.0 (livello API 24) estende l'API JobInfo con i seguenti metodi:

JobInfo.TriggerContentUri()
Esecuzione di un job in base alle modifiche degli URI dei contenuti.
JobInfo.Builder.addTriggerContentUri()
Passa un oggetto TriggerContentUri a JobInfo. Un ContentObserver monitora l'URI dei contenuti incapsulati. Se a un job sono associati più oggetti TriggerContentUri, il sistema fornisce un callback anche se segnala una modifica solo in uno degli URI dei contenuti.
Aggiungi il flag TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS per attivare il job in caso di modifica di eventuali discendenti dell'URI specificato. Questo flag corrisponde al parametro notifyForDescendants passato a registerContentObserver().

Nota: TriggerContentUri() non può essere utilizzato in combinazione con setPeriodic() o setPersisted(). Per monitorare continuamente le modifiche ai contenuti, pianifica un nuovo JobInfo prima che il JobService dell'app finisca di gestire il richiamata più recente.

Il seguente codice di esempio pianifica l'attivazione di un job quando il sistema segnala una modifica all'URI dei contenuti, MEDIA_URI:

Kotlin

const val MY_BACKGROUND_JOB = 0
...
fun scheduleJob(context: Context) {
    val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    val job = JobInfo.Builder(
            MY_BACKGROUND_JOB,
            ComponentName(context, MediaContentJob::class.java)
    )
            .addTriggerContentUri(
                    JobInfo.TriggerContentUri(
                            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                            JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS
                    )
            )
            .build()
    jobScheduler.schedule(job)
}

Java

public static final int MY_BACKGROUND_JOB = 0;
...
public static void scheduleJob(Context context) {
  JobScheduler js =
          (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
  JobInfo.Builder builder = new JobInfo.Builder(
          MY_BACKGROUND_JOB,
          new ComponentName(context, MediaContentJob.class));
  builder.addTriggerContentUri(
          new JobInfo.TriggerContentUri(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
          JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
  js.schedule(builder.build());
}

Quando il sistema segnala una modifica negli URI dei contenuti specificati, la tua app riceve un callback e un oggetto JobParameters viene passato al metodo onStartJob() in MediaContentJob.class.

Determinare quali autorità dei contenuti hanno attivato un job

Android 7.0 (livello API 24) estende inoltre JobParameters per consentire alla tua app di ricevere informazioni utili su quali autorità dei contenuti e URI hanno attivato il job:

Uri[] getTriggeredContentUris()
Restituisce un array di URI che hanno attivato il job. Il valore sarà null se nessun URI ha attivato il job (ad esempio, il job è stato attivato a causa di una scadenza o per qualche altro motivo) o se il numero di URI modificati è superiore a 50.
String[] getTriggeredContentAuthorities()
Restituisce un array di stringhe di autorità sui contenuti che hanno attivato il job. Se l'array restituito non è null, utilizza getTriggeredContentUris() per recuperare i dettagli degli URI che sono stati modificati.

Il seguente codice di esempio sostituisce il metodo JobService.onStartJob() e registra le autorità dei contenuti e gli URI che hanno attivato il job:

Kotlin

override fun onStartJob(params: JobParameters): Boolean {
    StringBuilder().apply {
        append("Media content has changed:\n")
        params.triggeredContentAuthorities?.also { authorities ->
            append("Authorities: ${authorities.joinToString(", ")}\n")
            append(params.triggeredContentUris?.joinToString("\n"))
        } ?: append("(No content)")
        Log.i(TAG, toString())
    }
    return true
}

Java

@Override
public boolean onStartJob(JobParameters params) {
  StringBuilder sb = new StringBuilder();
  sb.append("Media content has changed:\n");
  if (params.getTriggeredContentAuthorities() != null) {
      sb.append("Authorities: ");
      boolean first = true;
      for (String auth :
          params.getTriggeredContentAuthorities()) {
          if (first) {
              first = false;
          } else {
             sb.append(", ");
          }
           sb.append(auth);
      }
      if (params.getTriggeredContentUris() != null) {
          for (Uri uri : params.getTriggeredContentUris()) {
              sb.append("\n");
              sb.append(uri);
          }
      }
  } else {
      sb.append("(No content)");
  }
  Log.i(TAG, sb.toString());
  return true;
}

Ottimizza ulteriormente l'app

L'ottimizzazione delle app per l'esecuzione su dispositivi con poca memoria o in condizioni di scarsa memoria può migliorare le prestazioni e l'esperienza utente. La rimozione delle dipendenze sui servizi in background e sui broadcast receiver impliciti registrati con manifest può contribuire a migliorare il funzionamento dell'app su questi dispositivi. Anche se Android 7.0 (livello API 24) adotta misure per ridurre alcuni di questi problemi, ti consigliamo di ottimizzare l'app in modo che funzioni senza l'utilizzo di queste procedure in background.

I seguenti comandi Android Debug Bridge (ADB) possono aiutarti a testare il comportamento dell'app con le procedure in background disattivate:

  • Per simulare le condizioni in cui le trasmissioni implicite e i servizi in background non sono disponibili, inserisci il seguente comando:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND ignore
    
  • Per riattivare le trasmissioni implicite e i servizi in background, inserisci il seguente comando:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow
    
  • Puoi simulare l'utente che inserisce la tua app nello stato "Con restrizioni" per l'utilizzo della batteria in background. Questa impostazione impedisce all'app di essere eseguita in background. Per farlo, esegui il seguente comando in una finestra del terminale:
  • $ adb shell cmd appops set <PACKAGE_NAME> RUN_ANY_IN_BACKGROUND deny