Оптимізація швидкості рендерингу UI мобільного додатку

Одного разу наш клієнт зіткнувся з проблемою: на iPhone 13 додаток показував 58–60 FPS на більшості екранів, але один екран з кастомним UICollectionViewLayout стабільно просідав до 42–45 FPS при скролі. Ми запустили Instruments і виявили, що 14 мс з 16 доступних йшло на layoutAttributesForElementsIn

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1734 послуг
Оптимізація швидкості рендерингу UI мобільного додатку
Складний
~3-5 днів

Наші компетенції:

Часті запитання

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    894
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    782
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1216
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    1079
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    1002
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    597

Одного разу наш клієнт зіткнувся з проблемою: на iPhone 13 додаток показував 58–60 FPS на більшості екранів, але один екран з кастомним UICollectionViewLayout стабільно просідав до 42–45 FPS при скролі. Ми запустили Instruments і виявили, що 14 мс з 16 доступних йшло на layoutAttributesForElementsInRect — метод перераховував всі позиції комірок без кешування. Це класика: UI-рендеринг не гальмує «взагалі», він гальмує в конкретному місці з конкретної причини. Після впровадження простого кешу FPS повернувся до 60, і клієнт отримав плавний інтерфейс за один день роботи. Оптимізація швидкості рендерингу UI — це задача, що вимагає системного підходу.

Гальма рендерингу — одна з найпідступніших проблем, тому що вони видимі користувачеві негайно, але діагностуються довго. FPS-метрика каже «погано», а де саме — потрібно розкопувати. Ми працюємо з даними, а не з здогадками.

Чому main thread блокує рендеринг?

Золоте правило — 16 мс на кадр (60 FPS) або 8 мс (120 Hz на Pro-пристроях). Все, що виконується на main thread понад це, блокує рендер. Типові винуватці:

На iOS: синхронна робота з CoreData через viewContext прямо в cellForItemAt, декодування UIImage без preparingForDisplay(), NSAttributedString з обчисленням розміру в sizeForItemAt без кешу.

На Android: блокуючий I/O в onBindViewHolder, Bitmap.decodeResource() на main thread, важкі Drawable анімації через AnimationDrawable на бюджетних пристроях з Mali GPU.

Окремо стоїть проблема з measure/layout pass. На Android Jetpack Compose ConstraintLayout всередині LazyColumn з глибокою вкладеністю запускає два повних проходи measure на кожну комірку. На складному списку з 50+ елементами це помітно навіть на Pixel 7.

Як діагностувати проблеми рендерингу?

Діагностику починаємо з профілювання на реальному пристрої. На iOS використовуємо Xcode Instruments (шаблон Core Animation), на Android — GPU Inspector або Android Studio Profiler. Для Flutter — flutter run --profile з DevTools Performance overlay. Збираємо baseline: середній FPS, кількість janky frames, frame time distribution. Якщо середній FPS нижче 55 — шукаємо точки блокування.

Чому main thread — головний ворог плавності? — оптимізація швидкості рендерингу

Кожна операція на main thread понад 16 мс (або 8 мс для 120 Hz) викликає пропуск кадру. Наприклад, виклик sd_setImageWithURL: без прапорця SDWebImageAvoidAutoSetImage жере 8 мс на main thread — ми це бачили на проекті, де FPS впав з 55 до 47. Заміна на правильну опцію повернула плавність.

Що таке overdraw і як його знайти?

Overdraw — коли один піксель малюється кілька разів за кадр. На Android вмикається через «Developer Options → Show GPU Overdraw»: синій — 1×, зелений — 2×, рожевий — 3×, червоний — 4×+. Червоний екран на бюджетному Xiaomi з Adreno 610 — гарантований jank.

Часта причина — вкладені ViewGroup з непрозорими фонами, де кожен шар малює фон поверх попереднього. На iOS аналог — CALayer з opaque = false там, де прозорість не потрібна, або shouldRasterize без явного rasterizationScale.

Оптимізація швидкості рендерингу UI: покроковий план

Що входить в роботу

  • Аудит зі звітом: скріншоти профілів, список проблемних місць, baseline-метрики.
  • Правки в коді з коментарями для розробників.
  • Рекомендації щодо архітектури та інструментів для довгострокового підтримання FPS.
  • Моніторинг — інтеграція Firebase Performance або власного FPS-монітора.
  • Гарантія на досягнення цільового FPS протягом 30 днів після здачі.

Як ми це робимо: конкретний кейс

Типовий сценарій на iOS-проекті: клієнт скаржиться на «гальма в стрічці». Відкриваємо Time Profiler, записуємо скрол 5 секунд. В call tree одразу видно: [SDWebImage sd_setImageWithURL:] жере 8 мс на main thread, тому що хтось прибрав options:SDWebImageAvoidAutoSetImage і зображення застосовуються синхронно після завантаження. Один прапорець — і FPS виріс з 47 до 59.

На Android був кейс з RecyclerView + DiffUtil: розробник викликав submitList() з ViewModel, але DiffUtil працював на main thread (використовувався ListAdapter без AsyncListDiffer). На списку з 200 елементів diff займав ~18 мс. Ми перевели обчислення diff на фоновий потік через AsyncListDiffer — проблема зникла. AsyncListDiffer в 3 рази швидший за синхронний DiffUtil на списках з 500 елементів.

Конкретні інструменти та техніки

iOS:

  • CADisplayLink + кастомний FPS-монітор у debug-збірці для постійного моніторингу
  • UIView.setNeedsLayout() vs UIView.layoutIfNeeded() — розуміння різниці критичне при анімаціях
  • drawRect: майже завжди замінюємо на CALayer sublayers — Core Animation рендерить їх на GPU без участі CPU
  • UIGraphicsImageRenderer замість застарілого UIGraphicsBeginImageContextWithOptions для offscreen rendering
  • Prefetching через UICollectionViewDataSourcePrefetching — декодуємо зображення до того, як комірка з'явиться на екрані

Android / Compose:

  • Modifier.graphicsLayer {} для апаратного прискорення трансформацій замість програмного
  • remember {} та derivedStateOf {} — запобігають зайвим рекомпозиціям
  • key() в LazyColumn — без нього Compose не може перевикористовувати ноди при зміні списку
  • Bitmap.Config.RGB_565 замість ARGB_8888 там, де альфа-канал не потрібен — вдвічі менше пам'яті GPU

Flutter:

  • RepaintBoundary навколо віджетів, які часто перемальовуються незалежно
  • const конструктори — віджет не перестворюється при rebuild батька
  • flutter run --profile + DevTools → Performance overlay — обов'язковий інструмент перед релізом

Кейс з нашої практики: 120 Hz на iPad Pro

Наш клієнт зробив кастомну анімацію через UIViewPropertyAnimator з preferredFrameRateRange. Анімація працювала на 60 FPS замість 120. Виявилося — один CALayer з shouldRasterize = true без явного вказання rasterizationScale = UIScreen.main.scale * 2. Core Animation обмежував весь subtree до 60 FPS через невідповідність масштабу растеризації. Після правки анімація запрацювала на 120 FPS з помітною різницею у відчуттях. Core Animation Programming Guide рекомендує завжди явно задавати rasterizationScale для растрованих шарів.

Порівняння інструментів профілювання

Інструмент Платформа Типове використання Складність
Xcode Instruments (Core Animation) iOS Вимір FPS, виявлення overdraw та блокувань main thread Середня
Android GPU Inspector Android Кадрова трасування, аналіз навантаження на GPU Висока
Android Studio Profiler (Rendering) Android Швидкий перегляд frame time та рекомендації Низька
Flutter DevTools Performance Flutter Overlay FPS та шкала часу з ребілдами Низька

Етапи роботи

  1. Аудит — записуємо сесії в Instruments / Android Profiler, збираємо baseline-метрики FPS, janky frames, frame time.
  2. Аналіз — виявляємо вузькі місця: main thread блокування, overdraw, зайві layout passes.
  3. Правки — ітераційно, з виміром після кожної зміни.
  4. Регресійний прогін — перевіряємо, що правка не зламала сусідні екрани.
  5. Моніторинг — інтегруємо Firebase Performance або власний FPS-монітор для відстеження в продакшені.

Оцінюємо обсяг після аудиту — іноді проблема вирішується за день, іноді вимагає переписування кастомного layout. Ми маємо 8+ років досвіду в мобільній розробці та виконали понад 50 проектів з оптимізації UI. Наші клієнти економлять до 40% бюджету на доробках у порівнянні з повним переписуванням коду.

Орієнтири за термінами

Точкова правка (один екран, зрозуміла причина) — 1–3 дні. Системний аудит та оптимізація кількох екранів — 1–3 тижні. Якщо проблема в архітектурних рішеннях (неправильне використання main thread по всьому додатку) — закладайте 3–6 тижнів з поетапною міграцією.

Отримайте консультацію по вашому проекту — оцінимо обсяг робіт та запропонуємо оптимальне рішення. Зв'яжіться з нами для старту аудиту.