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

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

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