Фоновые процессы могут потреблять много памяти и заряда батареи. Например, неявная широковещательная рассылка может запустить множество фоновых процессов, зарегистрировавшихся для ее прослушивания, даже если эти процессы не выполняют большой работы. Это может существенно повлиять как на производительность устройства, так и на удобство использования для пользователя.
Для решения этой проблемы в 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» , система может предложить пользователю ограничить доступ этого приложения к системным ресурсам.
Если система обнаруживает, что приложение потребляет чрезмерное количество ресурсов, она уведомляет пользователя и предоставляет ему возможность ограничить действия приложения. К действиям, которые могут вызвать уведомление, относятся:
- Чрезмерное количество блокировок пробуждения: 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) }
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); }
Когда условия для вашей задачи будут выполнены, ваше приложение получит обратный вызов для выполнения метода 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) }
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()); }
Когда система сообщает об изменении указанного 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 }
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; }
Дополнительно оптимизируйте ваше приложение.
Оптимизация приложений для работы на устройствах с ограниченным объемом памяти или в условиях дефицита памяти может улучшить производительность и пользовательский опыт. Удаление зависимостей от фоновых служб и зарегистрированных в манифесте неявных широковещательных приемников может помочь вашему приложению лучше работать на таких устройствах. Хотя 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