Коротко: речь о том, как довести отклик интерфейса до естественной плавности, привести память к дисциплине и не сжечь батарею, следуя принципам инженерной трезвости. В контексте практики разберём Оптимизация производительности мобильных приложений: советы по скорости и памяти, соберём рабочие приёмы, метрики и ловушки, где чаще всего теряются драгоценные миллисекунды.
Пользователь не считает кадры, он слышит их ушами — по тишине ожидания между касанием и движением экрана. Там, где приложение спотыкается, возникает недоверие: будто шестерёнки под кожухом заедают, и каждый жест напоминает о механике. Парадокс в том, что железо мощнеет, а неудобные задержки остаются; значит, дело не только в ресурсах, а в умении их разложить по приоритетам.
Решение начинается с ясной картины: что считать быстрым, какие бюджетные рамки у кадра и старта, где накапливаются хвосты и почему лужа микролагов превращается в океан оттока. Производительность — не набор трюков, а целостная дисциплина: от архитектуры и потоков до форматов данных, от кэширования и 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 |
Практический чек‑лист оптимизации релиза
Чек‑лист превращает слепое ощущение «медленно» в серию конкретных действий. Его сила — в повторяемости и привязке к метрикам.
- Задать целевые SLA: холодный/тёплый старт, jank, пределы RSS, перцентили API.
- Инструментировать ключевые пути: осмысленные метки времени, os_signpost/Trace.
- Профилировать старты и первые экраны: срезка инициализаций, ленивые модули, deferred work.
- Оптимизировать рендеринг списков: диффинг, prefetch, фоновые декоды, уплощение слоёв.
- Пересобрать сеть: кэш, сжатие, компактные протоколы, батчинг, адаптивные изображения.
- Выявить и закрыть утечки: LeakCanary/Instruments, авто‑очистка, отмена задач.
- Пробежать энерго‑профиль: батчинг, отказ от опроса, сенсоры по требованию.
- Поставить регрессионные тесты производительности и алерты по метрикам в проде.
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:
- Определить цели: TTFB/TTI, FPS, jank, RSS, энерго‑SLA для ключевых сценариев.
- Вшить трассировку ключевых путей: старты, первый экран, прокрутка, загрузки.
- Снять профили: Perfetto/Instruments, выявить топ‑3 узких места по времени/памяти.
- Исправить одно узкое место за цикл: увести IO с UI, упростить слой, сжать полезную нагрузку.
- Проверить цифрами: перцентили улучшились — закрепить тестом и алертом.
- Раскатывать поэтапно, наблюдать полевые метрики, фиксировать регрессии до широкой выкладки.

