Пользователи устройств под управлением iOS часто жалуются, что аккумулятор быстро разряжается. Когда это происходит? Чаще всего при использовании GPS, но есть и другие причины ускоренного разряда батареи. Например, насыщенный обмен через разные виды приемопередатчиков (сотовая связь, bluetooth) или интенсивная отрисовка графики. Некоторые любители яблочных девайсов грешат на батарею, но часто в быстрой разрядке виноваты разработчики приложений. Пользователям важно, чтобы заряда хватало надолго, поэтому при разработке стоит избегать решений, которые повышают энергопотребление. Но зачем разработчикам вообще думать о том, как долго телефон держит заряд? Чем дольше iPhone включен, тем больше шанс, что пользователь откроет приложение и будет им пользоваться.
Я рассмотрю основные энергопотребители и подходы, которые помогают уменьшить энергопотребление девайса.
Основные энергопотребители
Больше всего заряд расходуют:
- CPU (central processing unit, центральный процессор);
- Взаимодействие с хранилищем
- Приемопередатчики сигналов
- Отрисовка графики
Как потребляется энергия при выполнении работы?
Рис. 1. Энергопотребление при выполнении работы.
На рис. 1 видно, как потребляется энергия, когда устройство выполняет работу. Также видим разделение на две части: фиксированное и динамическое энергопотребление.
Фиксированное энергопотребление — энергия, которая нужна, чтобы поддерживать работу системы, активировать передатчики данных (будь то сотовая связь, Wi-Fi, GPS) и т.д.
Динамическое энергопотребление — количество энергии, которое необходимо для выполнения полезной работы.
Пройдемся по состояниям с рис. 1 для большей ясности:
- Система находится в состоянии простоя — минимальное потребление энергии. Даже в состоянии простоя девайс потребляет энергию, чтобы оставаться отзывчивым.
- Происходит некая активность, например, пришло PUSH-уведомление.
- Простой, энергопотребление уменьшилось после того, как PUSH-уведомления обработалось.
- Опять некая активность, например, пользователь нажал на уведомление, и запустилось приложение. Оно отобразило полную информацию по уведомлению, после чего пользователь закрыл приложение.
- Простой, энергопотребление опять снизилось. Отметим, что это состояние идентично состоянию 3, вся разница в том, что после состояния 3 опять произошла активность, которая увеличила энергопотребление.
- Энергопотребление продолжает уменьшаться, и система опять входит в состояние простоя.
О чем это говорит? Когда необходимо выполнить работу, энергопотребление быстро увеличивается, чтобы немедленно выполнить задачу. После работы требуется значительно больше времени для спада энергопотребления. Падение происходит в состоянии простоя, на вхождение в которое требуется время.
Что делать чтобы энергопотребление снизилось?
1. Избегать выполнения работы в состояние inactive
Если приложение не отображается в данный момент (например, перекрыто другим приложением), останавливайте таймеры, потоки, работу по сети, прекращайте перерисовку экрана. Для этого достаточно подписаться на делегаты
applicationWillResignActive(_:)
applicationDidBecomeActive(_:)
Они будут вызваны в моменты перехода приложения из foreground и возвращения в него. Соответствующие нотификации есть у NSNotification.
2. Совершать работу в оптимальный момент времени
Наилучший момент — тот, во время которого на выполнение работы будет потрачено минимальное количество заряда. Разработчик не знает, какой момент времени является наилучшим для выполнения задачи, но об этом знает система, которая сама может запланировать выполнение.
Это может быть полезно, когда необходимо скачивать или отправлять значительные объемы данных, периодически обновлять информацию или выполнять другие периодические действия по сети.
Для планирования работы по сети есть: URLSessionConfiguration.background(withIdentifier)
. С его помощью можно создавать запросы, а обрабатывать результаты уже при следующем запуске приложения. Также поддерживается автоматическое повторение запроса в случае неудачи. При создании задачи система сама выбирает наиболее благоприятный момент для выполнения.
Когда стоит задуматься об использовании background конфигурации:
- Автоматическое сохранение
- Обработка данных
- Подкачка контента
- Выполнение чего-либо с интервалом 10 минут и более
Пример:
Необходимо загрузить много данных для дальнейшей работы в offline. Можно будет обработать результаты запросов в следующий раз, когда система или пользователь решит продолжить работу приложения, так как все эти запросы выполняются на системном потоке и результаты их точно придут в приложение.
3. Выполнять работу эффективнее
Допустим, работа уже выполняется, даже в наилучшее время. Но также важно задать этой работе соответствующий приоритет. В системе iOS есть системные очереди, и у каждой из них свой приоритет на владение ресурсами. Таким образом можно регулировать, сколько процессорного времени и прочих ресурсов будет выделено для выполнения задачи.
- User Interactive — самая приоритетная очередь. Еще ее называют main или ui, потому что на этой очереди выполняются задачи по отрисовке интерфейса.
- User Initiated — следующая по приоритету. Используется для выполнения задач, завершения которых ожидает пользователь (загрузка контента, обработка изображений).
- Utility — долгие задачи. Пользователь знает, что такие задачи выполняются, и может ждать их завершения.
- Background — самый низкий приоритет. Тут разработчику стоит задуматься, а можно ли отсрочить выполнение задачи? Если да, то используем
URLSessionConfiguration.background(withIdentifier)
.
Рис. 2. Энергопотребление на разных очередях.
Посмотрим на энергопотребление при выполнении одинакового кода на разных очередях. Динамическое энергопотребление будет одинаковым, но суммарное будет меньше, поскольку, сократив скорость выполнения, мы уменьшили фиксированные затраты. То есть суммарное потребление уменьшилось из-за уменьшения фиксированного. Более эффективный код всегда менее энергозатратен.
4. Совершать меньше работы
Для это следует улучшать производительность по четырем направлениям:
1. CPU
Сначала надо определить код, активно использующий CPU. В этом помогут XCode Instruments. Обычно такой код работает с таймерами и вызовами, которые останавливают поток: NSTimer, GCD timers, performSelector(withObject, afterDelay), CFRunLoopTimer, pthread_cond_timedwait(), sleep(), dispatch_semaphore_wait().
Рассмотрим этот процесс на примере таймера.
Рис. 3. Энергопотребление при работе таймера.
Видим накладные расходы, которые не успевают снизиться из-за частых срабатываний таймера. Чтобы улучшить производительность при использовании таймера, используйте setTolerance
. Это позволит выполнять срабатывания в оптимальное время, выбранное системой.
myTimer.setTolerance(60.0) // в промежутке 60 секунд будет вызван обработчик
2. Отрисовка графики
Чтобы сэкономить энергию, не совершайте лишних действий. Например, вместо вызова setNeedsDisplay
, который перерисует все View, вызовите setNeedsDisplayInRect
. Избегайте наложения блюров на часто перерисовывающиеся объекты, такое наложение заставит блюр постоянно перерисовываться. Также избегайте лишних перерисовываний, выводящих энергопотребление из спящего уровня. Мониторить отрисовку можно через Quartz Debug или Instruments.
3. Взаимодействие с хранилищем
- Помните, что операции записи потребляют гораздо больше энергии, чем операции чтения
- Группируйте операции записи, чтобы уменьшить фиксированное энергопотребление
- Используйте кэширование
4. Приемопередатчики сигналов
Различные виды передатчиков (мобильная связь, Wi-Fi, Bluetooth, GPS) часто используются в приложениях. Обычно обращения к ним происходят, как только это инициировал пользователь или появились данные для отправки. Посмотрим на график энергопотребления при таком подходе.
Рис. 4. Энергопотребление при активном взаимодействии по сети с интервалом.
Из-за фиксированных расходов на активацию передатчика энергопотребление остается высоким все время, когда периодически происходит отправка данных. Стоит также учитывать, что расход энергии зависит от типа приемопередатчика, которым мы пользуемся. Например, на 5S при использовании Wi-Fi батарея выдерживает 10 часов серфинга, а с мобильной связью — 8 часов.
Если есть возможность, буферизуйте данные и отправляйте их все в один момент времени, как на графике ниже.
Рис. 5. Энергопотребление при активном сетевом взаимодействии в один промежуток времени.
А еще не стоит забывать про размер данных, которые отправляет и принимает приложение. От этого зависит время на протяжении которого будет работать приемопередатчик, чем меньше размер, тем быстрее он выключится. Поэтому стараемся все сжимать и кэшировать.
Практики сокращения энергопотребления в background
Рассмотрим практики в моменты, когда приложение находится в background. Активности для этого раздела можно разделить на четыре группы:
Notifications
Notifications делятся на Local (запланированы приложением) и Push (отправляются с сервера). Локальные уведомления не требуют взаимодействия по сети и не запускают приложение. При уведомлении через APNS, устройство проснется, и приложение будет запущено в background для обработки уведомления (если необходимо подкачать данные для отображения уведомления).
Как здесь снизить энергопотребление? Используйте local notifications всегда, когда это возможно. Также выставляйте приоритет для APNS уведомления и, в зависимости от него, доставка произойдет немедленно, либо оптимальное время будет выбрано системой.
VoIP
Раньше поддерживать VoIP можно было только до тех пор, пока приложение не перешло в состояние terminated. Надо было вручную возобновлять сессию, опрашивать сервер, из-за этого энергия расходовалась быстро. Сейчас повышенного потребления можно избежать: появились PUSH-уведомления для звонков. Т.е. когда приходит такое уведомление, приложение просыпается и начинает обрабатывать звонок. И больше нет необходимости поддерживать постоянное соединение с сервером. Используйте новое API для снижения энергопотребления.
Location
Процесс определения геолокации вызывает сильное энергопотребление, из-за того, что нужно держать GPS приемник активным длительное время для позиционирования девайса. При использовании GPS, оптимизация энергопотребления приводит к снижению точности определения геопозиции. В приложениях типа игр Pokemon Go снижение точности определения геопозиции неприемлемо, но во многих других ситуациях это допустимо.
Есть множество настроек LocationManager, которые позволяют задавать требуемую точность в метрах, условия срабатывания, таймауты. Сейчас мы кратко рассмотрим основные техники для снижения энергопотребления:
- Переключать режимы точности. Например, разрабатывается квест по городу на основе геолокации. Необходимо, чтобы пользователь с точностью до 10 метров достиг определенного места на карте. В этом случае стоит использовать significantLocationChange, а затем переходить к более точным настройкам. SignificantLocationChange — наиболее энергоэффективный метод определения геолокации, предоставляющий данные с точностью ~700 метров. Пока пользователь не достиг даже 700 — метрового радиуса от цели, зачем тратить батарею на получение более точных данных?
- Откладывать обработку сигналов от LocationManager до тех пор, пока приложение находится в background. Например, мы делаем фитнес приложение, которое собирает данные во время бега. Пока телефон лежит в кармане, и им все равно никто не пользуется, не стоит разряжать, обрабатывая сигналы об изменении геопозиции. Пусть лучше они буферизуются в системе, а когда пользователь решит просмотреть свою дистанцию и откроет приложение, они поступят на обработку. В создании такого приложения вам поможет метод deferredLocationUpdatesAvailable и allowDeferredLocationUpdatesUntilTraveled.
- Обнаруживать регионы. startMonitoringForRegion позволит обнаруживать заранее установленные регионы, тем самым избавив от необходимости постоянно получать текущую позицию и проверять находится ли пользователь в нужном месте.
Коротко — используйте наихудшую точность определения геопозиции, которая позволяет решить вашу бизнес — задачу.
Bluetooth
Взаимодействие по Bluetooth тоже требует использования приемопередатчика, поэтому для экономии энергопотребления буферизуем данные, сокращая количество сеансов передачи, а также группируем сеансы по времени.
Есть еще одна возможность экономии — не “будить” приложение по событиям от bluetooth приемника.
Например, вспомним фитнес приложение. Мы оптимизировали его работу таким образом, чтобы сигналы от LocationManager обрабатывались только, когда пользователь открыл приложение. В этот самый момент стоит обратиться по bluetooth к фитнес-трекеру и забрать данные о пульсе. Это позволит не поддерживать связь с фитнес-трекером все время, забирая информацию о пульсе, ведь ее все равно никто не посмотрит во время бега.
Резюме
Энергопотребление быстро увеличивается, но долго снижается после выполнения работы. Используйте приемы для снижения энергопотребления:
- Помните, что фиксированное энергопотребление можно уменьшить, группируя выполнение задач.
- Старайтесь отдавать системе подгрузку или загрузку большого объема данных. Она это сделает в оптимальный момент.
- Не выводите приложение из состояния простоя, когда этого можно избежать
Автор: negovrodion