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

Почему «болтливые» API разряжают батарею на мобильных устройствах
«Болтливый» API заставляет приложение отправлять много мелких запросов, чтобы собрать один экран. На бумаге каждый запрос выглядит дешево, но на телефоне они быстро суммируются.
Наибольший удар по батарее обычно наносит радио сети. Чипы Cellular и Wi‑Fi переключаются в режим повышенного энергопотребления для отправки и получения данных. Когда приложение выполняет много запросов подряд, радио постоянно просыпается и дольше остаётся активным. Даже если каждый ответ небольшой, повторные пробуждения требуют реальной энергии.
Есть и работа CPU. Каждый запрос — это сбор заголовков, TLS‑рутина, парсинг JSON, обновление кэшей и код приложения для объединения результатов. Если соединения падают, подготовительная работа повторяется.
Болтливость также делает интерфейс медленнее. Вместо одной предсказуемой загрузки экран ждёт цепочку вызовов. Появляются долгие индикаторы загрузки, частичные отрисовки, которые затем «прыгают», и больше таймаутов при плохой сети. Фоновое обновление страдает так же: больше повторов, больше пробуждений, больше расхода батареи.
Практичный подход к проектированию API с учётом батареи мобильного устройства прост: показывайте тот же UI с меньшим количеством круг‑трипов, меньшим объёмом байт и меньшей фоновой работой.
Можно отслеживать успех по нескольким конкретным изменениям:
- Меньше вызовов API на загрузку экрана
- Меньше скачиваемых байт на экран
- Лучше медианное и худшее время до интерактивности по сотовой сети
- Меньше фоновых выборок и повторных попыток для получения тех же данных
Когда эти показатели улучшаются, отзывчивость и время работы батареи обычно идут в паре.
Что измерить до любых изменений
До пакетирования запросов или правки кэширования нужно ясно понять, что приложение делает сейчас. Быстрые выигрыши обычно приходят от исправления нескольких повторяющихся нарушителей, но вы найдёте их только если измеряете реальные экраны и потоки (а не только один сценарий «счастливого пути»).
Для пары самых загруженных экранов логируйте основы: сколько запросов происходит на загрузку, какие конечные точки вызываются, байты в отправке и приёме (заголовки плюс тело), частоту повторов и таймаутов, и сколько времени проходит до ощущения, что UI пригоден к использованию. По возможности разбейте ошибки по типу сети (Wi‑Fi vs cellular). Такие срезы часто выявляют проблемы, которые теряются на стабильном соединении.
Отделяйте трафик переднего плана от фонового. Экран может выглядеть тихим, но телефон занят фоновым обновлением, синхронизацией по пушам, загрузкой аналитики или предзагрузками «на всякий случай». Отслеживайте эти случаи отдельно, чтобы не оптимизировать не тот сценарий.
Горячие точки обычно концентрируются в нескольких моментах: запуск приложения, главный / лента, детальные экраны, которые разветвляются на много вызовов, и pull‑to‑refresh. Выберите один из них и измеряйте end‑to‑end.
Задайте базовый бюджет, чтобы команда соглашалась, что такое «хорошо». Например: «Холодная загрузка экрана отслеживания заказа не должна делать больше 6 запросов и скачивать больше 250 KB до показа статуса.»
Если нужен простой парный KPI для старта, используйте (1) число запросов на загрузку экрана и (2) частоту повторов. Снижение повторов часто экономит батарею сильнее, чем ожидают, потому что повторные попытки держат радио активным дольше.
Шаг за шагом: уменьшение числа запросов через пакетирование
Болтливые API легко получить случайно. Каждый виджет грузит «свои» данные, и один экран вызывает дюжину мелких вызовов. Исправление обычно несложное: найдите то, что всегда загружается вместе, и возвращайте это в меньшем числе вызовов.
Начните с проекции одного загруженного экрана (главная, входящие, список заказов). Запишите, что видно выше сгиба, затем проследите каждый элемент UI до запроса, который его заполняет. Часто найдёте дубликаты (например, один и тот же профиль пользователя запрашивается дважды) и вызовы, которые постоянно идут вместе (профиль плюс права доступа плюс счётчик непрочитанных).
Далее сгруппируйте вызовы, которые надёжно происходят вместе. Обычно есть две опции:
- Создать endpoint, предназначенный для этого экрана (часто самый стабильный)
- Добавить batch‑endpoint, который принимает небольшой предсказуемый список ресурсов
Держите батчи ориентированными на экран и ограниченными, чтобы их было легко кэшировать, мониторить и отлаживать.
Пара правил помогут не довести пакетирование до хаоса. Возвращайте только то, что нужно экрану прямо сейчас, а не целые объекты «на всякий случай». Если какие‑то части опциональны, явно указывайте это, чтобы UI мог отрисовать важное быстро. Также проектируйте ответ так, чтобы частичные ошибки не заставляли делать полный повтор. Гораздо дешевле повторить только то, что не получилось, чем пересылать весь батч заново.
Заголовки кэширования, которые экономят батарею (а не только трафик)
Кэширование — это функция для батареи. Каждый запрос будит радио, нагружает CPU и вызывает парсинг и логику приложения. Хорошие кэш‑заголовки превращают многие обновления в лёгкие проверки.
Самый большой выигрыш — условные запросы. Вместо перезагрузки одинаковых данных приложение спрашивает «Это изменилось?» Если нет, сервер отвечает компактно: 304 Not Modified.
Используйте ETag в ответах, которые представляют версию ресурса, и пусть клиент отправляет If-None-Match при следующем получении. Если генерировать ETag сложно, Last-Modified с If-Modified-Since хорошо подходит для простых ресурсов с полем «обновлено в».
Для данных, которые редко меняются, сделайте решение о кэше явным. Cache-Control должен соответствовать реальности. Короткий max-age для профилей пользователей может быть уместен, тогда как конфиг приложения, справочники и флаги функций часто можно хранить дольше.
На стороне клиента обрабатывайте 304 как быстрый путь. Не парсите JSON (его нет), не перестраивайте модели и не мерцайте UI. Оставляйте то, что уже на экране, и просто обновляйте индикаторы.
Пример: экран отслеживания заказа опрашивает статус каждые 15 секунд. Если ответ включает ETag, большинство проверок могут возвращать 304. Приложение сохраняет последний статус и делает реальную работу только при его изменении (например, из «Собран» в «Отправлен»).
Будьте намеренны с чувствительными данными. Кэширование не всегда небезопасно, но требует чётких правил. Избегайте кэширования ответов с личными данными или токенами, если продукт этого не допускает. Для пользовательских данных используйте короткие сроки хранения и предотвращайте сохранение в общих кэширующих прокси (при необходимости используйте Cache-Control: private).
Умные полезные нагрузки: меньше отправлять, меньше парсить
Каждый лишний байт стоит мобильной батареи больше, чем просто трафик. Он дольше держит радио активным и заставляет приложение тратить CPU на декодирование JSON и обновление моделей. Если вы пытаетесь сократить болтливость API, обрезка полезной нагрузки часто даёт самый быстрый эффект.
Начните с аудита полезных нагрузок по экрану. Выберите один экран (например, ленту), зафиксируйте размеры ответов и посмотрите поле за полем: клиент отображает это поле или использует его для принятия решения? Если нет — уберите его из endpoint’а.
Для списков обычно достаточно «суммарной» формы. В строке списка часто нужен id, заголовок, статус и временная метка. Не нужны полные описания, длинные заметки или глубоко вложенные объекты. Загружайте детали только когда пользователь открывает элемент.
Несколько изменений часто быстро режут объём:
- Предпочитайте id или короткие enum‑коды вместо повторяющихся длинных строк
- Не повторяйте очевидные значения по умолчанию, которые клиент может безопасно предположить
- Избегайте повторного включения одного и того же вложенного объекта в каждом элементе списка
- Стабилизируйте имена полей и типы, чтобы клиент реже делал ветвления и повторный парсинг
Сжатие помогает, особенно на медленных сетях, но тестируйте CPU‑трейд‑офф на реальных устройствах. Gzip или Brotli часто сильно уменьшают объём передачи, но старые телефоны могут тратить заметное время на распаковку очень больших ответов. Лучший выигрыш — всё же отправлять меньше данных изначально.
Рассматривайте формы ответов как контракт. Когда имена полей и типы стабильны, приложению нужно меньше резервных ветвей и защитного кода, что помогает держать UI плавным и снижать расход батареи.
Проектирование на случай плохих сетей и меньше повторов
Мобильное приложение расходует батарею не только при отправке запросов. Оно также расходует её при ошибках, повторах, повторных пробуждениях радио и повторении работы. Если API предполагает идеальный Wi‑Fi, реальные пользователи на нестабильной 4G за это расплачиваются.
Упрощайте запросы за меньшими объёмами данных. Предпочитайте серверные фильтры и пагинацию вместо «скачать всё и отфильтровать на телефоне». Если экран нужен последние 20 событий пользователя, поддержите такой точный запрос, чтобы приложение не тянуло сотни строк, чтобы затем выбросить большую часть.
Поддерживайте дельта‑синхронизацию, чтобы клиент мог спросить «что изменилось с тех пор, как я в последний раз проверял?» Это может быть простая метка времени или увеличивающийся номер версии, после чего клиент запрашивает только обновления и удаления с того момента.
Повторы неизбежны, поэтому делайте обновления идемпотентными. Повтор не должен дважды списывать оплату, повторно отправлять форму или создавать дубликаты. Idempotency‑ключи для операций создания и семантика обновлений, устанавливающая состояние (вместо «добавь ещё один»), очень помогают.
Когда повторы происходят, избегайте «шторма» повторов. Используйте экспоненциальный бэкофф с джиттером, чтобы тысячи устройств не ударили по вашим серверам одновременно, и чтобы телефон не просыпался каждую секунду.
Возвращайте понятные коды ошибок, которые помогают клиенту принять решение. 401 должен инициировать повторную аутентификацию, 404 обычно останавливает повторы, 409 может требовать обновления состояния, а 429 или 503 — вызывать бэкофф.
Поведение клиента, которое умножает расходы API
Даже при хорошо спроектированном API клиент может тихо умножать сетевую работу. На мобильном дополнительные пробуждения и время работы радио часто стоят дороже самих байт.
Кэшируйте данные, которые редко меняются. Фото профиля, флаги функций и справочники (страны, статусы, категории) не должны запрашиваться при каждом заходе на экран. Давайте им разумный срок жизни, сохраняйте на диск и обновляйте только при необходимости. Если API поддерживает валидацию (ETag или Last-Modified), быстрая проверка часто дешевле полного скачивания.
Ещё одна частая проблема — дублирование параллельных запросов. Две части приложения запрашивают один и тот же ресурс одновременно (например, хедер и экран настроек оба тянут профиль). Без коалесценции отправляются два вызова, парсятся два ответа и состояние обновляется дважды. Считайте один сетевой вызов единственным источником правды и позвольте нескольким потребителям ждать его результата.
Фоновое обновление должно быть осознанным. Если оно не меняет того, что пользователь скоро увидит, или не вызовет уведомление, обычно его можно отложить. Избегайте запуска логики обновления при каждом возобновлении приложения. Добавьте короткий «холоддаун» и проверяйте, когда данные были обновлены в последний раз.
Если вы строите бэкенды с AppMaster, вам проще поддерживать такое поведение клиента, формируя endpoint’ы вокруг экранов, сохраняя стабильность схем ответов и добавляя кэш‑заголовки контролируемым образом, чтобы клиенты могли чаще молчать.
Частые ошибки с пакетированием, кэшированием и полезными нагрузками
Цель — меньше пробуждений радио, меньше работы CPU и меньше повторов. Несколько паттернов могут аннулировать экономию и даже замедлить экраны.
Когда пакетирование делает хуже
Пакетирование помогает до тех пор, пока батч не превращается в запрос «дам всё». Если серверу приходится джоинить множество таблиц, выполнять тяжёлые проверки прав и собирать огромный ответ, этот единый запрос может работать медленнее, чем несколько небольших. На мобильном один медленный запрос держит приложение в ожидании, держит сеть активной и увеличивает риск таймаута.
Более здоровый батч — это тот, что по форме экрана: только то, что нужно одному виду, с чёткими лимитами. Если вы не можете описать ответ одним предложением, endpoint, вероятно, слишком широк.
Кэширование, которое делает экраны устаревшими
Кэширование без ясной инвалидации ведёт к петле: пользователи видят старые данные, они делают pull‑to‑refresh, и приложение делает лишние полные перезагрузки. Используйте кэш‑заголовки с планом по тому, что обновляет данные (операции создания/обновления, серверные события или короткие окна свежести), чтобы клиент мог доверять закэшированным ответам.
Опрос — ещё одна ловушка для батареи. Жёсткий таймер в 5 секунд держит устройство занятым, даже когда ничего не меняется. Предпочитайте серверные уведомления, если возможно, или агрессивный бэкофф (удлинение интервалов после пустых опросов, приостановка в фоне).
Переизбыточные полезные нагрузки — тихая плата. Возвращение гигантских вложенных объектов «для удобства» означает больше байт, больше парсинга JSON и большую нагрузку на память. Отправляйте только то, что экрану нужно, и запрашивайте детали по требованию.
Наконец, не игнорируйте частичные ошибки в батче. Если один под‑результат падает, и вы повторяете весь батч, вы дублируете трафик. Проектируйте ответы так, чтобы клиент мог повторить только упавшие части.
Крат чек‑лист в голове: держите батчи ограниченными, заранее определяйте свежесть кэша, избегайте жёсткого опрашивания, обрезайте полезные нагрузки и поддерживайте частичный успех.
Быстрый чек‑лист перед релизом
Перед выпуском сделайте проход, сосредоточенный только на сетевом поведении. Большинство выигрышов для батареи приходят от устранения сюрпризов: меньше пробуждений, меньше парсинга и меньше повторов в фоне.
Протестируйте это на трёх ваших главных экранах:
- Холодные загрузки завершаются малым, предсказуемым числом запросов (следите за скрытыми последующими вызовами, например, per‑item lookups).
- Ответы включают ясные правила кэширования (
ETagилиLast-Modifiedтам, где это уместно) и возвращают304 Not Modified, когда ничего не изменилось. - Эндпоинты списков ограничены: стабильная сортировка, пагинация и отсутствие неиспользуемых полей по‑умолчанию.
- Логика повторов использует бэкофф с джиттером и останавливается при ошибках, которые не исправятся сами; общее время повторов ограничено.
- Фоновые обновления обоснованы; избегайте постоянного опрашивания, если это явно не меняет то, что видит пользователь.
Простой реальный тест: загрузите экран один раз, включите режим самолёта, затем снова откройте экран. Если он всё ещё показывает что‑то полезное (кэшированный контент, последнее известное состояние, дружелюбные плейсхолдеры), вы, вероятно, сократили лишние вызовы и улучшили воспринимаемую скорость.
Пример: как сделать экран отслеживания заказа дешевле при загрузке
Клиент открывает приложение отслеживания заказа по мобильной сети с 20% заряда. Им нужно одно: «Где сейчас моя посылка?» Экран кажется простым, но трафик за ним может быть дорогим.
Раньше приложение загружало экран взрывом запросов. UI ждал, пока радио снова и снова просыпается, и пара вызовов таймаутилась на слабом соединении.
Типичный «до»‑паттерн выглядел так:
GET /orders/{id}— сводка заказаGET /orders/{id}/items— позицииGET /orders/{id}/history— события статусаGET /me— профиль и настройки пользователяGET /settings— правила отображения (валюта, формат даты)
Теперь примените три изменения, не меняя сам UI.
Во‑первых, добавьте единый экранный endpoint, который возвращает только то, что виду нужно в одном круговом обходе: сводка заказа, текущий статус, недавняя история и заголовки позиций. Во‑вторых, кэшируйте профиль: GET /me возвращает ETag и Cache-Control: private, max-age=86400, поэтому большинство открытий превращаются в быстрый 304 (или в отсутствие запроса вообще, если кэш ещё свеж). В‑третьих, уменьшите полезную нагрузку: список позиций шлёт только id, name, qty и thumbnail_url, а не полные описания или неиспользуемую метадату.
Результат — меньше круг‑трипов, меньше байт и меньше повторов при дерганой сети. Радио телефона дольше остаётся в спящем состоянии, а это где реальные экономии по батарее.
Для пользователя ничего «нового» не появляется. Экран тот же, но загружается быстрее, кажется отзывчивее и продолжает работать даже при плохом соединении.
Следующие шаги: практический план развёртывания (и где AppMaster может помочь)
Если нужны быстрые выигрыши, начните с малого и докажите эффект. Экономия батареи обычно приходит от уменьшения числа пробуждений радио и парсинга, а не от большого рефакторинга.
Три изменения, которые безопасно внедрять, легко измерять и откатывать:
- Измерьте один экран end‑to‑end (число запросов, суммарные байты, время до интерактивности, ошибки и частота повторов)
- Сгруппируйте запросы этого экрана в один‑два вызова (сохраните старые endpoint’ы для совместимости)
- Добавьте поддержку
ETagна ваших самых нагруженных GET‑эндпоинтах, чтобы клиенты могли использоватьIf-None-Matchи получать304 Not Modified
Выберите функцию со стабильным использованием — например, список заказов или почтовый ящик. Выпускайте за флагом на стороне сервера, если возможно, и сравнивайте KPI для старого и нового пути в течение нескольких дней. Ищите уменьшение числа запросов на сессию, сокращение повторов и меньше байт на активного пользователя.
Координируйте релизы API и клиента, чтобы старые клиенты не ломались. Практическое правило: сначала добавьте новое поведение, затем мигрируйте клиентов, и только потом удаляйте старое поведение. При изменении кэширования будьте внимательны к персонализированным данным и следите, чтобы общие кэши не смешивали пользователей.
Если хотите прототипировать и выпускать изменения бэкенда быстрее, AppMaster (appmaster.io) поможет смоделировать данные визуально, строить бизнес‑логику перетаскиванием и регенерировать production‑ready исходный код по мере изменения требований.
Попробуйте один батч‑endpoint плюс ETag на одном высоконагруженном экране сначала. Если показатели улучшатся, вы точно будете знать, куда вложить дальнейшее инженерное время.
Вопросы и ответы
Хорошая отправная точка — задать бюджет на экран и измерять реальные сессии. Многие команды начинают с диапазона 4–8 запросов для холодной загрузки экрана по сотовой сети, а затем ужимают этот показатель, когда устраняют худших нарушителей. Правильное число — то, которое стабильно укладывается в ваш целевой показатель времени до интерактивности без повторных попыток и длительных периодов активности радио.
Часто пакетирование помогает, когда несколько вызовов всегда идут вместе. Но оно может навредить, если батч превращается в тяжёлый или медленный «дай‑мне‑всё» запрос. Держите ответ батча «по форме экрана» и ограниченным — один запрос не должен становиться точкой отказа. Если батч регулярно таймаутится или возвращает много неиспользуемых данных, лучше разделить его на несколько узконаправленных вызовов.
Начните с ETag и условных запросов через If-None-Match — это часто даёт наибольшую экономию, потому что многие обновления превращаются в маленькие ответы 304 Not Modified. Дополните Cache-Control, чтобы оно соответствовало реальной частоте изменений данных. Если реализация ETag затруднена, Last-Modified с If-Modified-Since — надёжная замена для ресурсов со временной меткой обновления.
Используйте ETag, когда нужно точное «версионное» сравнение ресурса, особенно если изменения не привязаны к временной метке. Last-Modified подойдёт, когда у ресурса есть ясное время обновления и вас устраивает точность уровня времени. Если можно реализовать только одно — ETag чаще даёт более точную защиту от ненужных скачиваний.
Инструментируйте по экрану и по сессии, а не только по эндпоинту. Логируйте число запросов, байты (заголовки плюс тело), повторы, таймауты и время до интерактивности, а также разделяйте передний план и фоновые операции, чтобы не оптимизировать не ту нагрузку. Обычно вы обнаружите, что несколько экранов или сценариев создают большинство повторных «пробуждений» радио.
Формируйте ответ батча так, чтобы каждый под‑результат мог завершиться независимо: включите детали ошибок для отдельной подзадачи, чтобы клиент мог повторить только то, что провалилось. Избегайте принуждения клиента к повторному запросу всего батча из‑за одной неудачи — это снижает лишний трафик и дополнительные пробуждения радио в нестабильной сети.
Обрежьте ответы до того, что экран действительно рендерит в данный момент, и используйте «суммарную» форму для списков. Перенесите тяжёлые или редко используемые поля в детальный endpoint, который загружается только при открытии элемента. Это уменьшит объём передаваемых байтов и снизит парсинг JSON и обновления моделей, что реально экономит CPU и заряд у мобильных устройств.
Используйте экспоненциальную задержку с джиттером и лимитируйте общее окно повторов, чтобы телефон не просыпался каждые несколько секунд. Сделайте операции записи идемпотентными, чтобы повторы не создавали дубликаты или лишние действия. Также возвращайте понятные коды состояния, чтобы клиент знал, когда стоит прекратить попытки (например, ошибки, которые не исправятся сами по себе).
Частое опрашивание держит радио и CPU занятыми даже когда ничего не меняется. Если опрос необходим, увеличивайте интервалы при отсутствии изменений и приостанавливайте опрос в фоне. По возможности переходите на событийные обновления, чтобы приложение просыпалось только при появлении чего‑то нового.
В AppMaster вы можете быстро создать эндпоинты, ориентированные на экран, и поддерживать согласованные схемы ответов, что упрощает батчирование и формирование полезных нагрузок. Также можно добавить логику версионирования и возвращать заголовки для условных запросов, чтобы клиенты получали быстрые ответы «без изменений». Практичный путь — начать с одного часто используемого экрана: выпустить один батч‑эндпоинт и добавить ETag на ключевые GET‑запросы, затем измерить снижение числа запросов и повторов.


