Hintergrundoptimierung

Hintergrundprozesse können viel Arbeitsspeicher und Akkuleistung verbrauchen. Beispielsweise kann ein implizierter Broadcast viele Hintergrundprozesse starten, die sich für den Empfang registriert haben, auch wenn diese Prozesse möglicherweise nicht viel Arbeit erledigen. Dies kann erhebliche Auswirkungen auf die Geräteleistung und die Nutzerfreundlichkeit haben.

Um dieses Problem zu beheben, gelten unter Android 7.0 (API-Level 24) die folgenden Einschränkungen:

Wenn deine App einen dieser Intents verwendet, solltest du die Abhängigkeiten so schnell wie möglich entfernen, damit du Geräte mit Android 7.0 oder höher richtig ansprechen kannst. Das Android-Framework bietet mehrere Lösungen, um die Notwendigkeit dieser impliziten Broadcasts zu verringern. Beispielsweise bieten JobScheduler und der neue WorkManager robuste Mechanismen, um Netzwerkvorgänge zu planen, wenn bestimmte Bedingungen erfüllt sind, z. B. eine Verbindung zu einem Netzwerk ohne Datenlimit. Sie können jetzt auch JobScheduler verwenden, um auf Änderungen bei Contentanbietern zu reagieren. JobInfo-Objekte kapseln die Parameter, die JobScheduler zum Planen des Jobs verwendet. Wenn die Bedingungen des Jobs erfüllt sind, führt das System diesen Job für die JobService Ihrer App aus.

Auf dieser Seite erfahren Sie, wie Sie alternative Methoden wie JobScheduler verwenden, um Ihre App an diese neuen Einschränkungen anzupassen.

Vom Nutzer initiierte Einschränkungen

Auf der Seite Akkunutzung in den Systemeinstellungen kann der Nutzer aus den folgenden Optionen auswählen:

  • Uneingeschränkt:Alle Hintergrundaktivitäten sind zulässig, was zu einem höheren Akkuverbrauch führen kann.
  • Optimiert (Standardeinstellung): Die Fähigkeit einer App, Hintergrundaufgaben auszuführen, wird basierend auf der Interaktion des Nutzers mit der App optimiert.
  • Eingeschränkt:Verhindert vollständig, dass eine App im Hintergrund ausgeführt wird. Apps funktionieren möglicherweise nicht wie erwartet.

Wenn eine App einige der unter Android Vitals beschriebenen unerwünschten Verhaltensweisen aufweist, fordert das System den Nutzer möglicherweise auf, den Zugriff dieser App auf Systemressourcen einzuschränken.

Wenn das System feststellt, dass eine App übermäßig viele Ressourcen verbraucht, wird der Nutzer benachrichtigt und erhält die Möglichkeit, die Aktionen der App einzuschränken. Zu den Verhaltensweisen, die den Hinweis auslösen können, gehören:

  • Übermäßige Wakelocks: 1 Teil-Wakelock, der eine Stunde lang gehalten wird, wenn das Display ausgeschaltet ist
  • Übermäßige Hintergrunddienste: Wenn die App auf API-Level unter 26 ausgerichtet ist und übermäßige Hintergrunddienste hat

Die genauen Einschränkungen werden vom Gerätehersteller festgelegt. Bei AOSP-Builds mit Android 9 (API-Level 28) oder höher gelten für im Hintergrund ausgeführte Apps im Status „Eingeschränkt“ beispielsweise die folgenden Einschränkungen:

  • Dienste im Vordergrund können nicht gestartet werden
  • Vorhandene Dienste im Vordergrund werden aus dem Vordergrund entfernt.
  • Wecker werden nicht ausgelöst
  • Jobs werden nicht ausgeführt

Wenn eine App auf Android 13 (API-Level 33) oder höher ausgerichtet ist und sich im Status „Eingeschränkt“ befindet, liefert das System außerdem die Übertragung BOOT_COMPLETED oder LOCKED_BOOT_COMPLETED erst, wenn die App aus anderen Gründen gestartet wird.

Die spezifischen Einschränkungen sind unter Einschränkungen für die Energieverwaltung aufgeführt.

Einschränkungen für den Empfang von Broadcasts zu Netzwerkaktivitäten

Apps, die auf Android 7.0 (API-Level 24) ausgerichtet sind, erhalten keine CONNECTIVITY_ACTION-Broadcasts, wenn sie sich in ihrem Manifest für den Empfang registrieren. Prozesse, die von diesem Broadcast abhängen, werden nicht gestartet. Dies könnte ein Problem für Apps darstellen, die auf Netzwerkänderungen warten oder Bulk-Netzwerkaktivitäten ausführen möchten, wenn das Gerät eine Verbindung zu einem Netzwerk ohne Datenlimit herstellt. Im Android-Framework gibt es bereits mehrere Lösungen, um diese Einschränkung zu umgehen. Welche die richtige ist, hängt davon ab, was deine App erreichen soll.

Hinweis:Ein mit Context.registerReceiver() registrierter BroadcastReceiver empfängt diese Broadcasts weiterhin, während die App ausgeführt wird.

Netzwerkjobs für Verbindungen ohne Datenlimit planen

Wenn Sie die Klasse JobInfo.Builder verwenden, um das Objekt JobInfo zu erstellen, wenden Sie die Methode setRequiredNetworkType() an und übergeben Sie JobInfo.NETWORK_TYPE_UNMETERED als Jobparameter. Im folgenden Codebeispiel wird ein Dienst so geplant, dass er ausgeführt wird, wenn das Gerät mit einem Netzwerk ohne Datenlimit verbunden ist und geladen wird:

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);
}

Wenn die Bedingungen für den Job erfüllt sind, erhält Ihre App einen Callback, um die Methode onStartJob() im angegebenen JobService.class auszuführen. Weitere Beispiele für die Implementierung von JobScheduler finden Sie in der JobScheduler-Beispiel-App.

Eine neue Alternative zu JobScheduler ist WorkManager, eine API, mit der Sie Hintergrundaufgaben planen können, die garantiert abgeschlossen werden müssen, unabhängig davon, ob der App-Prozess vorhanden ist oder nicht. WorkManager wählt die geeignete Methode zum Ausführen der Arbeit aus (entweder direkt in einem Thread im App-Prozess oder mithilfe von JobScheduler, FirebaseJobDispatcher oder AlarmManager). Dabei werden Faktoren wie das API-Level des Geräts berücksichtigt. Außerdem sind für WorkManager keine Google Play-Dienste erforderlich und es bietet mehrere erweiterte Funktionen, z. B. das Verketten von Aufgaben oder das Prüfen des Status einer Aufgabe. Weitere Informationen finden Sie unter WorkManager.

Netzwerkverbindung überwachen, während die App ausgeführt wird

Ausgeführte Apps können weiterhin mit einem registrierten BroadcastReceiver auf CONNECTIVITY_CHANGE warten. Die ConnectivityManager API bietet jedoch eine robustere Methode, um einen Callback nur dann anzufordern, wenn bestimmte Netzwerkbedingungen erfüllt sind.

NetworkRequest-Objekte definieren die Parameter des Netzwerk-Callbacks in Form von NetworkCapabilities. Sie erstellen NetworkRequest-Objekte mit der Klasse NetworkRequest.Builder. registerNetworkCallback() übergibt dann das NetworkRequest-Objekt an das System. Wenn die Netzwerkbedingungen erfüllt sind, erhält die App einen Callback, um die in ihrer ConnectivityManager.NetworkCallback-Klasse definierte onAvailable()-Methode auszuführen.

Die App empfängt weiterhin Callbacks, bis sie entweder beendet wird oder unregisterNetworkCallback() aufruft.

Einschränkungen beim Empfang von Bild- und Videonachrichten

In Android 7.0 (API-Level 24) können Apps keine ACTION_NEW_PICTURE- oder ACTION_NEW_VIDEO-Broadcasts senden oder empfangen. Diese Einschränkung trägt dazu bei, die Auswirkungen auf die Leistung und die Nutzerfreundlichkeit zu verringern, wenn mehrere Apps aktiviert werden müssen, um ein neues Bild oder Video zu verarbeiten. Android 7.0 (API-Level 24) erweitert JobInfo und JobParameters, um eine alternative Lösung bereitzustellen.

Jobs bei Änderungen des Inhalts-URI auslösen

Um Jobs bei Änderungen des Inhalts-URI auszulösen, wird die JobInfo API in Android 7.0 (API-Level 24) um die folgenden Methoden erweitert:

JobInfo.TriggerContentUri()
Kapselt Parameter, die zum Auslösen eines Jobs bei Änderungen des Inhalts-URI erforderlich sind.
JobInfo.Builder.addTriggerContentUri()
Übergibt ein TriggerContentUri-Objekt an JobInfo. Ein ContentObserver überwacht den gekapselten Inhalts-URI. Wenn einem Job mehrere TriggerContentUri-Objekte zugeordnet sind, stellt das System einen Callback bereit, auch wenn nur in einem der Inhalts-URIs eine Änderung gemeldet wird.
Fügen Sie das Flag TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS hinzu, um den Job auszulösen, wenn sich untergeordnete Elemente des angegebenen URI ändern. Dieses Flag entspricht dem Parameter notifyForDescendants, der an registerContentObserver() übergeben wird.

Hinweis: TriggerContentUri() kann nicht in Kombination mit setPeriodic() oder setPersisted() verwendet werden. Wenn du die Inhaltsänderungen kontinuierlich überwachen möchtest, plane einen neuen JobInfo, bevor der JobService der App den letzten Callback verarbeitet hat.

Im folgenden Beispielcode wird ein Job geplant, der ausgelöst wird, wenn das System eine Änderung am Inhalts-URI MEDIA_URI meldet:

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());
}

Wenn das System eine Änderung an den angegebenen Inhalts-URIs meldet, erhält Ihre App einen Callback und ein JobParameters-Objekt wird an die Methode onStartJob() in MediaContentJob.class übergeben.

Ermitteln, welche Inhaltsautoritäten einen Job ausgelöst haben

Android 7.0 (API-Level 24) erweitert JobParameters, damit deine App nützliche Informationen darüber erhalten kann, welche Content-Anbieter und URIs den Job ausgelöst haben:

Uri[] getTriggeredContentUris()
Gibt ein Array von URIs zurück, die den Job ausgelöst haben. Dieser Wert ist null, wenn entweder keine URIs den Job ausgelöst haben (z. B. weil der Job aufgrund einer Frist oder aus einem anderen Grund ausgelöst wurde) oder die Anzahl der geänderten URIs größer als 50 ist.
String[] getTriggeredContentAuthorities()
Gibt ein String-Array von Inhaltsautoritäten zurück, die den Job ausgelöst haben. Wenn das zurückgegebene Array nicht null ist, verwenden Sie getTriggeredContentUris(), um die Details der geänderten URIs abzurufen.

Im folgenden Beispielcode wird die Methode JobService.onStartJob() überschrieben und die Content-Authorities und URIs, die den Job ausgelöst haben, werden aufgezeichnet:

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;
}

App weiter optimieren

Wenn du deine Apps für Geräte mit wenig Arbeitsspeicher oder für Situationen mit wenig Arbeitsspeicher optimierst, kannst du die Leistung und die Nutzerfreundlichkeit verbessern. Wenn du Abhängigkeiten von Hintergrunddiensten und im Manifest registrierten impliziten Übertragungsempfängern entfernst, kann deine App auf solchen Geräten besser ausgeführt werden. Obwohl Android 7.0 (API-Level 24) einige dieser Probleme reduziert, empfehlen wir, deine App so zu optimieren, dass sie ohne diese Hintergrundprozesse ausgeführt werden kann.

Mit den folgenden Android Debug Bridge-Befehlen (ADB) können Sie das Verhalten der App bei deaktivierten Hintergrundprozessen testen:

  • Geben Sie den folgenden Befehl ein, um Bedingungen zu simulieren, unter denen implizite Broadcasts und Hintergrunddienste nicht verfügbar sind:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND ignore
    
  • Geben Sie den folgenden Befehl ein, um implizite Broadcasts und Hintergrunddienste wieder zu aktivieren:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow
    
  • Du kannst simulieren, dass der Nutzer deine App für die Akkunutzung im Hintergrund in den Status „Eingeschränkt“ versetzt. Diese Einstellung verhindert, dass Ihre App im Hintergrund ausgeführt werden kann. Führen Sie dazu den folgenden Befehl in einem Terminalfenster aus:
  • $ adb shell cmd appops set <PACKAGE_NAME> RUN_ANY_IN_BACKGROUND deny