История ограничений фоновой работы в Android для разработчиков

в 6:15, , рубрики: android, android development, broadcastreceiver, Google Play, jobscheduler, service, workmanage, Блог компании Android Broadcast, Разработка под android, фоновая работа

Когда-то в Android были времена, когда запустить задачу в фоне было просто и гарантии её работы были высоки. Теперь же в ОС есть множество ограничений: работа в фоне, доступ к файловой системе, системы оптимизации расхода батарейки, разрешения, часть которых требует одобрения модераторов Google Play. Разработчикам приходится работать в условиях всех этих ограничений и учитывать их при разработке фичей. В рамках этой статьи я хочу разобраться с ограничениями, которые есть на разных версиях Android, чтобы вы смогли лучше понимать, что может происходить.

Если вам интересно следить за самыми последними новостями Android разработки и получать подборку интересных статей по этой тематике, тогда вам стоит подписаться на Телеграм-канал Android Broadcast и мой YouTube канал "Android Broadcast"

Что было вначале

Для выполнения работы с самых первых версий Android у нас было (и остаётся) множество API:

  • Alarm Manager. Инструмент, который позволяет поставить будильник в системе и получать уведомления в BroadcastReceiver.

  • Broadcast Intent. Уведомления о событиях, которые происходят в системе. Например, о новых сообщениях.

  • Service — Background, Foreground и Bound.

  • Sync Adapter. Специальное API, которое связано с аккаунтами для синхронизации данных. Простой пример - при настройке Google аккаунта в Android можно настроить синхронизацию календаря, контактов и других сервисов. Под капотом она работает через Sync Adapter.

  • Download Manager. Утилита, которая позволяет загружать файлы. Она небогата возможностями, зато простая и удобная. Например, Google Play, использует это API для загрузки файлов с сервера на устройство пользователя.

Проблемы появились, потому что разработчики были и остаются эгоистами, используя по максимуму все ресурсы операционки и железа, чтобы обеспечить своему приложению лучшую и гарантированную работу. Google с первых же версий Android дала разработчикам огромные возможности, но не предупредила, что они работают в системе, в которой работают другие приложения, которые нужно уважать. Зачастую, когда у пользователей приложения работали плохо, они винили не разработчиков приложения, а говорили: «Android тормозит, вот на iPhone всё быстро работает». Получалось, что разработчики хорошие, а Google плохой. Но на самом деле iOS всегда давала мало возможностей для работы приложениям в фоне, что для меня является большим минусом яблочной ОС, особенно для iPad Pro.

В один момент Google решила, что пришла пора навести порядок. Для этого нужно было понять что можно, а главное нужно оптимизировать для лучшего опыта работы с Android устройствами и без сокращения времени работы от одного заряда батареи. Я не знаю, как именно думали в Google, но я выдвинул несколько предположений, какие были проблемы:

  • Дилемма «размер батареи против габаритов устройства». Все хотят тонкие устройства, которые мало весят и приятно ложатся в руку. При этом всем нужна большая и мощная батарея. Вместить огромную батарею в тонкий корпус пока не получается. Возможно переход на новую технологию позволит увеличить энергоемкость, но пока имеем, что имеем.

  • Экран. Все хотят высокую яркость и цветопередачу, но экран — основное место, куда тратится энергия смартфона.

  • Оптимизация прошивки под железо устройства

  • Сторонние разработчики, которые не заботятся о работе устройства

  • “Как продать крутой смартфон?” У крутого смартфона мощное железо, а оно ест много энергии. Продать крутой смартфон с бюджетным процессором невозможно. Нужно поставить такой, что греется, как печка, и сумасшедше тратит энергию, но зато все делает быстро.

Android 5.0

Google все это надоело, и там решили, что на сторонних разработчиков полагаться нельзя, ведь это самые большие пираты в экосистеме Android. И тогда появился Project Volta.

Инициатива заключалась в том, чтобы дать новые инструменты для выполнения работы и операций с упором на сокращение расхода батарейки без существенного влияния на пользовательский опыт. Свелось все к тому, чтобы не давать разработчикам делать лишнего, когда это не нужно. Меньше работы - меньше расхода энергии. Так началась большая оптимизация длиной во множество версий ОС.

JobScheduler

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

// Создаем специальный service - подклас JovService,
// который будет вызван JobScheduler для выполнения job
class AppJobService : JobService() {
   
    override fun onStartJob(params: JobParameters): Boolean {
          // Выполняем работу
    }
}


// Указываем JobService для выполнения Job
val jobService = ComponentName(context, AppJobService.class);

// Настраиваем условия для выполнения Job
val job: JobInfo = JobInfo.Builder(jobId, jobService)
    .setRequiresCharging(false); // не важно заряжается телефон или нет
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) // Безлимитная сеть
    .build()

// Получаем JobServices и кладем job в очередь
context.getSystemService<JobScheduler>()
    .schedule(job);

Самое интересное: JobScheduler не использовался в Android 5.0, потому что был жутко забагованный и Google официально рекомендовала его не использовать

Battery Saver

Вторая важная новинка - унифицированный сервис экономии заряда батареи. До этого похожие механизмы были у вендоров как собственные фичи в оболочках. Теперь инструмент стал стандартизированной частью системы. При его включении происходит следующее:

  • Уменьшение частоты процессора

  • Уменьшение частоты обновления дисплея (с 90+ до 60 Гц)

  • Ограничение потребления данных в фоне

  • Другие оптимизации от вендора

Android 6.0

Doze Mode

Давайте проанализируем, как мы пользуемся телефоном. Вы кладёте телефон на стол, пока работаете или ложитесь спать, и телефон оказывается на прикроватной тумбочки. Вы не используете смартфон, зачем тогда приложения активно ходят за обновлениями в сеть или выполняют работу, которая вам не важна? Можно отключить сетевой доступ и другие ненужные функции для части приложения. Такой режим работы устройства назвали Doze Mode! Режим включается, когда система определяет, что телефон не подвижен и не используется.

Google заявляла, что LG Nexus 5 в состоянии Doze Mode поедал всего 1% зарядки за ночь. Правда, я такого никогда не видел.

Чтобы приложения все также могли проверять обновления в Doze Mode, есть промежутки времени, когда приложения могут работать как обычно - maintenance window.

App Standby

В дополнение к Doze Mode появился еще один режим экономии для приложений - App Standby. В этом режиме приостанавливается активность тех приложений, с которыми пользователь не взаимодействует. Если мы сворачиваем приложение и не пользуемся им, то уже тогда на него накладываются ограничения. Они не такие жесткие, как в Doze Mode, но ограничивают доступ в сеть и снижают частоту срабатываний различных событий. В чем конкретно заключаются ограничения, Google не уточняет. Также вендоры могут сделать дополнительные изменения

Firebase Cloud Messaging High Priority

Обычные пуши неспособны вывести приложения из сна и будут доставлены, когда будет возможность. Для того чтобы ваше устройство все также доставляло вам уведомления в нужный момент времени, был разработан новый тип пушей, способных разбудить устройство в Doze Mode и App Standby, под названием High Priority Push.

Возможно в вашей голове уже возник план, чтобы сделать все пуши высокоприоритетными, но это не сработает. Система видит такое и начнет ограничивать их, а также высокоприоритетный пуш - это не гарант доставки сообщения до пользователя. Поэтому использовать новый тип пушей нужно только на действительно важные события. Например, когда пользователю приходит сообщение в чате, в котором он включил уведомления.

Android 7.0

Doze on the Go

В Android 7.0 продолжили развитии Doze Mode. Если раньше устройство приходило в Doze Mode только когда было неподвижным какое-то время, то теперь достаточно выключить экран и положить смартфон в карман. Новую версию Doze называют Doze 2.0 или Doze On-the-go

С учетом нововведения изменился вызов с maintenance window. Окна стали появляться чаще, ограничения стали мягче. Но приложения по-прежнему не могли делать в фоне все что угодно.

Как работает Doze 2.0

Как работает Doze 2.0

Project Svelte

Project Svelte - инициатива в Android, направленная на сокращение расхода оперативной памяти и оптимизации способов работы приложений в фоне.

Первое, что сделали в рамках проекта — убрали бродкасты. В Android 7.0 начали с CONNECTIVITY_ACTION. Он стал первым и самым важным, потому что этот бродкаст рассылается на любое изменение сети. Например, когда пользователь переходит с мобильной сети на Wi-Fi. А в Android есть особенность: даже если приложение сейчас убито, но BroadcastReceiver подписан на какой-то бродкаст в AndroidManifest, система запустит процесс приложения, чтобы доставить его в BroadcastReceiver, который подписался на рассылку.

Представьте, что такое приложение у нас одно. Поднять процесс — недешевая операция, но в целом не страшная. А если таких приложений 20? Каждая смена будет приводить к тому, что процессы будут подниматься и убиваться 20 раз. Хотя и это звучит не так страшно. Проблемой становится это при понимании того, что CONNECTIVITY_ACTION вызывается не только при уходе с сети на Wi-Fi, но и, например, с 3G на 4G. А ведь тип соединения скачет постоянно при прогулках по городу — представьте, сколько событий рассылается системе. Поэтому этот Broadcast и стал первым для отключения. Ограничение на него распространяется, только если вы подписались на него из манифеста. При регистрации Broadcast Receiver из кода все также будет работать без проблем, пока жив процесс приложения.

Также в Android 7.0 отключили рассылку броадкастов о новых картинках и видео в галереи. На замену пришла возможность выполнять работу в JobScheduler при изменении контента в ContentProvider по заданному Uri.

// Uri контента, который будем отслеживать
val contentUri: Uri

val job: JobInfo = JobInfo.Builder(jobId, jobService)
    .addTriggerContentUri(TriggerContentUri(contentUri, 0))
    // Настраиваем job
    .build()

Android 8.0

Project Svelte (развитие)

В Android 8.0 расширили количество броадкастов, которые теперь не доставляются при подписывании на них из AndroidManifest. Проще назвать список тех, что работают, чем рассказать про все отключенные.

Список исключений, актуальный на момент выхода статьи:

  • ACTION_BOOT_COMPLETED;

  • ACTION_LOCAL_CHANGED;

  • ACTION_PACKAGE_DATA_CLEARED и ACTION_PACKAGE_ FULLY_REMOVED;

  • ACTION_NEW_OUTGOING_CALL;

  • ACTION_MEDIA_***;

  • SMS_RECEIVED_ACTION и WAP_PUSH_RECEIVED_ACTION.

Остались только те, кому нет альтернативы. Если вам из списка что-то было важно, тогда смотрите, как заменить их на что-то в JobScheduler или подписывайтесь на броадкасты из кода.

Нет фоновым Service

Android Oreo также запустил долгую серию из ограничений на работу Service, которая продолжается до сих пор в новых версиях ОС. Ввели запрет на запуск фоновых Service. Если разработчик запустит такой Service, а пользователь свернет приложение, то Service проживет совсем недолго и будет принудительно убит системой. Если попытаться запустить обычный Service, когда приложения в фоне, будет крэш. Это привело к тому, что надо запускать Foreground Service или использовать JobScheduler

Foreground Service

Из-за новых ограничений на Background Service теперь надо было явно указывать, что запущенный Service будет Foreground. Связано это с тем, что в API оба типа service запускались одинаково, а Foreground Service становится, если у него вызвать специальный метод startForeground(). Для того чтобы решить сложившуюся задачу в Context, появился новый метод startForegroundService()

// Запускаем Service и говорим системе что он будет Foreground
context.startForegroundService(Intent(context, MediaService::class))

class MediaService : Service() {

    override fun onCreate() {
        super.onCreate()
        // Между вызовом startForegroundService и startForeground
        // должно быть не больше 5 секунд

        // Делаем сервис Foreground
        startForeground(NOTIFICATION_ID, newOngoingNotification())
    }

    fun newOngoingNotification() : Notification
}

Важная особенность запуска Foreground Services - между вызовом методов startForegroundService() и startForeground() должно пройти не больше 5 секунд. Иначе у приложения будет крэш, потому что мы не выполнили контракт.

Обновление JobScheduler

В JosScheduler добавили новые условия: “не мало свободной памяти” (именно так звучит условие). Сколько памяти достаточно, в Google не говорят. И еще одно условие “Не низкий заряд батареи” — что именно под этим подразумевается, опять же не поясняют. Видимо, это про критичные проценты, когда батарея становится красной.

Также JobScheduler позволил указывать, что для сетевой операции должна быть использована нетарифицируемая сеть. Или по простому - вы не платите за объем трафика в сети. Когда мы подключаемся к сети в настройках, мобильная сеть по умолчанию идет как тарифицируемая, то есть мы платим за трафик. Wi-Fi по умолчанию считается нетарифицируемым, но в настройках можно это изменить. В наших локациях зачастую интернет безлимитный, а вот в развивающихся странах тарификации идут за мегабайт и цены немаленькие.

Ограничение на доступ к местоположению

Еще одно ограничение, связанное с безопасностью и сохранением заряда батареи - ограничения на доступ к местоположению. Изменения коснулись сценария, когда приложение находится в фоне и хочет получать актуальные данные о местоположении устройства. Теперь приложение сможет получить их не чаще, чем несколько раз в час. Сколько это раз - подробностей нет.

Android 9.0

App StandBy Buckets

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

  • Active — с приложением работает пользователь или другое приложение в состоянии Active;

  • Working Set — приложение используют часто, но сейчас пользователь с ним не работает;

  • Frequent — приложение используют регулярно, но не обязательно каждый день;

  • Rare — приложение используют редко;

  • Never — приложение установили, но пользователь никогда не запускал его. Выключено все.

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

Ограничения, применяемый для приложения в App Standby Bucket

Ограничения, применяемый для приложения в App Standby Bucket

Улучшение режима экономии энергии

Сделали улучшения в режим “Экономия батареи”. Ему добавили больше ограничений:

  • система стала агрессивнее переводить приложения в режим App Standby;

  • ограничения на работу применяются ко всем приложениям, независимо от targetSdk;

  • доступ к приложению может пропасть при отключении экрана;

  • у фоновых приложений нет доступа к сети;

  • дополнительный пункт — вендоры могут что-то добавить или изменить.

Разрешение на запуск Foreground Service

Также в Android 9.0 добавили новое разрешения для запуска Foreground Service. Нововведение некритично, потому что попадает в категорию normal. Его нужно только декларировать в манифесте, а запрашивать в рантайме не нужно. Фактически оно помогает системе понять, что вы запросили пермишн — за вами можно следить. Если попытаться запустить Foreground Service без разрешения, система все крэшнет и скажет, что у вас SecurityException.

<manifest>

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

    <application ...>
        ...
    </application>
</manifest>

Ограничения на доступ к сенсорам

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

Обновление JobScheduler

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

Еще появилась важная штука для предзагрузки контента. Если приложению нужно предзагрузить данные для отображения, например, главного экрана, можно пометить джобу с помощью этого нового флага, и JobScheduler поднимет ее приоритет.

// Настраиваем условия для выполнения Job
val downloadJob: JobInfo = JobInfo.Builder(jobId, jobService)
    // Для выполнение job нужна любая сеть
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
    // Задаем примерный объем трафика (в байтах), который будет расходован
    .setEstimatedNetworkBytes(
        downloadBytes = SIZE_100_MB,
        uploadBytes = JobInfo.NETWORK_BYTES_UNKNOWN
    )
    // Указываем что job нужна для предзагрузки данных для улучшения UX
    .setPrefetch(true)
    .build()

Android 10

Тип Foreground Service

Первым нововведением Android стало указание типа Foreground Service, который говорит о том, для чего такой service будет использоваться. В Android 10 добавили 8 стандартных типов для операций с камерой, подключенными устройствами, синхронизацией данных, местоположением, проигрыванием и записью медиа, звонки и запись аудио. Задание типа не являлось обязательным.

<manifest>
    <application>
        <service
            android:name="dev.androidbroadcast.sync.VideoSyncService"
            android:foregroundServiceType="dataSync"
        />

        <service
            android:name="dev.androidbroadcast.media.MediaService"
            android:foregroundServiceType="mediaPlayback|mediaProjection"
        />
    </application>
</manifest>

Важно, что у одного Foreground Service может быть объявлено несколько типов в AndroidManifest, а при запуске service из кода вы укажите, для какой цели он запускается, но строго одну из тех, что вы указали в манифесте.

class MediaService : Service() {

		override fun onCreate() {
        super.onCreate()
        // Сделать Service Foreground с одним из типов (из AndroidManifest)
        startForeground(NOTIF_ID, newNotification(), 
            ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK)

        // Сделать Service Foreground со всеми типами из AndroidManifest
        startForeground(NOTIF_ID, newNotification(),
            ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST)

        // Сделать Service Foreground без использования типов
        startForeground(NOTIF_ID, newNotification(),
            ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE)
		}
}

Запрет на запуск Activity из фона

В Android 10 также запретили запускать Activity, когда приложение находится в фоне. В таком состоянии приложения сделать эту операцию может только пользователь. Например, нажатием на уведомление или взаимодействием с виджетом приложения на домашнем экране.

Доступ к местоположению из фона

Если вы захотите получать информацию о местоположении пользователя, когда приложения находится в фоне, то в дополнение к одному из 2 разрешений на доступ к геолокации придется получить новое разрешение на доступ к местоположению из фона и делать это в Foreground Service.

<manifest>

    <!-- Объявляем одно из разрешений для получения местоположения -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    <!-- Объявляем разрешение для доступа к местоположению в фоне -->
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

</manifest>

Android 11

Самое интересное и самое разочаровывающее для меня — что в Android 11 ничего не ограничили. Поэтому мы переходим к…

Android 12

Restricted App Standby Bucket

В Android 12 расширил систему App Standy Bucket, добавив новый, самый жесткий bucket - Restricted. Если приложение потребляет много системных ресурсов или работает нежелательным образом, то система поместит его в этот специальный bucker. Также же в системных настройках приложения появилась новая опция “Использования батареи”, которая может быть выбрана в одно из 3 значений:

  • Unrestricted — «делай что угодно, мне все равно»;

  • Optimized — используется баланс между расходом энергии и возможностями в работе

  • Restricted — когда приложение практически ничего не может делать, пока явно не будет запущено пользователем

Приложения в Restricted App Standy Bucket практически ничего не могут делать:

  • нельзя запускать Foreground Service;

  • запущенные Foreground Service становятся обычными;

  • не будет срабатывать Alarm;

  • не будет запускаться Job;

  • не будут доставляться бродкасты BOOT_COMPLETED и LOCKED_BOOT_COMPLETED.

Ограничения запуска Foreground Service

Начиная с Android 12, приложения в фоне не могут запускать Foreground Service за исключением нескольких случаев. Список исключений очень маленький и скорее всего вы туда не попадёте.

Разрешение на Exact Alarm

Задать будильник в точное время через AlarmManager можно было без каких-либо ограничений, но в Android 12 теперь надо запрашивать разрешение у пользователя.

<manifest>

    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />

</manifest>

И это не какой-нибудь runtime permission с диалогов, а настройка в разделе “Специальных доступов”. То есть мы должны сказать пользователю: сходи в настройки системы и разреши моему приложению устанавливать будильники в точное время.

С одной стороны, это неудобно, с другой — полезно. Многие разработчики сталкивались с тем, что Exact Alarm срабатывали непредсказуемо или не срабатывали вовсе. Новое разрешение — это стандартизация и явный сигнал системе, что пользователю важно это поведение приложения. Главное помнить, что Exact Alarm не обязательно вызовется в нужное нам время. Это может случиться позже на несколько минут или больше. А то и вовсе не произойти, если система так решит.

Expedited Job

Для выполнения важных для пользователя в JobScheduler добавили новый тип Job для - Expedited. Вам нужно использовать такой тип job, когда она должна выполнится мгновенно и с максимальными гарантиями

Expedited Job отличается от обычного Job следующим:

  • имеет меньше ограничений в режимах Doze и «Экономия батареи»;

  • на него не влияют ограничения доступа в сеть, накладываемые Doze, App Standby и режимом «Экономия батареи»;

  • имеет меньше шансов быть убитой, чем обычная Job;

  • ограничения на доступ к местоположению в фоне сохраняются;

  • для запуска работы доступны лишь требования по наличию сети, объему свободного места и сохранению Job между перезагрузками устройства.

Expedited Job также имеет и особенности выполнения:

  • система выделяет квоту на количество одновременно запущенных Expedited Job для приложения, но эти квоты не касаются приложения, с которым взаимодействует пользователь

  • Expedited Job не даёт гарантии мгновенного запуска. Job не запуститься, если устройство слишком загружено или заданные условия нельзя выполнить

  • Expedited Job имеет ограниченное время на выполнение — не больше минуты, но если в системе есть свободные ресурсы, то лимит может быть увеличен. Также при низкой нагрузке может быть увеличено и кол-во выполняемых Expedited Jobs

Android 13

Foreground Service Task Manager

В системной панели системных уведомлений появился новый раздел, который показывает все приложения, которые сейчас работают в фоне. Определяется это по запущенному Foreground Services. Новый раздел называется Foreground Task Manager

Специальная кнопка принудительно останавливает приложение. Не так, как Force Stop в настройках, но все же полностью его останавливает. Даже если оно просто свернуто. Не все приложения будут отображаться в Foreground Task Manager, а часть тех, что там будут показывать, не будут иметь возможности остановки (в интерфейсе не будет соответствующей кнопки).

FGS Task Manager

Удаление из Recent

Force Stop

Незамедлительное удаление приложения из памяти

Остановка воспроизведения медиа

Остановка Foreground Service

Удаление Activity Back Stack

Удаление приложения из истории

Запланированные Job отменены

Alarm отменяются

Благодаря этому изменению уведомления, связанные с Foreground Service можно убрать, если разработчик явно не укажет обратное при создании Notification.

// Запускаем Service и говорим системе что он будет Foreground
context.startForegroundService(Intent(context, MediaService::class))

class MediaService : Service() {

    override fun onCreate() {
        super.onCreate()
        // Между вызовом startForegroundService и startForeground
        // должно быть не больше 5 секунд

        // Делаем сервис Foreground
        startForeground(NOTIFICATION_ID, newOngoingNotification())
    }

    private fun newOngoingNotification() : Notification {
        return Notification.Builder(this)
            // Настраиваем уведомлени
            .setOngoing(true) // Показывать уведомление всегда
            .create()
    }
}

Уведомление о слишком долгом Foreground Service

Также система стала отслеживать приложения, которые слишком долго работают в фоне. Для таких приложений будет показано уведомление с предложением принудительно остановит ь это приложение. По версии Google долго работать в фоне — это 20 часов в окне в 24 часа. За свой опыт использования Android 13 еще до финального релиза я не столкнулся с таким уведомлением для реальных приложений. Кажется, что изменение направлено на откровенных зловредов.

Обновление JobScheduler

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

Кроме этого, добавили приоритет Job. Это спецконстанта, которая помогает отсортировать все Job в рамках одного приложения и приоритизировать их выполнение. Доступно 5 приоритетов от минимального, для неважных пользователю задач, до наивысшего, который предназначен только для Expedited Jobs.

Есть возможность выбрать один из 5 приоритетов

  • PRIORITY_MIN Для задач, результат которых пользователь не ожидает или вовсе не знает про них, например, загрузка аналитики. Может быть отложено, чтобы обеспечить достаточную квоту для задач более высокого приоритета.

  • PRIORITY_LOW Для задач, представляющих минимальную пользу для пользователя, например, предварительная загрузка данных, которых пользователь явно не запрашивал. Такую работы все еще можно отложить, чтобы гарантировать достаточную квоту для задач с приоритетом выше.

  • PRIORITY_DEFAULT Приоритет по умолчанию для всех задач. Максимальное время выполнение - 10 минут и стандартные правила по управлению задачей, как было раньше.

  • PRIORITY_HIGH: Для задач, которые должны выполняться, чтобы пользователь не подумал, что что-то идет не так. Такие задачи имеют максимальное время выполнения - 4 минуты при условии, что все ограничения выполнены и система находится в нагрузке, которая позволяет её выполнить.

  • PRIORITY_MAX: Для задач, которые должны выполняться в первую очередь, например, обработка текстового сообщения для отображения уведомления. Только Expedited Jobs

Android 14

Foreground Service

На момент выхода статьи уже вышла третья бета следующей версии Android 14, в которой уже анонсировали изменения, касающиеся работы с Foreground Service. Указание типа для них появилось в Android 10, а в Android 14 указание типа станет обязательным.

<manifest ...>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

    <application>
        <service
            android:name=".MediaPlaybackService"
            android:foregroundServiceType="mediaPlayback"
            android:exported="false">
        </service>
    </application>
</manifest>

Для этого дополнили список типов, чтобы покрыть все возможные сценарии. И того их стало 13:

  • camera

  • connectedDevice

  • dataSync

  • health

  • location

  • mediaPlayback

  • mediaProjection

  • microphone

  • phoneCall

  • remoteMessaging

  • shortService

  • specialUse

  • systemExempted

User Initiated Data Transfer Jobs

Для загрузки данных “с” и “на” сервер, начиная с Android 14 надо будет использовать новое специальное API - User Initiated Data Transfer Jobs. Приходят они на замену Foreground Service. Это специальный тип Job в JobScheduler.

Чтобы их использовать надо будет запросить в AndroidManifest новый permission - RUN_LONG_JOBS, при создании Job указать, что она инициализирована пользователем и будет использоваться для передачи данных, а также рекомендуется указать ожидаемый объект трафика в байтах, который будет передаваться. Также, как и для Foreground Service, при выполнении такой Job вам надо будет показать уведомление в течение 10 секунд после её запуска. Такой тип задач будет показываться в Task Manager и может быть остановлен пользователем в любой момент.

<manifest>
    <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS"/>
</manifest>
val networkRequestBuilder = NetworkRequest.Builder()
        .addCapability(NET_CAPABILITY_INTERNET)
        .addCapability(NET_CAPABILITY_VALIDATED)

val dataTransferJobInfo: JobInfo = JobInfo.Builder()
        // ...
        .setUserInitiated(true)
        .setDataTransfer(true)
        .setRequiredNetwork(networkRequestBuilder.build())
        .setEstimatedNetworkBytes(1024 * 1024 * 1024)
        // ...
        .build()
val jobScheduler: JobScheduler =
        context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
jobScheduler.schedule(dataTransferJobInfo)
class DataTranserJobService : JobService() {

    override fun onStartJob(params: JobParameters?): Boolean {
        // Задаем уведомление, которое будет показываться во время выполнения Data Transfer Job
        // Уведмоление надо показать в течении 10 секунд после вызова onStartJob()
        val notification = newJobNotification(this)
        setNotification(params, notification.id, notification,
             JobService.JOB_END_NOTIFICATION_POLICY_DETACH)
      
        // Выполняем работу
    }
}

private fun newJobNotification(context: Context): Notificatio {
     // Создаём уведомление
}

Уже было объявлено, что Google Play будет делиться информацией о политике, ограничивающей когда и какие типы Foreground Service может использовать ваше приложение, особенно если их тип не соответствует ожиданиям системы.

Вот теперь выполнять работу в фоне придется только по согласованию системой. Кто-то скажет, что мы уже пришли в iOS, но еще нет. Там вообще даже с согласованием нет такой возможности, а новое правило заставит разработчиков не использовать фоновые сервисы как вздумается.

Заключение

Последние восемь лет Google пытается ограничить разработчиков и заставить их использовать специальные API, так как свобода выбора инструментов привела к хаосу на устройствах. Второй линией обороны служит Google Play, который не дает публиковать приложения с отдельными разрешениями без прохождения специальной проверки со стороны магазина. Дальше будет становиться только жестче: все новые API по умолчанию направлены на ограничения, экономию батарейки и четкий контроль работы приложения со стороны системы. Но это все равно больше, чем можно делать на iOS. Я использую iPad Pro 11” 2018 года, которому ОС не дает стать Pro при всей мощности его железа! Это уже не говоря про мощности планшетов на M1 и M2.

В комментариях делитесь своим опытом выполнения задач в фоне на Android, как негативным, так и, конечно, позитивным. Не забудьте поделиться своим мнением касательно тренда на ограничение сценариев для выполнения работы в фоне.

Автор: Кирилл Розов

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js