Фоновая оптимизация

Фоновые процессы могут потреблять много памяти и заряда батареи. Например, неявная широковещательная рассылка может запустить множество фоновых процессов, зарегистрированных для её прослушивания, даже если эти процессы не выполняют большой работы. Это может существенно повлиять как на производительность устройства, так и на удобство использования.

Чтобы решить эту проблему, Android 7.0 (уровень API 24) применяет следующие ограничения:

  • Приложения для Android 7.0 (уровень API 24) и выше не получают широковещательные сообщения CONNECTIVITY_ACTION , если они объявили свой приёмник в манифесте. Приложения будут по-прежнему получать широковещательные сообщения CONNECTIVITY_ACTION , если зарегистрируют свой BroadcastReceiver с помощью Context.registerReceiver() , и этот контекст всё ещё действителен.
  • Приложения не могут отправлять и принимать трансляции ACTION_NEW_PICTURE и ACTION_NEW_VIDEO . Эта оптимизация затрагивает все приложения, а не только те, которые предназначены для Android 7.0 (API уровня 24).

Если ваше приложение использует какие-либо из этих намерений, следует как можно скорее удалить зависимости от них, чтобы иметь возможность корректно ориентироваться на устройства под управлением Android 7.0 или более поздних версий. Фреймворк Android предоставляет несколько решений для снижения необходимости в этих неявных широковещательных рассылках. Например, JobScheduler и новый WorkManager предоставляют надежные механизмы планирования сетевых операций при выполнении заданных условий, таких как подключение к безлимитной сети. Теперь вы также можете использовать JobScheduler для реагирования на изменения в поставщиках контента. Объекты JobInfo инкапсулируют параметры, которые JobScheduler использует для планирования вашего задания. При выполнении условий задания система выполняет его в JobService вашего приложения.

На этой странице мы узнаем, как использовать альтернативные методы, такие как JobScheduler , чтобы адаптировать ваше приложение к этим новым ограничениям.

Ограничения, инициированные пользователем

На странице «Использование батареи» в настройках системы пользователь может выбрать один из следующих вариантов:

  • Без ограничений: разрешена вся фоновая работа, которая может потреблять больше заряда батареи.
  • Оптимизировано (по умолчанию): оптимизирует способность приложения выполнять фоновую работу в зависимости от того, как пользователь взаимодействует с приложением.
  • Ограничено: полностью запрещает работу приложения в фоновом режиме. Приложения могут работать некорректно.

Если приложение демонстрирует некоторые из проблем, описанных в Android Vitals , система может предложить пользователю ограничить доступ этого приложения к системным ресурсам.

Если система замечает, что приложение потребляет чрезмерно много ресурсов, она уведомляет об этом пользователя и предоставляет ему возможность ограничить действия приложения. Поведение, которое может привести к появлению уведомления, включает:

  • Чрезмерное количество блокировок: 1 частичная блокировка, удерживаемая в течение часа при выключенном экране.
  • Избыточные фоновые службы: если приложение ориентировано на уровни API ниже 26 и имеет избыточные фоновые службы

Точные ограничения определяются производителем устройства. Например, в сборках AOSP под управлением Android 9 (уровень API 28) и выше приложения, работающие в фоновом режиме и находящиеся в состоянии «ограничено», имеют следующие ограничения:

  • Невозможно запустить службы переднего плана
  • Существующие приоритетные службы удаляются с переднего плана.
  • Сигнализации не срабатывают
  • Задания не выполняются

Кроме того, если приложение предназначено для Android 13 (уровень API 33) или выше и находится в «ограниченном» состоянии, система не отправляет широковещательную рассылку BOOT_COMPLETED или LOCKED_BOOT_COMPLETED до тех пор, пока приложение не будет запущено по другим причинам.

Конкретные ограничения перечислены в разделе Ограничения по управлению питанием .

Ограничения на прием трансляций сетевой активности

Приложения, ориентированные на Android 7.0 (уровень API 24), не получают широковещательные сообщения CONNECTIVITY_ACTION если они зарегистрированы для их получения в манифесте, и процессы, зависящие от этих широковещательных сообщений, не запускаются. Это может создать проблему для приложений, которые отслеживают изменения сети или выполняют массовые сетевые действия при подключении устройства к безлимитной сети. В фреймворке Android уже существует несколько решений для обхода этого ограничения, но выбор подходящего решения зависит от того, какие задачи должно выполнять ваше приложение.

Примечание: BroadcastReceiver , зарегистрированный с помощью Context.registerReceiver() продолжает принимать эти трансляции, пока приложение работает.

Планирование сетевых заданий по безлимитным соединениям

При использовании класса JobInfo.Builder для создания объекта JobInfo примените метод setRequiredNetworkType() и передайте JobInfo.NETWORK_TYPE_UNMETERED в качестве параметра задания. Следующий пример кода планирует запуск службы при подключении устройства к безлимитной сети и начале зарядки:

Котлин

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

Ява

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

Когда условия вашего задания выполнены, ваше приложение получает обратный вызов для запуска метода onStartJob() в указанном JobService.class . Дополнительные примеры реализации JobScheduler см. в примере приложения JobScheduler .

Новой альтернативой JobScheduler является WorkManager, API, позволяющий планировать фоновые задачи, требующие гарантированного завершения, независимо от того, запущен ли процесс приложения. WorkManager выбирает подходящий способ выполнения задачи (непосредственно в потоке процесса приложения, а также с помощью JobScheduler, FirebaseJobDispatcher или AlarmManager) на основе таких факторов, как уровень API устройства. Кроме того, WorkManager не требует использования сервисов Play и предоставляет ряд расширенных функций, таких как объединение задач в цепочку или проверка статуса задачи. Подробнее см. в разделе WorkManager .

Мониторинг сетевого подключения во время работы приложения

Работающие приложения по-прежнему могут прослушивать CONNECTIVITY_CHANGE с зарегистрированным BroadcastReceiver . Однако API ConnectivityManager предоставляет более надёжный метод запроса обратного вызова только при соблюдении определённых сетевых условий.

Объекты NetworkRequest определяют параметры обратного вызова сети в терминах NetworkCapabilities . Объекты NetworkRequest создаются с помощью класса NetworkRequest.Builder . Затем registerNetworkCallback() передает объект NetworkRequest системе. При выполнении сетевых условий приложение получает обратный вызов для выполнения метода onAvailable() определенного в его классе ConnectivityManager.NetworkCallback .

Приложение продолжает получать обратные вызовы до тех пор, пока приложение не завершит работу или не вызовет unregisterNetworkCallback() .

Ограничения на прием изображений и видеотрансляций

В Android 7.0 (API уровня 24) приложения не могут отправлять и получать сообщения ACTION_NEW_PICTURE или ACTION_NEW_VIDEO . Это ограничение помогает снизить влияние на производительность и удобство использования, когда для обработки нового изображения или видео требуется активация нескольких приложений. В Android 7.0 (API уровня 24) расширены JobInfo и JobParameters , чтобы предоставить альтернативное решение.

Запуск заданий при изменении URI контента

Для запуска заданий при изменении URI контента Android 7.0 (уровень API 24) расширяет API JobInfo следующими методами:

JobInfo.TriggerContentUri()
Инкапсулирует параметры, необходимые для запуска задания при изменении URI контента.
JobInfo.Builder.addTriggerContentUri()
Передаёт объект TriggerContentUri в JobInfo . ContentObserver отслеживает инкапсулированный URI контента. Если с заданием связано несколько объектов TriggerContentUri , система выполняет обратный вызов, даже если сообщает об изменении только одного из URI контента.
Добавьте флаг TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS , чтобы запустить задание при изменении любого потомка заданного URI. Этот флаг соответствует параметру notifyForDescendants , переданному в registerContentObserver() .

Примечание: TriggerContentUri() нельзя использовать в сочетании с setPeriodic() или setPersisted() . Для непрерывного отслеживания изменений контента запланируйте новый JobInfo до того, как JobService приложения завершит обработку последнего обратного вызова.

Следующий пример кода планирует запуск задания, когда система сообщает об изменении URI контента, MEDIA_URI :

Котлин

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

Ява

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

Когда система сообщает об изменении указанного URI контента, ваше приложение получает обратный вызов, и объект JobParameters передается методу onStartJob() в MediaContentJob.class .

Определите, какие органы по контролю контента инициировали работу

Android 7.0 (уровень API 24) также расширяет JobParameters , позволяя вашему приложению получать полезную информацию о том, какие органы управления контентом и URI инициировали задание:

Uri[] getTriggeredContentUris()
Возвращает массив URI, запустивших задание. Значение будет null если задание не было запущено ни одним URI (например, задание было запущено из-за крайнего срока или по другой причине), или количество изменённых URI превышает 50.
String[] getTriggeredContentAuthorities()
Возвращает строковый массив полномочий по контенту, запустивших задание. Если возвращаемый массив не равен null , используйте getTriggeredContentUris() для получения информации об изменившихся URI.

Следующий пример кода переопределяет метод JobService.onStartJob() и записывает полномочия по контенту и URI, которые запустили задание:

Котлин

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
}

Ява

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

Продолжайте оптимизировать свое приложение

Оптимизация приложений для работы на устройствах с малым объёмом памяти или в условиях её дефицита может повысить производительность и удобство использования. Устранение зависимостей от фоновых служб и неявных приёмников широковещательных сообщений, зарегистрированных в манифесте, может улучшить работу вашего приложения на таких устройствах. Хотя Android 7.0 (уровень API 24) принимает меры для устранения некоторых из этих проблем, рекомендуется оптимизировать приложение для работы без использования этих фоновых процессов.

Следующие команды Android Debug Bridge (ADB) помогут вам протестировать поведение приложения с отключенными фоновыми процессами:

  • Чтобы смоделировать условия, при которых неявные трансляции и фоновые службы недоступны, введите следующую команду:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND ignore
    
  • Чтобы повторно включить неявные трансляции и фоновые службы, введите следующую команду:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow
    
  • Вы можете имитировать, как пользователь переводит ваше приложение в режим «ограниченного» использования батареи в фоновом режиме. Эта настройка запрещает вашему приложению работать в фоновом режиме. Для этого выполните следующую команду в окне терминала:
  • $ adb shell cmd appops set <PACKAGE_NAME> RUN_ANY_IN_BACKGROUND deny