Тупить у розробці — що це таке, причини та методи оптимізації

Автор: IT Sectr Опубліковано: 2026-07-28 Час читання: 8 хв

Тупить — це користувацький опис ситуації, коли мобільний застосунок працює повільно та нерівномірно: то відповідає нормально, то раптово зависає на кілька секунд. У технічному контексті «тупить» означає комбінацію лагів і мікрозависань, спричинену частими GC-паузами, блокуванням головного потоку синхронними операціями та неоптимальними структурами даних. За даними Android Performance Benchmarking Guide, зниження часу відповіді з 300 мс до 100 мс підвищує утримання користувачів на 25%. Діагностика туплення потребує поєднання CPU та Memory-профілювання з аналізом частоти збирання сміття.

Головне

  • Туплення — нерівномірне сповільнення роботи застосунку, що чергується з нормальною продуктивністю
  • Основні причини — часті GC-паузи, синхронні операції в UI-потоці, великий обсяг даних в адаптерах без пагінації
  • Діагностика потребує CPU Profiler для пошуку блокувань та Memory Profiler для аналізу частоти й тривалості GC
  • Усунення включає впровадження пагінації (Paging 3), оптимізацію SQL-запитів через Room і вивантаження важких завдань у WorkManager
  • Профілактика — Benchmark Baseline Profiles, AOT-компіляція, мінімізація алокацій у гарячих ділянках коду

Що означає «тупить» у мобільній розробці

Тупить — неформальний термін, яким користувачі описують суб'єктивно повільну роботу застосунку. На відміну від лага, який проявляється як постійна затримка, туплення — це нерегулярні підвисання: застосунок може працювати ідеально кілька секунд, а потім «задуматися» на 1–3 секунди.

Технічна характеристика явища

З точки зору профілювання, туплення проявляється як серія пропущених кадрів (jank) із піковими затримками понад 100 мс. На графіку FPS це виглядає як різкі провали: 60 → 20 → 55 → 10 кадрів на секунду. На відміну від лага з рівномірно низьким FPS, туплення має виражену варіативність.

Сприйняття користувачем

Коли застосунок тупить, користувач не розуміє логіки сповільнень: екран може прокручуватися плавно, а потім раптово зупинитися на секунду. Це викликає фрустрацію та знижує довіру до застосунку. За даними Google, 53% користувачів залишають сайт або застосунок, якщо завантаження триває понад 3 секунди.

Причини раптових сповільнень у застосунках

Непостійний характер туплення вказує на те, що проблема спричинена подієвими факторами, а не постійним перевантаженням. Розглянемо типові сценарії.

GC-паузи під час алокації об'єктів

На Android у середовищі ART збирання сміття зупиняє всі потоки застосунку. Якщо в коді створюється багато тимчасових об'єктів — наприклад, під час кожного виклику onBindViewHolder створюється новий String через конкатенацію — GC запускається частіше. Пауза може тривати 5–50 мс залежно від розміру купи та покоління об'єктів. Користувач відчуває це як раптову «задумливість».

Синхронні SQL-запити в UI-потоці

Room на Android та Core Data на iOS підтримують асинхронні запити, але розробники часто викликають getValue() або виконують запит через runBlocking для простоти. Важкий SELECT із джойнами на таблиці в 10 000 рядків може зайняти 200–500 мс, повністю блокуючи UI на цей час.

Декодування зображень без downscale

Завантаження зображення з камери (12 Мп, 4000x3000 px) без масштабування займає до 200 мс на декодування в Bitmap. Якщо зображення підвантажуються асинхронно, але без пулу потоків з обмеженням, одночасний запуск 5–6 декодувань може перевантажити CPU, спричинивши мігруючі гальмування.

  • Android — конкатенація рядків у циклах, створення об'єктів у гарячих шляхах, Bitmap без inSampleSize
  • iOS — автореліз-пули з великою кількістю об'єктів, imageWithContentsOfFile без масштабування, синхронні URLSession
  • Крос-платформа — JSON-парсинг в UI-потоці, завантаження даних у головному потоці з очікуванням відповіді сервера

Як діагностувати підвисання на Android та iOS

Діагностика непостійних сповільнень складніша, ніж діагностика постійних лагів, оскільки проблема може не відтворюватися під час кожного запуску. Потрібен збір статистики за тривалий період.

Memory Profiler із записом GC-подій

Android Studio Memory Profiler показує не лише використання пам'яті, а й GC-події: частоту, тип (Concurrent, Full), тривалість. Якщо GC відбувається частіше ніж 1 раз на 5 секунд у спокійному стані — це ознака надмірної алокації. Запис heap dump у момент туплення дає змогу побачити, які об'єкти займають пам'ять.

Xcode Instruments з Allocation Tracking

На iOS використовуйте шаблон Allocations в Instruments для відстеження створення та звільнення об'єктів. Увімкніть покоління (Generations) — вони дають змогу робити знімки купи між діями та бачити, які об'єкти залишаються в пам'яті. Persistent objects, які не звільняються, — джерело накопичення пам'яті та подальших пауз.

JankStats API на Android

JankStats — бібліотека Android, яка збирає метрики пропущених кадрів у реальному часі. Вона прив'язує кожен jank до поточного сценарію (наприклад, «прокрутка списку», «відкриття екрана»), що дає змогу зрозуміти, під час якої саме дії виникає туплення.

Приклад інтеграції JankStats для відстеження підвисань на Android:

kotlin
class MainActivity : AppCompatActivity() {
    private lateinit var jankStats: JankStats

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        jankStats = JankStats.create(this.window.decorView) { frameData ->
            if (frameData.isJank()) {
                Log.w("Пропуски кадрів", "Тривалість=${frameData.durationMs}мс")
            }
        }
    }
}

Методи усунення повільної роботи

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

Впровадження пагінації через Paging 3

Якщо список містить 1000+ елементів і всі завантажуються одразу — це гарантоване туплення. Paging 3 на Android та NSFetchedResultsController на iOS завантажують дані порціями під час прокручування. Користувач бачить лише перші 10–20 елементів, решта підвантажується у фоні.

Оптимізація SQL-запитів та індексів

Room дає змогу профілювати запити через Inspection Tool в Android Studio: видно час виконання, кількість повернутих рядків і план запиту. Додавання індексів на стовпці WHERE та ORDER BY може скоротити час запиту з 300 мс до 5 мс. На iOS аналогічну перевірку виконує Core Data Profiler в Instruments.

Вивантаження завдань у WorkManager

Фонові синхронізації, завантаження файлів, обробка даних — усе це має виконуватися через WorkManager (Android) або Background Tasks (iOS). Якщо синхронізація запускається в UI-потоці, застосунок тупитиме на час виконання. WorkManager гарантує виконання у фоновому потоці з урахуванням стану батареї та мережі.

Приклад фонової синхронізації через WorkManager на Android:

kotlin
class SyncWorker(context: Context, params: WorkerParameters)
    : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        return try {
            Log.d("Синхронізація", "Синхронізація даних у фоновому потоці")
            syncData()
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
}

Профілактика туплення на етапі розробки

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

Baseline Profiles для AOT-компіляції

Baseline Profiles — це список класів і методів, які Android компілює заздалегідь (AOT), а не JIT. Без профілю кожен новий екран компілюється під час першого відкриття, викликаючи затримку 100–500 мс. Підготуйте Baseline Profile для ключових екранів і увімкніть генерацію в Gradle через baseline-profile-gradle-plugin.

Мінімізація алокацій у гарячих шляхах

Hot path — це код, який виконується під час кожного кадра: onBindViewHolder, draw, layoutSubviews. Уникайте створення об'єктів у цих методах: використовуйте пул об'єктів, StringBuilder замість конкатенації, кешуйте сформовані рядки та форматери. Кожна зайва алокація наближає наступний GC.

Профілювання через Baseline Profiles в CI

Додайте в CI-пайплайн запуск Macrobenchmark зі сценарієм прокрутки списку та відкриття екрана. Установіть поріг: 99-й перцентиль часу кадра не має перевищувати 16 мс. Якщо поріг перевищено — збірка відхиляється до оптимізації.

  • Android — Baseline Profiles, Macrobenchmark, JankStats, StrictMode з penaltyDeath
  • iOS — MetricKit, os_signpost, XCTMetric, Main Thread Checker у схемі Debug
  • Загальний підхід — регулярне профілювання, код-рев'ю з фокусом на алокації в гарячих шляхах

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

Чим туплення відрізняється від звичайного лага?

Лаг — це постійна затримка (наприклад, 200 мс на кожне натискання). Туплення — непостійне: застосунок працює нормально, потім раптово гальмує на 1–3 секунди, потім знову нормально. Причина — подієві фактори на кшталт GC-пауз або синхронних запитів до БД.

Як виміряти частоту GC-пауз на Android?

Використовуйте Memory Profiler в Android Studio: вкладка Memory показує GC-події з тривалістю. Для продакшн-моніторингу підключіть Firebase Performance Monitoring з кастомними трейсами. На iOS увімкніть Malloc Debug і позначте покоління алокацій в Instruments.

Чи може туплення бути спричинене мережевими запитами?

Опосередковано — так. Якщо відповідь сервера приходить із затримкою, а UI очікує її синхронно, застосунок зависає. Якщо запит асинхронний, але обробка відповіді виконується в UI-потоці — це теж спричинить туплення. Рішення — асинхронна обробка з корутинами та індикаторами прогресу.

Як Kotlin Multiplatform впливає на продуктивність?

За неправильного використання KMP може генерувати зайві об'єкти-обгортки для інтероперабельності. На iOS це збільшує частоту алокацій і, як наслідок, паузи ARC. Використовуйте @ObjCName, оптимізуйте expect/actual та уникайте частих викликів shared-коду з гарячих шляхів UI.

Чи допомагає збільшення розміру heap на Android?

Збільшення heap через android:largeHeap="true" відкладає GC, але не усуває причину алокацій. Коли GC все ж запускається, пауза буде довшою, оскільки потрібно обійти більше об'єктів. Рішення — зменшити кількість алокацій, а не розширювати купу.

Підсумки

  • Туплення — непостійне сповільнення застосунку, спричинене подієвими факторами (GC-паузи, синхронні запити, декодування зображень)
  • Діагностика потребує Memory Profiler, JankStats на Android та Allocation Tracking в Instruments на iOS
  • Основні причини — часті GC-паузи, відсутність пагінації, неоптимальні SQL-запити та синхронна обробка в UI-потоці
  • Усунення — Paging 3, WorkManager, оптимізація індексів БД, downscale зображень і мінімізація алокацій
  • Профілактика — Baseline Profiles, Macrobenchmark, StrictMode, код-рев'ю з перевіркою гарячих шляхів
  • Інструменти — JankStats, Firebase Performance, MetricKit для продакшн-моніторингу підвисань
  • Рекомендація: впровадьте регулярні прогони Macrobenchmark в CI з порогом 16 мс на 99-й перцентиль кадрів

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також