Optymalizacja w tle

Procesy w tle mogą zużywać dużo pamięci i baterii. Na przykład komunikat ogólny może uruchomić wiele procesów w tle, które zostały zarejestrowane do jego nasłuchiwania, nawet jeśli te procesy nie wykonują zbyt wielu zadań. Może to mieć znaczący wpływ zarówno na wydajność urządzenia, jak i wrażenia użytkownika.

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

  • Aplikacje kierowane na Androida 7.0 (poziom interfejsu API 24) lub nowszego nie otrzymują komunikatów CONNECTIVITY_ACTION, jeśli odbiornik komunikatów jest zadeklarowany w pliku manifestu. Aplikacje będą nadal otrzymywać komunikaty CONNECTIVITY_ACTION, jeśli zarejestrują swoje BroadcastReceiver za pomocą Context.registerReceiver() i ten kontekst będzie nadal ważny.
  • Aplikacje nie mogą wysyłać ani odbierać transmisji ACTION_NEW_PICTURE ani ACTION_NEW_VIDEO. Ta optymalizacja dotyczy wszystkich aplikacji, nie tylko tych kierowanych na Androida 7.0 (poziom interfejsu API 24).

Jeśli Twoja aplikacja używa którejkolwiek z tych intencji, jak najszybciej usuń zależności od nich, aby móc prawidłowo kierować reklamy na urządzenia z Androidem 7.0 lub nowszym. Framework Androida udostępnia kilka rozwiązań, które pozwalają ograniczyć potrzebę stosowania tych komunikatów ogólnych. 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 pomiaru. Możesz teraz używać JobScheduler, aby reagować na zmiany w mechanizmach dostarczania 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 używać metod alternatywnych, takich jak JobScheduler, aby dostosować aplikację do nowych ograniczeń.

Ograniczenia inicjowane przez użytkownika

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

  • Bez ograniczeń:zezwala na wszystkie działania w tle, co może zwiększyć zużycie baterii.
  • Zoptymalizowane (domyślnie): optymalizuje możliwość wykonywania przez aplikację zadań 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ć poprawnie.

Jeśli aplikacja wykazuje niektóre z niewłaściwych działań 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 nadmierne zasoby, powiadomi o tym użytkownika i da mu możliwość ograniczenia jej działań. Zachowania, które mogą spowodować wyświetlenie powiadomienia, to m.in.:

  • Zbyt wiele blokad uśpienia: 1 częściowa blokada uśpienia utrzymywana przez godzinę, gdy ekran jest wyłączony.
  • Nadmierne korzystanie z usług w tle: jeśli aplikacja jest kierowana na interfejsy API na poziomie niższym niż 26 i nadmiernie korzysta z usług w tle.

Dokładne ograniczenia są określane przez producenta urządzenia. Na przykład w kompilacjach AOSP z Androidem 9 (API na poziomie 28) lub nowszym aplikacje działające w tle, które są w stanie „ograniczonym”, mają te 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ą aktywowane.
  • 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 LOCKED_BOOT_COMPLETED, dopóki aplikacja nie zostanie uruchomiona z innych powodów.

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

Ograniczenia dotyczące odbierania transmisji aktywności sieciowej

Aplikacje kierowane na Androida 7.0 (poziom interfejsu API 24) nie otrzymują komunikatów CONNECTIVITY_ACTION, jeśli zarejestrują się, aby je otrzymywać w manifeście, a procesy, które zależą od tego komunikatu, nie zostaną uruchomione. Może to stanowić problem w przypadku aplikacji, które chcą nasłuchiwać zmian w sieci lub wykonywać zbiorcze działania sieciowe, gdy urządzenie łączy się z siecią bez taryfowego rozliczania danych. W platformie Androida istnieje już kilka rozwiązań, które pozwalają obejść to ograniczenie, ale wybór odpowiedniego zależy od tego, co chcesz osiągnąć za pomocą aplikacji.

Uwaga: BroadcastReceiver zarejestrowany za pomocą Context.registerReceiver() nadal odbiera te komunikaty, gdy aplikacja jest uruchomiona.

Planowanie zadań sieciowych w połączeniach bez pomiaru

Gdy używasz klasy JobInfo.Builder do utworzenia obiektu JobInfo, 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 taryfowego rozliczania 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ć ukończone, niezależnie od tego, czy proces aplikacji jest aktywny. WorkManager wybiera odpowiedni sposób wykonywania zadania (bezpośrednio w wątku w procesie aplikacji lub za pomocą JobScheduler, FirebaseJobDispatcher lub AlarmManager) na podstawie takich czynników jak poziom interfejsu API urządzenia. Dodatkowo WorkManager nie wymaga Usług Google Play i udostępnia kilka funkcji zaawansowanych, 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;

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

Obiekty NetworkRequest definiują parametry wywołania zwrotnego sieci w odniesieniu do NetworkCapabilities. Tworzysz obiekty NetworkRequest z klasą NetworkRequest.Builder. registerNetworkCallback(), a następnie przekazuje obiekt NetworkRequest do systemu. Gdy warunki sieciowe są spełnione, aplikacja otrzymuje wywołanie zwrotne, aby wykonać metodę onAvailable() zdefiniowaną w klasie ConnectivityManager.NetworkCallback.

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

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

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

Uruchamianie zadań po zmianie identyfikatora URI treści

Aby umożliwić uruchamianie zadań po zmianie identyfikatora URI treści, Android 7.0 (poziom interfejsu API 24) rozszerza interfejs API JobInfo o te metody:

JobInfo.TriggerContentUri()
Zawiera parametry wymagane do aktywowania zadania w przypadku zmian identyfikatora URI treści.
JobInfo.Builder.addTriggerContentUri()
Przekazuje obiekt TriggerContentUri do JobInfo. ContentObserver monitoruje identyfikator URI treści w kapsułce. Jeśli z zadaniem jest powiązanych kilka obiektów TriggerContentUri, system wywołuje funkcję zwrotną, nawet jeśli 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ę jakiekolwiek elementy podrzędne danego identyfikatora URI. Ta flaga odpowiada parametrowi notifyForDescendants przekazanemu do registerContentObserver().

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

Poniższy przykładowy kod planuje zadanie, które ma zostać aktywowane, 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 obiekt JobParameters zostanie przekazany do metody onStartJob() w MediaContentJob.class.

Określanie, które urzędy ds. treści wywołały zadanie

Android 7.0 (poziom interfejsu API 24) rozszerza też JobParameters, aby umożliwić aplikacji otrzymywanie przydatnych informacji o tym, jakie urzędy i identyfikatory URI wywołały zadanie:

Uri[] getTriggeredContentUris()
Zwraca tablicę identyfikatorów URI, które wywołały zadanie. Wartość będzie wynosić null, jeśli żadne identyfikatory URI nie wywołały zadania (np. zadanie zostało wywołane z powodu terminu lub z innego powodu) lub liczba zmienionych identyfikatorów URI jest większa niż 50.
String[] getTriggeredContentAuthorities()
Zwraca tablicę tekstową dostawców treści, którzy wywołali zadanie. Jeśli zwrócona tablica nie jest null, użyj getTriggeredContentUris(), aby pobrać szczegółowe informacje o tym, które identyfikatory URI uległy zmianie.

Poniższy przykładowy kod zastępuje metodę JobService.onStartJob() i rejestruje urzędy ds. 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

Optymalizacja aplikacji pod kątem działania na urządzeniach z małą ilością pamięci lub w warunkach ograniczonej pamięci może poprawić wydajność i wygodę użytkowników. Usunięcie zależności od usług w tle i zarejestrowanych w manifeście odbiorników komunikatów ogólnych może poprawić działanie aplikacji na takich urządzeniach. Chociaż Android 7.0 (poziom interfejsu API 24) podejmuje kroki w celu ograniczenia niektórych z tych problemów, zalecamy zoptymalizowanie aplikacji pod kątem działania bez użycia tych procesów w tle.

Te polecenia Android Debug Bridge (ADB) mogą pomóc w testowaniu działania aplikacji z wyłączonymi procesami w tle:

  • Aby zasymulować warunki, w których komunikaty ogólne i usługi w tle są niedostępne, wpisz to polecenie:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND ignore
    
  • Aby ponownie włączyć komunikaty ogólne i usługi w tle, wpisz to polecenie:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow
    
  • Możesz zasymulować sytuację, w której użytkownik ustawia aplikację w stanie „ograniczonym” pod kątem wykorzystania 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