Фоновые процессы могут потреблять много памяти и заряда батареи. Например, неявная широковещательная рассылка может запустить множество фоновых процессов, зарегистрированных для ее получения, даже если эти процессы не выполняют много работы. Это может существенно повлиять на производительность устройства и удобство использования.
Чтобы устранить эту проблему, в 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, система может предложить пользователю ограничить доступ приложения к системным ресурсам.
Если система обнаруживает, что приложение потребляет слишком много ресурсов, она уведомляет об этом пользователя и предлагает ему ограничить действия приложения. Вот некоторые примеры:
- Слишком долгая работа запретов блокировки: один частичный запрет блокировки, который действует в течение часа при выключенном экране.
- Чрезмерное количество фоновых служб. Если целевой уровень 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 в качестве параметра задания. В следующем примере кода показано, как запланировать запуск сервиса, когда устройство подключено к безлимитной сети и заряжается:
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); }
Когда условия для задачи будут выполнены, приложение получит обратный вызов для запуска метода onStartJob() в указанном классе JobService.class. Другие примеры реализации JobScheduler можно найти в примере приложения JobScheduler.
Новой альтернативой JobScheduler является WorkManager – API, который позволяет планировать фоновые задачи, требующие гарантированного завершения, независимо от того, запущен ли процесс приложения. WorkManager выбирает подходящий способ выполнения работы (непосредственно в потоке процесса приложения или с помощью JobScheduler, FirebaseJobDispatcher или AlarmManager) на основе таких факторов, как уровень API устройства. Кроме того, WorkManager не требует сервисов Google 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:
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()); }
Когда система сообщает об изменении в указанных URI контента, ваше приложение получает обратный вызов, а объект JobParameters передается методу onStartJob() в MediaContentJob.class.
Как определить, какие авторитетные источники контента активировали задание
В Android 7.0 (уровень API 24) также расширены возможности JobParameters, чтобы приложение могло получать полезную информацию о том, какие поставщики контента и URI активировали задание:
-
Uri[] getTriggeredContentUris() -
Возвращает массив URI, которые активировали задание. Если ни один URI не запустил задание (например, оно было запущено из-за крайнего срока или по другой причине) или количество измененных URI больше 50, будет возвращено значение
null. -
String[] getTriggeredContentAuthorities() -
Возвращает массив строк с организациями, которые инициировали задание.
Если возвращенный массив не равен
null, используйтеgetTriggeredContentUris(), чтобы получить сведения о том, какие URI изменились.
В следующем образце кода переопределяется метод JobService.onStartJob() и записываются поставщики контента и URI, которые активировали задание:
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; }
Дополнительная оптимизация приложения
Оптимизация приложений для работы на устройствах с небольшим объемом памяти или в условиях нехватки памяти может повысить производительность и удобство использования. Чтобы приложение работало лучше на таких устройствах, удалите зависимости от фоновых служб и неявных широковещательных приемников, зарегистрированных в манифесте. Хотя в 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