Optymalizacja w tle

Procesy w tle mogą zużywać dużo pamięci i baterii. Na przykład niejawna transmisja może uruchomić wiele procesów w tle, które zarejestrowały się, aby jej nasłuchiwać, nawet jeśli te procesy nie wykonują zbyt wielu zadań. Może to mieć duży wpływ na wydajność urządzenia i wygodę użytkownika.

Aby rozwiązać ten problem, Android 7.0 (poziom interfejsu API 24) wprowadza te ograniczenia:

Jeśli Twoja aplikacja korzysta z któregoś z tych intencji, jak najszybciej usuń od nich zależności, aby móc prawidłowo kierować reklamy na urządzenia z Androidem 7.0 lub nowszym. Platforma Androida udostępnia kilka rozwiązań, które ograniczają potrzebę korzystania z tych niejawnych transmisji. Na przykład JobScheduler i nowy WorkManager zapewniają niezawodne mechanizmy planowania operacji sieciowych po spełnieniu określonych warunków, takich jak połączenie z siecią bez limitu danych. Możesz teraz też używać JobScheduler do reagowania na zmiany u dostawców treści. Obiekty JobInfo zawierają parametry, których JobScheduler używa do planowania zadania. Gdy warunki zadania zostaną spełnione, system wykona je w JobService aplikacji.

Na tej stronie dowiesz się, jak za pomocą alternatywnych metod, np. JobScheduler, dostosować aplikację do tych nowych ograniczeń.

Ograniczenia zainicjowane przez użytkownika

Na stronie Zużycie baterii w ustawieniach systemu użytkownik może wybrać jedną z tych opcji:

  • Nieograniczone:zezwalaj na wszystkie działania w tle, co może powodować większe zużycie baterii.
  • Zoptymalizowane (domyślne): optymalizuje możliwość wykonywania przez aplikację pracy w tle na podstawie sposobu, w jaki użytkownik z niej korzysta.
  • Ograniczone: całkowicie uniemożliwia działanie aplikacji w tle. Aplikacje mogą nie działać zgodnie z oczekiwaniami.

Jeśli aplikacja wykazuje niektóre z niepożądanych zachowań opisanych w Android Vitals, system może poprosić użytkownika o ograniczenie dostępu tej aplikacji do zasobów systemowych.

Jeśli system zauważy, że aplikacja zużywa zbyt dużo zasobów, powiadomi o tym użytkownika i da mu możliwość ograniczenia działań aplikacji. Zachowania, które mogą spowodować wyświetlenie powiadomienia, to:

  • Zbyt wiele blokad uśpienia: 1 częściowa blokada uśpienia utrzymywana przez godzinę, gdy ekran jest wyłączony
  • Nadmierna liczba usług działających w tle: jeśli aplikacja jest kierowana na poziomy interfejsu API niższe niż 26 i ma nadmierną liczbę usług działających w tle

Dokładne ograniczenia są określane przez producenta urządzenia. Na przykład w przypadku kompilacji AOSP z Androidem 9 (API na poziomie 28) lub nowszym aplikacje działające w tle, które są w stanie „ograniczonym”, mają następujące ograniczenia:

  • Nie można uruchomić usług działających na pierwszym planie
  • Istniejące usługi działające na pierwszym planie są usuwane z pierwszego planu.
  • Alarmy nie są włączane
  • Zadania nie są wykonywane

Jeśli aplikacja jest kierowana na Androida 13 (API na poziomie 33) lub nowszego i jest w stanie „ograniczonym”, system nie dostarcza transmisji BOOT_COMPLETED ani transmisji LOCKED_BOOT_COMPLETED, dopóki aplikacja nie zostanie uruchomiona z innych powodów.

Konkretne ograniczenia są wymienione w sekcji Ograniczenia dotyczące zarządzania energią.

Ograniczenia dotyczące odbierania transmisji aktywności sieciowej

Aplikacje kierowane na Androida 7.0 (poziom interfejsu API 24) nie otrzymują transmisji CONNECTIVITY_ACTION, jeśli zarejestrują się w celu ich otrzymywania w pliku manifestu, a procesy, które zależą od tej transmisji, nie zostaną uruchomione. Może to stanowić problem dla aplikacji, które chcą nasłuchiwać zmian w sieci lub wykonywać zbiorcze działania sieciowe, gdy urządzenie połączy się z siecią bez limitu danych. W platformie Android istnieje już kilka rozwiązań, które pozwalają obejść to ograniczenie, ale wybór odpowiedniego zależy od tego, co chcesz osiągnąć w swojej aplikacji.

Uwaga: BroadcastReceiver zarejestrowany w  Context.registerReceiver() nadal otrzymuje te transmisje, gdy aplikacja jest uruchomiona.

Planowanie zadań sieciowych w przypadku połączeń bez limitu danych

Podczas tworzenia obiektu JobInfo za pomocą klasy JobInfo.Builder zastosuj metodę setRequiredNetworkType() i przekaż JobInfo.NETWORK_TYPE_UNMETERED jako parametr zadania. Poniższy przykładowy kod planuje uruchomienie usługi, gdy urządzenie połączy się z siecią bez limitu danych i będzie się ładować:

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

Gdy warunki zadania zostaną spełnione, aplikacja otrzyma wywołanie zwrotne, aby uruchomić metodę onStartJob() w określonym JobService.class. Więcej przykładów implementacji JobScheduler znajdziesz w przykładowej aplikacji JobScheduler.

Nową alternatywą dla JobScheduler jest WorkManager, interfejs API, który umożliwia planowanie zadań w tle, które muszą zostać wykonane niezależnie od tego, czy proces aplikacji jest aktywny. WorkManager wybiera odpowiedni sposób uruchomienia zadania (bezpośrednio w wątku w procesie aplikacji, a także za pomocą JobScheduler, FirebaseJobDispatcher lub AlarmManager) na podstawie takich czynników jak poziom interfejsu API urządzenia. WorkManager nie wymaga też usług Play i zapewnia kilka zaawansowanych funkcji, takich jak łączenie zadań w łańcuchy czy sprawdzanie stanu zadania. Więcej informacji znajdziesz w artykule WorkManager.

Monitorowanie połączenia sieciowego podczas działania aplikacji

Aplikacje, które są uruchomione, nadal mogą nasłuchiwać CONNECTIVITY_CHANGE za pomocą zarejestrowanego BroadcastReceiver. Interfejs ConnectivityManager API zapewnia jednak bardziej niezawodną metodę żądania wywołania zwrotnego tylko wtedy, gdy spełnione są określone warunki sieciowe.

Obiekty NetworkRequest określają parametry wywołania zwrotnego sieci w terminach NetworkCapabilities. Obiekty NetworkRequest tworzysz za pomocą klasy NetworkRequest.Builder. registerNetworkCallback() przekazuje następnie obiekt NetworkRequest do systemu. Gdy warunki sieciowe zostaną spełnione, aplikacja otrzyma wywołanie zwrotne, aby wykonać metodę onAvailable() zdefiniowaną w klasie ConnectivityManager.NetworkCallback.

Aplikacja nadal będzie otrzymywać wywołania zwrotne, dopóki nie zostanie zamknięta lub nie wywoła funkcji unregisterNetworkCallback().

Ograniczenia dotyczące odbierania transmisji obrazów i filmów

W Androidzie 7.0 (interfejs API na poziomie 24) aplikacje nie mogą wysyłać ani odbierać transmisji ACTION_NEW_PICTURE ani ACTION_NEW_VIDEO. To ograniczenie pomaga zmniejszyć wpływ na wydajność i komfort użytkownika, gdy kilka aplikacji musi się uaktywnić, aby przetworzyć nowy obraz lub film. Android 7.0 (poziom interfejsu API 24) rozszerza JobInfoJobParameters, aby zapewnić alternatywne rozwiązanie.

Aktywowanie zadań w przypadku zmian identyfikatora URI treści

Aby wywoływać zadania przy zmianach identyfikatora URI treści, Android 7.0 (poziom interfejsu API 24) rozszerza interfejs JobInfo o te metody:

JobInfo.TriggerContentUri()
Zawiera parametry wymagane do wywołania zadania w przypadku zmian identyfikatora URI treści.
JobInfo.Builder.addTriggerContentUri()
Przekazuje obiekt TriggerContentUri do JobInfo. ContentObserver monitoruje identyfikator URI zamkniętych treści. Jeśli z danym zadaniem jest powiązanych kilka obiektów TriggerContentUri, system wywołuje funkcję zwrotną nawet wtedy, gdy zgłasza zmianę tylko w jednym z identyfikatorów URI treści.
Dodaj flagę TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS, aby uruchomić zadanie, jeśli zmieni się którykolwiek z potomków danego identyfikatora URI. Ten flag odpowiada parametrowi notifyForDescendants przekazywanemu do registerContentObserver().

Uwaga: parametru TriggerContentUri() nie można używać w połączeniu z parametrami setPeriodic() ani setPersisted(). Aby stale monitorować zmiany treści, zaplanuj nowe JobInfo, zanim JobService aplikacji zakończy obsługę ostatniego wywołania zwrotnego.

Poniższy przykładowy kod planuje zadanie, które ma być wywoływane, gdy system zgłosi zmianę identyfikatora URI treści, 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());
}

Gdy system zgłosi zmianę w określonych identyfikatorach URI treści, aplikacja otrzyma wywołanie zwrotne, a do metody onStartJob()MediaContentJob.class zostanie przekazany obiekt JobParameters.

Określanie, które instytucje zarządzające treściami wywołały zadanie

Android 7.0 (poziom interfejsu API 24) rozszerza też JobParameters, aby umożliwić aplikacji otrzymywanie przydatnych informacji o tym, które organy treści i adresy URI wywołały zadanie:

Uri[] getTriggeredContentUris()
Zwraca tablicę adresów URI, które wywołały zadanie. Wartość ta będzie wynosić null, jeśli żadne identyfikatory URI nie spowodowały uruchomienia zadania (np. zadanie zostało uruchomione z powodu terminu lub z innego powodu) albo liczba zmienionych identyfikatorów URI jest większa niż 50.
String[] getTriggeredContentAuthorities()
Zwraca tablicę ciągów zawierającą podmioty uprawnione do treści, które wywołały zadanie. Jeśli zwrócona tablica nie jest pusta (null), użyj getTriggeredContentUris(), aby pobrać szczegóły dotyczące tego, które adresy URI uległy zmianie.

Poniższy przykładowy kod zastępuje metodę JobService.onStartJob() i rejestruje organy treści oraz identyfikatory URI, które wywołały zadanie:

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

Dalsza optymalizacja aplikacji

Zoptymalizowanie aplikacji pod kątem działania na urządzeniach z małą ilością pamięci lub w warunkach małej ilości pamięci może poprawić wydajność i wygodę użytkowników. Usunięcie zależności od usług działających w tle i niejawnych odbiorników transmisji zarejestrowanych w manifeście może poprawić działanie aplikacji na takich urządzeniach. Android 7.0 (poziom interfejsu API 24) podejmuje działania mające na celu ograniczenie niektórych z tych problemów, ale zalecamy zoptymalizowanie aplikacji tak, aby działała bez użycia tych procesów w tle.

Te polecenia Android Debug Bridge (ADB) pomogą Ci przetestować działanie aplikacji przy wyłączonych procesach w tle:

  • Aby zasymulować warunki, w których nie są dostępne niejawne transmisje i usługi działające w tle, wpisz to polecenie:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND ignore
    
  • Aby ponownie włączyć niejawne transmisje i usługi działające w tle, wpisz to polecenie:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow
    
  • Możesz symulować umieszczenie aplikacji w stanie „ograniczonym” w przypadku zużycia baterii w tle. To ustawienie uniemożliwia działanie aplikacji w tle. Aby to zrobić, uruchom w oknie terminala to polecenie:
  • $ adb shell cmd appops set <PACKAGE_NAME> RUN_ANY_IN_BACKGROUND deny