Ottimizzazione in background

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

Per risolvere il 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 loro interno il prima possibile, in modo da poter scegliere correttamente come target i dispositivi con Android 7.0 o versioni successive. Il framework Android offre diverse soluzioni per ridurre la necessità di queste trasmissioni implicite. Ad esempio, JobScheduler e il nuovo WorkManager forniscono meccanismi affidabili per pianificare le operazioni di rete quando vengono soddisfatte condizioni specifiche, come una connessione a una rete unmetered. Ora puoi anche utilizzare JobScheduler per reagire alle modifiche apportate ai fornitori di contenuti. JobInfo incapsula i parametri utilizzati da JobScheduler per pianificare il job. Quando le condizioni del job sono soddisfatte, il sistema esegue questo job sull'JobService dell'app.

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

Limitazioni avviate dall'utente

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

  • Senza restrizioni:consenti tutte le operazioni in background, il che potrebbe consumare più batteria.
  • Ottimizzata (impostazione predefinita): consente di ottimizzare la capacità di un'app di eseguire operazioni in background, in base a come l'utente interagisce con l'app.
  • Con restrizioni:impedisce completamente l'esecuzione in background di un'app. Le app potrebbero non funzionare come previsto.

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

Se il sistema rileva un utilizzo eccessivo di risorse, avvisa l'utente e offre 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 presenta un numero eccessivo di servizi in background

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

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

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

Le limitazioni specifiche sono elencate in Restrizioni alla gestione dell'alimentazione.

Limitazioni relative alla ricezione di trasmissioni delle attività di rete

Le app che hanno come target Android 7.0 (livello API 24) non ricevono broadcast CONNECTIVITY_ACTION se si registrano per riceverli nel loro file manifest e i processi che dipendono da questa trasmissione non verranno avviati. Questo potrebbe rappresentare un problema per le app che vogliono rimanere in ascolto delle modifiche alla rete o eseguire attività di rete collettive quando il dispositivo si connette a una rete unmetered. Nel framework Android esistono già diverse soluzioni per aggirare questa limitazione, ma la scelta quella giusta dipende dall'obiettivo dell'app.

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

Pianifica job di rete su connessioni senza limiti

Quando utilizzi la classe JobInfo.Builder per creare il tuo oggetto JobInfo, applica il metodo setRequiredNetworkType() e trasmetti JobInfo.NETWORK_TYPE_UNMETERED come parametro job. Il seguente esempio di codice programma l'esecuzione di un servizio quando il dispositivo si connette a una rete illimitata 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 del job sono soddisfatte, la tua app riceve un callback per eseguire il metodo onStartJob() nell'elemento JobService.class specificato. Per visualizzare altri esempi dell'implementazione di JobScheduler, consulta l'app di esempio JobScheduler.

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

Monitorare la connettività di rete mentre l'app è in esecuzione

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

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

L'app continua a ricevere callback finché non viene chiusa o chiamata unregisterNetworkCallback().

Limitazioni relative alla ricezione di trasmissioni di immagini e video

In Android 7.0 (livello API 24), le app non possono inviare o ricevere trasmissioni ACTION_NEW_PICTURE o ACTION_NEW_VIDEO. Questa limitazione contribuisce a ridurre l'impatto sulle prestazioni e sull'esperienza utente quando è necessario attivare più app per elaborare una nuova immagine o un nuovo video. Android 7.0 (livello API 24) estende JobInfo e JobParameters per fornire una soluzione alternativa.

Attiva job in caso di modifiche all'URI dei contenuti

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

JobInfo.TriggerContentUri()
Incapsula i parametri necessari per attivare un job in caso di modifiche all'URI del contenuto.
JobInfo.Builder.addTriggerContentUri()
Passa un oggetto TriggerContentUri a JobInfo. Un elemento ContentObserver monitora l'URI del contenuto incapsulato. Se più oggetti TriggerContentUri sono associati a un job, 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 cambiamenti dei 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 JobService dell'app finisca di gestire il callback più recente.

Il seguente codice di esempio pianifica un job da attivare quando il sistema segnala una modifica all'URI del contenuto, 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, l'app riceve un callback e un oggetto JobParameters viene trasmesso al metodo onStartJob() in MediaContentJob.class.

Stabilire quali autorità per i contenuti hanno attivato un job

Android 7.0 (livello API 24) estende anche JobParameters per consentire alla tua app di ricevere informazioni utili sulle autorità dei contenuti e sugli URI che 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 altri motivi) oppure se il numero di URI modificati è maggiore di 50.
String[] getTriggeredContentAuthorities()
Restituisce un array di stringhe di autorità dei contenuti che hanno attivato il job. Se l'array restituito non è null, utilizza getTriggeredContentUris() per recuperare i dettagli di quali URI sono stati modificati.

Il seguente codice campione esegue l'override del metodo JobService.onStartJob() e registra gli URI e le autorità di contenuto 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 la tua app

Se ottimizzi le tue app per eseguirle su dispositivi con memoria ridotta o in condizioni di memoria insufficiente, puoi migliorare le prestazioni e l'esperienza utente. La rimozione delle dipendenze da servizi in background e ricevitori di trasmissione impliciti registrati con manifest può consentire alla tua app di funzionare meglio su questi dispositivi. Anche se Android 7.0 (livello API 24) prende provvedimenti per ridurre alcuni di questi problemi, ti consigliamo di ottimizzare l'app in modo che venga eseguita senza utilizzare questi processi in background.

I seguenti comandi Android Debug Bridge (ADB) possono aiutarti a testare il comportamento dell'app con i processi in background disattivati:

  • 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 riabilitare 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 "limitato" per l'utilizzo della batteria in background. Questa impostazione impedisce che l'app venga eseguita in background. Per farlo, esegui questo comando in una finestra del terminale:
  • $ adb shell cmd appops set <PACKAGE_NAME> RUN_ANY_IN_BACKGROUND deny