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

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

Чтобы решить эту проблему, 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