Когда-то в 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, как негативным, так и, конечно, позитивным. Не забудьте поделиться своим мнением касательно тренда на ограничение сценариев для выполнения работы в фоне.

Комментарии (28)


  1. stalinets
    16.06.2023 08:47
    +5

    Пользуюсь дозиметром Atom Fast, подключаемым по BLE к смартфону. Соответственно, его родной софт (Atom Swift) должен всегда работать, потому что он собирает статистику в график мощности дозы, рисует трек на карте показывая где какая была радиация, должен (помимо пищалки в дозиметре) выдать аларм при скачке радиации. Так вот просто заставить это приложение стабильно работать в фоне - большой гемор, надо слазить в несколько закоулков системы, отключая всевозможные энергосберегающие опции. И всё равно примерно раз в 2-3 недели я обнаруживаю, что приложение не запущено и дозиметр "отвалился". Возможно, это происходит потому, что андроид иногда ставит какие-то минорные обновления и без спросу уходит в ребут (я пару раз ловил этот момент ночью - телефон просто лежит и пошёл перезагружаться), а запустить то, что работало, андроид не желает. И при этих перезагрузках слетают некоторые настройки ввода текста, что отдельно бесит.

    Вообще эта экономия ресурсов телефона где не надо напрягает. Раньше что на компе, что на телефоне я мог открыть страницу в браузере, уехать на месяц в отпуск, вернуться и продолжить с ней работать. Устарела она, не устарела - это была только моя забота. А теперь и ОС свапит браузер из RAM (даже если её вагон), и браузер считает вправе решать, что сессия и куки устарели и надо всё обнулить, и сайтостроители делают динамические страницы, где всё меняется не по воле пользователя.


    1. S1re10k_4f99
      16.06.2023 08:47
      +3

      Вот полностью согласен.

      Раньше на 5-ом AOSP, с 2 гб оперативкой можно было открыть все предустановленные приложения и ещё пару вкладок в браузере и оно всё так могло висеть минимум неделю и никуда не пропадать.

      Сейчас на MIUI с андроид 10 и 6гб оперативки, почти всегда приложения открываются с нуля каждый раз! Просто открываешь СМС, чтобы потом воткнуть код в браузер или банковское приложение, так сразу страница в браузере выгружается, а банковское приложение тем более.

      А уж пропадает всё почти со 100% вероятностью за ночь, даже из меню запущенных пропадает. Это при том, что везде где только можно разрешено всё что только можно - работа в фоне, автозапуск в двух местах разрешён, в меню запущенных замки поставлены. Если это всё не потыкать, то будет просто система одного приложения.

      При этом работа от батареи всё равно никакая! Даже при таких агрессивных закрытиях моих приложений, телефон довольно туго входит в режим сна и часто из него выходит, поэтому за ночь может сожрать до 20% зарядки, тогда как на старечке с 5-ым андроидом таких проблем не было, за ночь больше 1% никогда не разряжал.


      1. Inoriol
        16.06.2023 08:47

        Это больше проблема MIUI. Очень агрессивная в своих потугах освободить побольше памяти и ресурсов прошивка в которой куча вещей сделана через задний проход. Разрабы Vanced например на MIUI бугуртели очень знатно... AdGuard тоже. В общем MIUI очень проблемна.


        1. S1re10k_4f99
          16.06.2023 08:47
          +1

          А кто сейчас не агрессивный то?
          Хуавей даже уведомления через раз показывал например.
          Рилми, 1+, оппо, виво - они лучше?
          Смотрю в сторону пикселей, просто надоело это.

          Например в приложении aliexpress листаешь себе каталог товаров и тут тебе пришло СМС или сообщение в мессенджере, нужно ответить. Даже когда отвечаешь в шторке, не открывая приложение, всё равно aliexpress закрывается и открывается полностью с нуля, просто минус 2 часа листания товаров. После такого просто удаляешь нахрен приложение и пользуешься только сайтом, полной версией сайта на телефоне или вообще только на ПК, где ничего никуда не девается, даже при том что оперативки меньше чем в телефоне.


    1. kirich1409 Автор
      16.06.2023 08:47
      +2

      Android смартфон не имеет SWAP да его принцы не дают это делать, хотя уже некоторые производители стали делать расширение RAM за счет памяти на диске. Также учитывайте что софт стал сложнее, на экранах больше пикселей и потребление памяти растет.


      1. stalinets
        16.06.2023 08:47

        Ну, я не программист и не знаю как оно там под капотом, но по опыту использования я это вижу. Открыл в мобильном браузере несколько вкладок (причём как со сложными динамическими сайтами, так и с простыми статическими сайтами-визитками), свернул браузер на часок - открываю - и все они снова по несколько секунд подгружаются. А если просто свернуть-развернуть браузер - они продолжают висеть и реагируют мгновенно. То же самое с приложением ВК, я не могу просто свернуть и через день продолжить просмотр ленты с места где я остановился: оно обнуляется и грузится с нуля.


        1. samally
          16.06.2023 08:47
          +2

          Приложение ВК могло бы восстанавливать просмотр ленты с того же места. Это выбор компании обнулять и грузить с нуля.


          1. S1re10k_4f99
            16.06.2023 08:47
            -1

            Если бы это был выбор компании, то это было бы одинаково на любом андроиде.


            1. SergeyMax
              16.06.2023 08:47
              +1

              Нет.


        1. kirich1409 Автор
          16.06.2023 08:47
          +3

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


          1. S1re10k_4f99
            16.06.2023 08:47
            -1

            И каким образом это тогда работает на старых андроидах, но не работает на новых
            И зачем вообще что-то сохранять, если можно просто не закрывать?

            Например с aliexpress тоже самое, листаешь себе каталог товаров и тут тебе пришло СМС или сообщение в мессенджере, нужно ответить. Даже когда отвечаешь в шторке, не открывая приложение, всё равно aliexpress закрывается и открывается полностью с нуля, просто минус 2 часа листания товаров.
            После такого просто удаляешь нахрен приложение и пользуешься только сайтом, полной версией сайта на телефоне или вообще только на ПК, где ничего никуда не девается, даже при том что оперативки меньше чем в телефоне.


            1. kirich1409 Автор
              16.06.2023 08:47

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


              1. S1re10k_4f99
                16.06.2023 08:47

                Наоборот получается. Старые телефоны держат все приложения в памяти, а новые делают чтобы открытое приложение работало лучше и просто все остальные приложения нахрен выкидывают.

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

                Просто какой-то дешман ZTE с 512мб оперативки и то мог держать открытыми 2 приложения, а иногда и 3. Да он тормозил дико, но он держал открытыми 2-3 приложения с 512мб оперативкой.
                А сяоми с 6 гб оперативкой такое не всегда может, но не тормозит.


      1. Pro-invader
        16.06.2023 08:47

        Это не верно. Android поддерживает swap, другое дело, что оф. прошивки его не поддерживают. С рутом можно поставить проги, которые свапят.


        1. kirich1409 Автор
          16.06.2023 08:47

          Сама система специально строилась без SWAP изначально. Та же Dalvik и JVM от Oracle разные виртуальные машины с учетом особенностей мобильных устройств


          1. Pro-invader
            16.06.2023 08:47
            -2

            Может и строилась без, но андроид поддерживает swap изначально т.к. это все же Linux.


            1. kirich1409 Автор
              16.06.2023 08:47

              Android изначально имел свой форум Linux, SWAP не подходит из-за особенностей устройств под Android.


  1. ebragim
    16.06.2023 08:47
    +6

    Есть приложения, которые должны работать в фоне всегда, эта самая их суть. Например системный блокер рекламы, блокер спамзвонков, запись разговора, или упомянутый другим комментатором мониторинг радиации. Я вот не верю, что там люди за много лет не научились использовать правильные api, но всё равно, чтобы их не выгрузило приходится корячить кучу настроек, всякие appconnector и прочие обходные пути. При этом что-то я не вижу прироста производительности или времени работы вообще.
    И да, "телефон должен быть тонким" - это маркетинговая чушь, которую нам навязали производители. При уменьшении толщины ниже 12-15мм телефон становится всё менее удобным, 7мм уже банально неприятно держать - ощущение "хрупкости", слишком неухватистый, необходимость в торчащих камерах так как не влезает их оптика, вырезание джека или сим/sd лотков...


    1. kirich1409 Автор
      16.06.2023 08:47

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


  1. vadimk91
    16.06.2023 08:47
    +1

    Теперь гугл еще стал отбирать ранее выданные приложениям разрешения даже на старых версиях. Телефон с 8 версией, приложение записи геотреков должно работать в фоне, всё разрешаю, но в очередной раз опять получаю сообщение "чтобы приложение продолжало работать, разрешите фоновую работу". Я сам и разрешал, зачем гугл запрещает без спроса, мне непонятно.


    1. kirich1409 Автор
      16.06.2023 08:47

      Не очень про что идет речь и как этой выглядит


  1. quaer
    16.06.2023 08:47

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

    А как сейчас приложения работают с поддержкой Андроид 5?


    1. kirich1409 Автор
      16.06.2023 08:47

      Там используются старые API. Тот же WorkManager работает на основе AlarmManager + Service


  1. Dolios
    16.06.2023 08:47

    Все хотят тонкие устройства

    Отучаемся говорить за всю сеть (с) фидо


  1. expromt
    16.06.2023 08:47

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


    1. kirich1409 Автор
      16.06.2023 08:47

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


  1. kirich1409 Автор
    16.06.2023 08:47

    Там используются старые API. Тот же WorkManager работает на основе AlarmManager + Service


  1. rombell
    16.06.2023 08:47

    Спасибо! Как пользователь Андроида, много я не знал, а кое-чего лучше бы и не знал — была бы надежда на собственную криворукость, а оно, оказывается, так и задумано. Печаль.


    даже если приложение сейчас убито, но BroadcastReceiver подписан на какой-то бродкаст в AndroidManifest, система запустит процесс приложения, чтобы доставить его в BroadcastReceiver, который подписался на рассылку.

    Так вот откуда были приложения, которые, сколько не убивай, всё равно вылезают! В новых версиях я научился загонять всё под Shelter во вторую область, и теперь оно самопроизвольно не стартует. Вот ещё бы разного рода гугло-системные приложения поприбивать, типа того же Play Market самостартующего.


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

    А, теперь понятно, почему два навигатора одновременно не получались — скажем, Яндекс и 2ГИС. При езде по трассе часть камер знает Яндекс, част 2ГИС, эти множества не совпадают, и хочется иметь оба набора. Приходится в поездках использовать два смарта.


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


    Я из тез, кто предпочитает сам всё отстроить в системе, но гуглу и вендорам всё лучше знать, как обычно.