Оптимизация мобильных приложений: скорость и память без компромиссов

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

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

Решение начинается с ясной картины: что считать быстрым, какие бюджетные рамки у кадра и старта, где накапливаются хвосты и почему лужа микролагов превращается в океан оттока. Производительность — не набор трюков, а целостная дисциплина: от архитектуры и потоков до форматов данных, от кэширования и GC/ARC до привычек рисовать слои так, чтобы GPU не плавился под ними, как свеча под прожектором.

Что означает «быстрое» мобильное приложение на практике

Быстрым называют приложение, где касание превращается в действие без ощутимой паузы, а интерфейс держит частоту 60/120 FPS без заиканий, экономя память и батарею. Это измеримо: бюджет кадра, время старта, доля jank и стабильность RSS образуют объективный портрет скорости.

Здесь важно уйти от расплывчатых формулировок и перейти к порогам восприятия. Для холодного старта приемлемым считается укладываться в 1,5 секунды на среднем устройстве, для тёплого — около 700 миллисекунд; первый контентный отрисованный пиксель должен появляться раньше, чем взгляд пользователя начинает скользить по краям, выискивая подвох. Прокрутка обязана быть предсказуемой: при 60 Гц бюджет кадра — 16,6 мс, при 120 Гц — 8,3 мс, и каждый лишний миллисекундный шип разбрасывает песок под колёса. Память — не свалка, а стройплощадка: утечки и фрагментация накапливаются незаметно и внезапно превращают простую операцию в бездонный колодец пауз из-за GC или page fault. Энергопрофиль — последняя инстанция доверия: чрезмерные wake‑ups и частые сетевые пики оставляют после себя горячий корпус и холодное отношение к продукту.

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

  • Холодный старт: до первого интерактивного экрана — 1,2–1,6 с (средний девайс); тёплый — 400–800 мс.
  • Плавность прокрутки: средний FPS 60/120; jank < 1% кадров; 99‑й перцентиль длительности кадра < 24 мс при 60 Гц.
  • Память: стабильный RSS без ползучего роста; отсутствие частых принудительных сборок (GC)/утечек (ARC/retain cycles).
  • Сеть: медиана ответа критичных API < 200–300 мс на 4G/Wi‑Fi; полезная нагрузка компактна, кэш актуален.
  • Энергия: фоновая активность сдержанна; отсутствие избыточных будильников и долгих удержаний CPU/GPU.

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

Действие Комфортная задержка Отлично Критично плохо
Отклик на тап (визуальная реакция) < 100 мс ~50 мс > 200 мс
Холодный старт до интерактива 1,5 с ≤ 1,0 с > 2,5 с
Прокрутка (60 Гц) jank < 1% ~0,3% > 3%
Загрузка списка TTFR ≤ 600 мс ≤ 350 мс > 1 с

Где теряется время: рендеринг, ввод‑вывод, сеть, GC

Время уплывает там, где логика встречает железо: в отрисовке, операциях диска, сети и сборке мусора. Каждое звено добавляет крошечные задержки, и они складываются в заметную паузу.

Карта задержек выглядит как срез города в час пик: главный поток перегружен разметкой и измерением, соседние — застревают в ожидании диска, а над ними летит тяжелая сеть, разбрасывая пачки JSON. В такие моменты GC/ARC действует как полицейский, останавливая движение ради уборки проезжей части. Расшить этот узел удаётся, когда каждый компонент получает свою очередь и свой лимит, а данные переезжают компактно и вовремя. Рендеринг любит предсказуемость; ввод‑вывод — крупные батчи и индексы; сеть — краткость и кэш; сборщик мусора — редкие и осмысленные выделения. Там, где эта дисциплина нарушена, кадр трескается, как лёд под каблуком.

Как измерять задержки рендеринга без самообмана

Верная оценка рендеринга — это кадр в лупе, а не общий взгляд на FPS. Правильно измерять нагрузку каждого кадра, просматривать таймлайны и отделять работу CPU от GPU.

Рассмотрение рендеринга через призму бюджета кадра выявляет настоящие узкие места: долгие measure/layout, избыточные инвалидации, сложные тени и размытия, дорогие шейдеры, перерасчёт текста. На Android полезно включать визуализацию overdraw: там, где цвет становится багровым, кадры тают. Излюбленный виновник — глубокие иерархии, когда каждый перерисовывает соседа; помощь приходит из мира диффинг‑алгоритмов: ListAdapter/RecyclerView с DiffUtil экономит обновления, а на iOS — UITableView/UICollectionView с DiffableDataSource разгружает apply‑операции. Пре‑растрированные слои, отключение непрозрачных эффектов, разумное использование Rounded Corners и тени, кэширование измерений текста — это не банальные советы, а образ жизни UI, который хочет жить на 8,3–16,6 мс и не краснеть. Важна и дисциплина главного потока: никакой синхронной декодировки изображений и блокирующих дисковых вызовов рядом с Core Animation.

Ввод‑вывод и базы данных: где тонко — там рвётся

Диск медленнеe памяти на порядки, и каждое случайное чтение в разогретом UI слышно, как щелчок выключателя в тишине. Лекарство — последовательный доступ, батчи, индексы и фоновые очереди.

Чтение мелкими порциями порождает N+1 запросов: адаптер стучится в БД за каждым элементом, а GC собирает крошки строк. На Android Room и Cursor с правильными индексами и ограниченным набором колонок быстро приучают таблицы к смирению; на iOS Core Data с NSFetchedResultsController отрабатывает дифф и секции без судорог при прокрутке. Праймер к стабильности — write‑ahead logging (WAL) и транзакции для батч‑вставок, предзагрузка страниц и бережное использование mmap для больших блобов. Важно уводить все операции на диспетчеры IO/Background: никакой блокировки Looper/RunLoop. Там, где считывание всё же необходимо в реальном времени, помогают кеши второго уровня, заранее подогретые ключевыми наборами, и протоколы передачи, где подгружается не текстовый мешок, а компактная структура.

Операция Типичный «вес» Оптимизация Комментарий
Случайное чтение небольшого файла 0,2–2 мс Группировка запросов Свести мелкие чтения к последовательным
Запись в БД одной строки 2–10 мс WAL + батчи Вставлять пачками по 50–200
N+1 выборка в списке 10–200 мс+ JOIN/предзагрузка Заменять каскад — единым запросом
Декодирование крупного изображения 5–40 мс Бэкграунд + downsample Точечное масштабирование под вид

Сеть: экономия миллисекунд и мегабайт

Сеть выигрывает там, где меньше ждут рукопожатий и меньше носят лишнего. Помогают HTTP/2/3, кэш, сжатие и компактные форматы данных.

Распухший JSON на 500 полей слазит в память, как чемодан без колёс по лестнице: и тащить тяжело, и бросить жалко. Если протокол стабилен, Protobuf и FlatBuffers режут байты и ускоряют парсинг; если нет, JSON приводят к компактности: убирают болтовню, сокращают ключи, включают Gzip/Brotli и добавляют ETag/If‑None‑Match. Становится тише, когда клиенты уважают Cache‑Control и понимают, что 304 — лучший друг скорости. DNS‑кеширование, TLS session resumption/0‑RTT и объединение запросов в один батч снижают накладные расходы рукопожатий. Изображения живут по своим законам: WebP/AVIF/HEIF дают тонкую грань между качеством и размером, а адаптивные размеры для сетки карточек снимают вопрос в корне. С CDN стоит держать короткий путь и умные заголовки, иначе половина оптимизаций пропадёт вглядыванием в облака.

  • Группировать мелкие API‑вызовы, удерживать соединения живыми (HTTP/2/3).
  • Включать кэш и валидацию (ETag, Last‑Modified), отдавать 304 там, где это возможно.
  • Сжимать полезную нагрузку, выбирать компактные форматы (Protobuf/FlatBuffers для стабильных контрактов).
  • Использовать адаптивные размеры изображений и современные форматы (WebP/AVIF/HEIF).

Память под контролем: утечки, фрагментация, кэши

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

В ежедневной работе чаще всего подводят простые вещи: забытые слушатели, длинно живущие синглтоны, удержание контекстов активити, циклы удержаний на iOS из-за сильных ссылок в замыканиях и делегатах. Android охотнее всего «протекает» через View/Adapter, статики, неосвобождённые Bitmap/Surface/Texture; iOS — через ретейн‑циклы, KVO и таймеры. Лечение — слабые ссылки там, где нет владения, аккуратные жизненные циклы экранов, auto‑cancel для наблюдателей, своевременная очистка больших объектов из полей. Фрагментация и всплески аллокаций лечатся реюзом буферов и пулов, предсказуемым профилем объектов, отказом от порождения тысячи крошечных строк в пользу компактных структур. Кэш — инструмент, а не склад: LRU помогает на горячих путях, но только с чётким бюджетом и политикой инвалидации, иначе кэш начинает бороться с системой за каждую страницу памяти.

Утечки и жизненный цикл экранов

Утечка — это объект, который уже не нужен, но цепляется за жизнь чужими ссылками. В UI её рождают слушатели, таймеры и долгие ссылки на контекст экрана.

Решение строится вокруг явной ответственности: экран уходит — наблюдатели удаляются, дисплеи и битмапы освобождаются, корутины отменяются. На Android помогает LeakCanary в связке с правилами владения контекстом (избегать Activity в долгоживущих компонентах, хранить ApplicationContext там, где допустимо). На iOS — внимательная работа со слабым/неудерживаемым (weak/unowned) в замыканиях и делегатах, и проверка Allocations+Leaks в Instruments после каждой крупной фичи. Важно помнить о скрытых владельцах: Rx/Combine‑цепочки, наблюдатели за NotificationCenter, дисплеи CADisplayLink — все они норовят удержать последний экран, как якорь удерживает лодку в штиле.

Кэш как инструмент, а не склад

Правильный кэш ускоряет частые операции и экономит сеть; неправильный — крадёт память и вредит актуальности данных. Баланс достигается бюджетом, стратегией и метриками промахов.

Предметный кэш строит политику из реального доступа: горячие ключи получают приоритет, холодные утекают без сожаления. Стоит считать «стоимость» элемента — пиксели и миллисекунды декодирования, а не просто количество. Слои кэшей — память, диск, сеть — уважают валидность и таймауты, а инвалидация происходит не от ощущения, а от сигналов данных (версионирование ресурсов, ETag). Подогрев кэша заранее под первый экран снижает TTI, но требует предсказуемого сценария. Рабочая практика — держать мониторинг hit/miss, размеров и времени жизни. Там, где кэш мешает отладке, включается прозрачный режим: сбор метрик без кеширования, чтобы увидеть цену истинных вызовов.

Стратегия кэша Плюсы Минусы Когда применять
LRU (по недавности) Простая, предсказуемая Не учитывает «стоимость» Стабильные горячие ключи
Cost‑based LRU Справедливо выкидывает «дорогие» Сложнее оценка стоимости Изображения, шрифты, раскладка
SLRU/2Q Защищает частые ключи Сложнее реализация Смешанные нагрузки
Write‑through диск/память Данные согласованы Накладные на запись Критичная к консистентности кешируемая БД

Архитектура и асинхронность: как не застрять в главном потоке

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

Хорошая архитектура отдаёт UI только то, что нужно для отрисовки, и уводит остальное в фон: преобразование данных, декодирование, сериализацию. На Android корутины и Dispatchers.IO/Default, WorkManager для фоновых работ, StrictMode как сигнализация. На iOS — GCD (QoS), NSOperationQueue, structured concurrency (async/await), приоритеты и отмена. Главный поток не тянет сеть, не парсит JSON, не трогает диск; его задача — провести касание через слой презентации к реакции без лишних пересечений. Иммутабельные модели и единый источник правды не позволяют гонкам переписать результаты, а «хрупкие» блокировки заменяются акторной моделью или очередями с чётким порядком.

  • Запрещать синхронный ввод‑вывод из UI; StrictMode/OSSignpost как светофор.
  • Назначать QoS/приоритеты и избегать инверсии приоритетов.
  • Проектировать отмену и таймауты как часть контракта, а не «после».
  • Ограничивать параллелизм: много потоков без структуры — новый вид блокировки.

Очереди задач и приоритеты

Приоритеты — це́на времени. Дорогие задачи должны уступать кадру; дешёвые — сливаться в серии, чтобы не дробить расписание.

В реальности всё упирается в управление давлением: фоновые операции не имеют права влезать в UI‑окно. На iOS QoS.userInteractive/utility задают рамки; на Android корутины с приоритетами и ограниченными пулами не позволяют IO‑шторму засыпать систему. Инверсии приоритетов лечатся отказом от глобальных мьютексов, сужением критических секций и передачей данных по каналам/акторам. Там, где очередей много, полезно сегментировать их по доменам (сеть, декод, БД) и держать агрегирующий планировщик. Отмена обязательна: контент больше не нужен — цепочка гасится на корню, а не дотягивается «из вежливости».

Распараллеливание без гонок и блокировок

Параллелизм ускоряет только то, что не спорит за ресурсы. Иммутабельные структуры, акторы и чёткие границы секций снимают напряжение и убирают гонки.

Опыт подсказывает: как только появляется общий изменяемый объект, вокруг него выстраивается очередь желающих. Лечение простое в теории и сложное в быту: не делиться состоянием, а делиться сообщениями; не блокировать, а упорядочивать. В экосистемах есть инструменты: Kotlin Flow/Channels, Swift actors/AsyncSequence. Там, где без разделяемого не обойтись (кеши, пулы), помогают lock‑free структуры или узкие мьютексы со строгими SLA на удержание. Дополняют картину таймауты и backpressure: источник не должен заливать потребителя, UI — всегда конечная труба. Такой подход держит ритм, когда контента много, а кадр по‑прежнему всего один.

Графика и анимация: 60/120 FPS как норма, а не удача

Стабильный FPS — это дисциплина кадра, где работа распределена между CPU и GPU, а дерево слоёв простое и предсказуемое. Оптимизация — в уменьшении перерисовок и стоимости каждого кадра.

Планирование кадра напоминает упаковку чемодана: всё лишнее останется на полу. Упрощение иерархии, отказ от невидимых слоёв, предрасчёт layout для тяжёлых карточек, кэширование измерений текста и картинок дают прозрачный бюджет. На Android полезно избегать избыточных invalidate() и плотных теней; на iOS — минимизировать offscreen‑рендеринг (маски, круговые изображения без лишних слоёв), выбирать CABasicAnimation/UIViewPropertyAnimator вместо тяжёлых кастомных анимаций там, где можно. Листы и карусели живут счастливо с предвыделением ViewHolder/Prefetch (RecyclerView) и предварительным снапшотом моделей. Изображения декодируются и масштабируются на фоне; повторное использование буферов, векторные иконки вместо растровых при мелких размерах — всё это — милости для GPU. Сюрпризы производительности часто прячутся в шейдерах и текстовых операциях: кэш скомпилированных шрифтов, ограничение fallback‑наборов и продуманное кернинг‑поведение гасят «зубцы» таймлайна.

Проблема рендеринга Признак Решение
Overdraw Цветовые подсказки показывают 3–4+ слоя Уплощать иерархию, убирать невидимые фоны
Offscreen‑рендеринг Core Animation показывает длинные участки Избегать масок/теней; использовать круглые слои напрямую
Дорогой текст Пики при измерении/отрисовке Кэширование layout, ограничение шрифтов
Декод картинок на UI Синхронные блокировки главного потока Фоновые очереди, подготовка размеров

Экономия батареи как часть производительности

Скорость, купленная ценой батареи, — проигрыш. Энергоэффективность достигается батчингом задач, уважением к снам системы и снижением будильников и радио‑прыжков.

Энергия уходит в простые вещи: лишние wake‑ups, частые запросы сети, тяжёлые сенсоры, держатели экрана и GPS. Там, где данные не критичны к секундам, задачи собираются в окна, уведомления идут через пуши, а не через опрос, локация берётся по «значимым изменениям», а не по постоянной записи. На Android Doze/App Standby диктуют правила игры — фоновым сервисам остаётся WorkManager с окнами; на iOS — BackgroundTasks и осмысленные разрешения. Изображения и видео лучше отдавать адаптивно, а не «на все деньги пикселей» радиомодуля. Инженерная забота о батарее возвращается в виде более холодного устройства и тёплых отзывов.

  • Когортировать фоновые работы в окна, уважать Doze/BackgroundTasks.
  • Переходить на push вместо опроса там, где это возможно.
  • Снижать частоту сенсоров и локации, использовать значимые изменения.
  • Минимизировать сетевые чаты: повторные коннекты, keep‑alive, сжатие.

Инструменты профилирования: от лаборатории к полю

Профилировщик — фонарик в тёмной комнате. Он показывает, где именно теряется кадр и память, а также подтверждает эффект правок цифрами, а не ощущениями.

В лаборатории полезно чередовать микробенчмарки и сценарные прогоны. Android даёт Perfetto/Systrace и Macrobenchmark для старта/прокрутки, Memory Profiler для отслеживания аллокаций и пауз GC, LeakCanary для утечек, StrictMode/Network Profiler для дисциплины. iOS вооружает Instruments: Time Profiler для горячих путей, Allocations/Leaks для памяти, Core Animation для кадров, Energy Log для батареи, Network — для пакетов и кеша; os_signpost связывает логику и таймлайны. Полевые метрики от статистики ANR/Crash, frame metrics API и перцентилей времени ответа дополняют лабораторию реальностью. Вместе они создают ритм: исходная боль — гипотеза — замер — правка — повторная валидация.

Платформа Инструмент Задача Что смотреть
Android Perfetto / Systrace Кадры, старты FrameTimeline, блокировки, длинные секции
Android Memory Profiler / LeakCanary Аллокации, утечки Пики GC, удерживающие пути
Android Macrobenchmark Старт, прокрутка Перцентили времени, стабильность
iOS Time Profiler Горячие функции Стек вызовов, «жирные» методы
iOS Allocations / Leaks Память, retain cycles Рост heap, объекты‑долгожители
iOS Core Animation / Energy Log FPS, батарея Jank‑участки, offscreen, wake‑ups

Практический чек‑лист оптимизации релиза

Чек‑лист превращает слепое ощущение «медленно» в серию конкретных действий. Его сила — в повторяемости и привязке к метрикам.

  1. Задать целевые SLA: холодный/тёплый старт, jank, пределы RSS, перцентили API.
  2. Инструментировать ключевые пути: осмысленные метки времени, os_signpost/Trace.
  3. Профилировать старты и первые экраны: срезка инициализаций, ленивые модули, deferred work.
  4. Оптимизировать рендеринг списков: диффинг, prefetch, фоновые декоды, уплощение слоёв.
  5. Пересобрать сеть: кэш, сжатие, компактные протоколы, батчинг, адаптивные изображения.
  6. Выявить и закрыть утечки: LeakCanary/Instruments, авто‑очистка, отмена задач.
  7. Пробежать энерго‑профиль: батчинг, отказ от опроса, сенсоры по требованию.
  8. Поставить регрессионные тесты производительности и алерты по метрикам в проде.

FAQ: частые вопросы по скорости и памяти

Как уменьшить время холодного старта Android‑приложения?

Короткий ответ: выносить тяжёлую инициализацию из критического пути, сокращать работу в Application.onCreate и откладывать ненужные модули.

Практика показывает, что старты выигрывают от ленивых DI‑графов, deferred init SDK‑модулей, предварительно скомпилированных ресурсов (AOT, Baseline Profiles), инициализации логгирования/аналитики в фоновом потоке и переноса прогревов в экран‑сплэш с асинхронным ожиданием. Macrobenchmark измеряет 50‑й/90‑й перцентили старта, а Perfetto раскрывает «жирные» зоны: main thread blocked, class loading, disk IO. Resource shrinking и включение R8/ProGuard убирают балласт, а лёгкий первый экран с минимальными зависимостями подаёт интерактив быстрее.

Что делать с «тяжёлыми» списками и мерцанием при прокрутке?

Ответ: использовать диффинг, префетч и фоновую подготовку контента, убирая синхронные операции с главного потока.

RecyclerView с ListAdapter/DiffUtil и выключенной анимацией там, где она не нужна, минимизирует перерисовки. Подготовка ViewHolder и предзагрузка следующей порции данных сглаживают прокрутку. Изображения декодируются на IO‑очередях с downsample до целевого размера; сложные тени и размытия заменяются на предрендеренные или упрощённые. Лэйауты именуются и кэшируются; сеть не дергается за каждым пикселем, а поставляет батчи. На iOS схожую роль играет DiffableDataSource и prefetchDataSource; CA инструменты помогают увидеть «красные» участки.

Как отловить утечки памяти, которые появляются только у части пользователей?

Ответ: включить детекторы утечек в препродакшн/интернал‑сборках и собирать heap‑срезы/симптомы в телеметрии.

LeakCanary с отчётами на внутренних билдах подсвечивает удерживающие пути; в продакшне стоит ограничиться сигналами: ростом RSS по сессиям, частотой OOM, паузами GC и событиями закрытия экранов без освобождения. На iOS Instruments хорош для LAB‑проверки, а в поле помогает логирование точек жизни объектов и ручные «маячки». Критично воспроизводить сценарии: повороты экрана, быстрые переходы, отложенные таймеры, цепочки подписок — именно там зарыты редкие утечки.

JSON или Protobuf: что быстрее в мобильном клиенте?

Если контракт стабилен и размер важен, Protobuf быстрее и компактнее. JSON удобнее в эволюции схем и отладке, но требует сжатия и чистоты полей.

Реальные замеры дают 1,5–5‑кратный выигрыш Protobuf по времени парсинга и байтам, особенно на больших массивах. Однако эволюция схем Protobuf требует дисциплины версионирования; JSON выигрывает простотой и прозрачностью, что критично там, где контракты часто меняются. Гибридный подход встречается чаще: критичные и тяжёлые эндпоинты — бинарные, остальное — JSON с gzip/brotli и аккуратной схемой.

Почему анимации плавные на флагманах и дёргаются на бюджетных устройствах?

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

Анимации страдают от тех же причин: offscreen‑рендеринг, переразметка, декод на UI, сложные шейдеры. На устройствах без избыточной мощности каждый такой фактор виден невооружённым глазом. В лаборатории стоит держать «средний» девайс для референса, измерять 99‑й перцентиль длительности кадров и вычищать тяжелые участки до зелёной зоны. Понижение качества эффекта ради стабильности кадра — взрослая инженерная позиция.

Сколько кэша хранить на устройстве и как его ограничить?

Столько, сколько оправдывает hit‑rate и не провоцирует давление на память. Бюджет кэша задаётся процентом от свободной памяти и фиксированной «крышкой».

Хорошая практика — динамический бюджет: верхний предел (например, 100–200 МБ для изображений), плюс доля от свободной памяти. Политика — LRU/стоимостная; очистка — по LRU и при сигналах системы о давлении. Метрики — hit/miss, средний размер элемента, TTL и доля избыточных инвалидов. Критично не копить «мёртвые души» и вовремя сбрасывать кэш при смене пользователя/версии данных.

Стоит ли форсировать 120 Гц и зачем?

Форсировать — нет; уважать — да. Если устройство в 120 Гц, кадр должен укладываться в 8,3 мс, иначе система сама снизит частоту и плавность пропадёт.

Инженерный смысл — в стабильности: лучше честные 60 Гц без jank, чем нестабильные 120. При этом анимации и прокрутка выигрывают от высокой частоты там, где бюджет выдержан. Поэтому оптимизация идёт по тем же правилам, что и при 60 Гц, только допуска меньше. На Android можно управлять предпочтением частоты на уровне окна; на iOS система сама балансирует, исходя из нагрузки.

Финальный аккорд: скорость как качество мышления

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

В этой картине особенно ценны привычки: замер перед действием, подтверждение эффектов метриками, регрессионные тесты и внимательность к полю, где истинные пользователи и настоящие устройства. Не зрелищные трюки, а повторяемые ремёсленные приёмы двигают продукт из «терпимо» в «легко и приятно».

How To:

  1. Определить цели: TTFB/TTI, FPS, jank, RSS, энерго‑SLA для ключевых сценариев.
  2. Вшить трассировку ключевых путей: старты, первый экран, прокрутка, загрузки.
  3. Снять профили: Perfetto/Instruments, выявить топ‑3 узких места по времени/памяти.
  4. Исправить одно узкое место за цикл: увести IO с UI, упростить слой, сжать полезную нагрузку.
  5. Проверить цифрами: перцентили улучшились — закрепить тестом и алертом.
  6. Раскатывать поэтапно, наблюдать полевые метрики, фиксировать регрессии до широкой выкладки.