Android / Kotlin
Розробка під Android — це створення застосунків мовою Kotlin для системи, що володіє часом життя застосунку й може будь-коли перебудувати екран чи вбити процес. Цей один факт пояснює найчастіший баґ платформи — екран, що повертається порожнім, — і майже кожну архітектурну рекомендацію Google. Сторінка починається з шістнадцяти подій, що знищують екран, і того, що переживає кожну; далі — смерть процесу й збережений стан, Kotlin і корутини до версії 2.4, Compose і його три фази, фонова робота, доріжки Google Play і його календар на 2026 рік, п’ять задокументованих кейсів і те, де все це вироджується.
- Поточна версія
- Kotlin 2.4 · Android 17 · Compose 1.12
- Час життя
- Належить системі — поворот перебудовує, брак пам’яті вбиває
- Google Play
- Target API 36 обов’язковий з 31 серп. 2026
Що знищує ваш екран — шістнадцять подій і що переживає кожну
На Android система, а не ваш застосунок, вирішує, скільки житимуть екран і його процес. Активність — об’єкт, що веде один екран, — знищують і будують заново щоразу, коли змінюється конфігурація пристрою. Весь процес убивають щоразу, коли системі потрібна його пам’ять, а коли користувач повертається, Android відбудовує екран, з якого той пішов, ніби нічого не сталося. Кожна подія знищує різний обсяг, і частина стану переживає подію, лише якщо живе там, куди ця подія не дістає. Тож корисне питання до будь-якого екрана — не «чи він працює?». Воно таке: «що від нього лишиться після кожної з цих подій?». Ось шістнадцять подій. Наступні розділи посилаються на рядки за номерами.
| # | Подія | Що робить Android | Що переживає | Як перевірити |
|---|---|---|---|---|
| 1 | Натискання Home чи перехід в інший застосунок | Зупиняє активність; процес лишається | Усе — поки що | Натисніть Home, поверніться |
| 2 | Перехід далі з Fragment | Знищує в’ю фрагмента; фрагмент лишається в стеку повернення | Фрагмент і його ViewModel — але не його в’ю й не те, що до неї прив’язано | Перейдіть туди й назад зі встановленим LeakCanary |
| 3 | Перехід з екрана в Compose-навігації | Утилізує його композицію; запис у стеку повернення лишається | ViewModel запису й rememberSaveable — але не remember | Перейдіть і поверніться |
| 4 | Телефон лежить без діла й без зарядки (Doze) | Відкладає мережу, задачі й будильники до коротких вікон обслуговування | Усе — але фонова робота чекає | adb shell dumpsys deviceidle force-idle |
| 5 | Поворот пристрою | Знищує активність і створює нову; процес лишається | ViewModel, збережений стан, диск — але не старий об’єкт активності й не remember | Поверніть емулятор |
| 6 | Перемикання темної теми | Як у рядку 5 | Як у рядку 5 | Плитка у швидких налаштуваннях |
| 7 | Зміна мови застосунку (мова для окремого застосунку, з Android 13) | Як у рядку 5 | Як у рядку 5 | Налаштування → Застосунки → Мова |
| 8 | Зміна розміру шрифту чи масштабу екрана | Як у рядку 5 | Як у рядку 5 | Налаштування → Екран |
| 9 | Зміна розміру вікна — поділ екрана, розкладання складаного телефона, вікно на десктопі | Як у рядку 5; на екранах від 600 dp застосунок із target API 36 уже не може зафіксувати орієнтацію, а з API 37 — і відмовитися від цього | Як у рядку 5 | Емулятор зі змінним розміром чи складаний |
| 10 | Підключення фізичної клавіатури | Як у рядку 5; з Android 17 екранна клавіатура не з’являється знову, якщо застосунок не попросить | Як у рядку 5 | Під’єднайте клавіатуру до емулятора |
| 11 | Системі потрібна пам’ять, поки застосунок у фоні | Убиває процес; коли користувач повертається, новий процес відбудовує екран зі збереженого стану | Збережений стан і диск — але не ViewModel | adb shell am kill <package>, поки застосунок у фоні, потім відкрийте його з «Недавніх» |
| 12 | Користувач відкликає дозвіл у налаштуваннях | Убиває процес; повернення відновлює екран, як у рядку 11 | Як у рядку 11 | Відкличте дозвіл, поки застосунок у фоні |
| 13 | Жест «назад» на останньому екрані | Завершує активність; для застосунків із target API 36 onBackPressed() більше не викликається | Лише диск — збережений стан відкидається | Свайп назад |
| 14 | Змахування застосунку в «Недавніх» | Прибирає задачу та її збережений стан; процес зазвичай теж завершується | Лише диск | Змахніть його |
| 15 | «Примусово зупинити» в налаштуваннях | Убиває процес; його будильники й задачі стоять, доки користувач знову не відкриє застосунок | Лише диск | Налаштування → Застосунки → Примусово зупинити |
| 16 | Перезавантаження телефона | Завершує все; WorkManager знову планує свою роботу, будильники AlarmManager зникають | Диск і черга WorkManager | Перезавантажте емулятор |
Смерть процесу, збережений стан і пам’ять
Рядки 11 і 12 існують тому, що телефон тримає десятки застосунків у кількох гігабайтах пам’яті й не вивантажує пам’ять застосунків на диск — натомість стискає її. Коли цього не вистачає, системна служба, low-memory killer, завершує процеси за важливістю: спершу кешовані застосунки у фоні, потім менш важливі. На телефоні середнього класу з камерою чи грою на передньому плані ваш застосунок у фоні може зникнути за секунди. І тоді Android виконує обіцянку, якої користувач не бачить: коли той повертається з «Недавніх», запускається новий процес і екран, з якого він пішов, відбудовується — та сама вкладка, та сама позиція скролу, те саме недописане повідомлення, — якщо застосунок це зберіг.
Механізм — збережений стан. Коли активність зупиняється, Android просить у неї Bundle — невеликий набір значень — і тримає його поза процесом застосунку, тож він переживає процес і передається новому. SavedStateHandle у ViewModel і rememberSaveable у Compose пишуть саме туди. Bundle подорожує через Binder — механізм Android для викликів між процесами, — чий буфер має 1 МБ і ділиться між усім, що процес надсилає в цю мить. Google радить тримати збережений стан меншим за 50 КБ, а з Android 7.0 перевищення межі кидає TransactionTooLargeException — падіння, що стається на шляху у фон.
| Де живе стан | Рядки 5–10: перебудова | Рядки 11–12: процес убито | Рядки 13–16: сесію завершено | Для чого |
|---|---|---|---|---|
remember у composable-функції | Втрачено | Втрачено | Втрачено | Значення, які дешево перерахувати, — анімація, відфільтрований список |
| ViewModel | Збережено | Втрачено | Втрачено | Те, що можна отримати знову; робота в польоті |
Збережений стан — SavedStateHandle, rememberSaveable | Збережено | Збережено | Втрачено | Дрібниці, які обрав користувач: id, вкладка, позиція скролу, чернетка — до 50 КБ |
| Диск — Room, DataStore, файли | Збережено | Збережено | Збережено | Усе, що користувачеві було б прикро втратити |
Перевірка рядків 11–12 займає хвилину. Опція розробника «Не зберігати активності» знищує кожну активність, щойно з неї йдеш, і ловить частину проблеми. Справжня перевірка — відправити застосунок у фон, виконати adb shell am kill з назвою його пакета — вона вбиває лише фоновий процес — і знову відкрити застосунок із «Недавніх». Екран має виглядати рівно так, як ви його лишили. З Android 11 ApplicationExitInfo каже новому процесу, чому завершився попередній — брак пам’яті, падіння, ANR, користувач, — тож команда може рахувати смерті процесу в продакшні, а не вгадувати.
Саму пам’ять тепер контролюють з двох боків. Android 17, що вийшов 16 червня 2026 року, встановлює межі пам’яті для кожного застосунку залежно від загального обсягу RAM пристрою й завершує процес, який за них виходить; причина завершення містить текст «MemoryLimiter:AnonSwap». Google Play зробив використання пам’яті ключовим показником із порогами для кожного класу RAM — на телефоні з 4 ГБ це 2 ГБ на передньому плані й 1 ГБ у фоні — і каже, що застосунки понад них можуть втратити видимість у магазині з лютого 2027 року. Звична причина — зображення, з тієї самої причини, що на сторінці про React Native: декодоване зображення займає ширина × висота × 4 байти, тож фото на 12 мегапікселів займає в пам’яті 48 МБ.
Kotlin і корутини: робота, що закінчується разом з екраном
Kotlin — мова від JetBrains, стабільна з лютого 2016 року, яку Google зробив офіційною мовою Android у травні 2017-го, а з 2019 року — пріоритетною. Jetpack Compose існує лише на Kotlin, а документацію й нові APIApplication Programming Interface — контракт між програмамиПравила, за якими одна програма звертається до іншої: які запити можна надіслати, у якому вигляді й що прийде у відповідь. Для того, хто звертається, це контракт — усе, що за ним, лишається чорною скринькою, і саме тому другий бік можна переписати, не зламавши вас.Докладніше в статті →Усі терміни пишуть спершу для нього. Kotlin компілюється в той самий JVMJava Virtual Machine — середовище виконання JavaМашина, на якій насправді виконується скомпільований Java-код. Вона дає збирач сміття і JIT — і саме вона пояснює, чому одна збірка без змін працює на Windows, Linux і macOS: різниця платформ є проблемою JVM, а не програми.Докладніше в статті →Усі терміни-байткод, що й Java, а збірка Android потім перетворює його на DEX-код для середовища виконання Android, тож обидві мови напряму викликають одна одну, а Java-кодову базу можна переводити по одному файлу. Дві риси окуповуються одразу. Можливість null є частиною типу — String? не можна вжити як String без перевірки, — а data- та sealed-класи описують стани екрана кількома рядками, які компілятор потім вичерпно перевіряє в when.
| Версія | Вихід | Що змінилося для Android-розробника |
|---|---|---|
| 1.0 | лют. 2016 | Перший стабільний реліз; Google бере його для Android у травні 2017 |
| 1.3 | жовт. 2018 | Корутини стають стабільними |
| 1.9.20 | листоп. 2023 | Kotlin Multiplatform стабільний — спільна логіка для Android та iOS |
| 2.0 | трав. 2024 | Компілятор K2; компілятор Compose переїжджає в репозиторій Kotlin і виходить разом із ним |
| 2.0.20 | серп. 2024 | Strong skipping у Compose увімкнено типово |
| 2.2 | черв. 2025 | Guard-умови в when стабільні; context parameters у попередньому перегляді |
| 2.3 | груд. 2025 | Перевірка проігнорованих результатів функцій; підтримка Java 25 |
| 2.4 | черв. 2026 | Context parameters і явні backing-поля стабільні; літерали колекцій (експериментально); 18 місяців виправлень безпеки для кожної лінійки |
Корутини змінили саме письмо Android-коду. suspend-функція може призупинитися — чекаючи на мережу чи базу даних — без блокування свого потоку, тож асинхронна робота читається згори вниз. Диспетчери кажуть, де вона виконується: Dispatchers.Main — для інтерфейсу, IO — для блокувальних читань і записів, Default — для обчислень. Безпечними, а не лише зручними, корутини робить структурована паралельність. Кожну корутину запускають в області (scope), і коли область скасовують, скасовується все, що в ній запущено. Тож таблиця на початку сторінки — це ще й таблиця областей: правильна область та, що закінчується рівно тоді, коли робота перестає бути корисною.
| Область | Скасовується, коли | Для чого |
|---|---|---|
| viewModelScope | ViewModel очищено — переживає рядки 5–10 і закінчується з рядками 11–16 | Завантаження й оновлення даних екрана |
| lifecycleScope | Активність чи фрагмент знищено — зокрема на кожному повороті | Коротка робота, прив’язана саме до цього об’єкта, як-от показ діалогу |
| rememberCoroutineScope() | Composable-функція виходить із композиції | Робота, запущена тапом, — прокрутка вгору, анімація |
| GlobalScope | Ніколи | Майже ні для чого: так робота переживає екран, для якого її запускали |
| WorkManager | Не область — роботу збережено, і вона переживає рядки 11–16 | Робота, що мусить завершитися, навіть якщо користувач пішов |
Поверх корутин лежить Flow — потік значень у часі: запит до бази, що видає результат знову, коли змінюються рядки; поле пошуку, що робить запити, поки користувач набирає. Для стану екрана StateFlow тримає поточне значення й видає оновлення — саме те, що потрібно відбудованому екрану: щось, що можна прочитати одразу й спостерігати далі. Як екран його збирає, важить не менше, ніж де його створюють. collectAsStateWithLifecycle() у Compose або repeatOnLifecycle(STARTED) з View припиняє збирання, коли застосунок іде у фон, і починає знову, коли той повертається. Без цього потік оновлень геолокації тримає GPS увімкненим для екрана, на який ніхто не дивиться.
Головний потік на Android теж має дедлайн. Якщо він не обробить подію введення за 5 секунд, система показує діалог «Застосунок не відповідає» — ANR. Google Play рахує частку сесій, де користувач побачив ANR, і понад 0,47% загалом або 8% на будь-якій окремій моделі телефона видимість застосунку в магазині може постраждати. Більшість ANR — звичайна робота не на своєму місці: запит до бази, розбір великого JSONJavaScript Object Notation — текстовий формат данихТекстовий формат для передавання даних: пари «ключ — значення», списки, числа й рядки. Він народився в JavaScript, але сьогодні його читає будь-яка мова — і саме тому ним відповідає більшість API, а не чимось потужнішим.Усі терміни чи читання файлу на Dispatchers.Main. StrictMode, увімкнений у debug-збірках, повідомляє про доступ до диска й мережі в головному потоці тієї ж миті.
Compose: три фази і що перескладається
Jetpack Compose, стабільний з липня 2021 року й у версії 1.12 станом на вересень 2026-го, — набір інструментів для інтерфейсу, який рекомендує Google. Ви пишете composable-функції, що описують екран для заданого стану; коли змінюється стан, який вони читають, Compose виконує їх знову — це перескладання (recomposition) — і оновлює те, що змінилося. Він замінює XML-розкладки, findViewById, адаптери й звичку тримати посилання на в’ю та змінювати їх руками. Для списку це різниця між RecyclerView з адаптером і view holder та LazyColumn з лямбдою.
Compose будує кожен кадр у три фази. Композиція вирішує, що показати, виконуючи composable-функції. Розкладка вирішує, де: кожен елемент вимірюють і розміщують. Малювання виводить пікселі. Compose запам’ятовує, яка фаза прочитала кожну частину стану, і коли стан змінюється, повторює лише цю фазу й наступні. Це дає головному правилу продуктивності точну форму: читайте стан якомога пізніше. Заголовок, що рухається зі скролом і читає зсув скролу у своїй composable-функції, перескладається на кожному кадрі скролу. Той самий заголовок, що читає зсув усередині Modifier.offset { }, повторює лише розкладку, а всередині graphicsLayer { } — лише малювання.
Друге правило — пропуск. Під час перескладання Compose пропускає composable-функцію, чиї параметри не змінилися. З Kotlin 2.0.20 strong skipping увімкнено типово: параметри стабільних типів порівнюють через equals(), а все інше — за ідентичністю: це той самий об’єкт? Через це один патерн стає дорогим так, що цього не видно. Room видає новий список нових об’єктів щоразу, коли змінюється будь-який рядок запиту, тож кожен рядок LazyColumn отримує інший об’єкт і перескладається, навіть якщо змінився лише один. Давайте елементам списку стабільні key, позначайте справді незмінні data-класи як @Immutable і читайте звіт про стабільність від компілятора Compose, перш ніж гадати.
| Аспект | View (XML) | Compose |
|---|---|---|
| Як стан доходить до екрана | Ви знаходите в’ю, ставите значення й самі тримаєте їх синхронними | Екран є функцією стану; синхронізувати нічого |
| Списки | RecyclerView, адаптер і view holder — кілька файлів на список | LazyColumn з лямбдою елемента, віртуалізований від початку |
| Де кусає | Витеклі посилання на в’ю після рядка 2, стан, що розходиться з екраном | Стан, прочитаний зарано, нестабільні параметри, нові об’єкти від шару даних |
| Як вимірювати | Layout Inspector, профіль GPU-рендерингу | Лічильники перескладань у Layout Inspector, звіт компілятора про стабільність |
| Реальність у 2026 | Більшість наявних екранів і бібліотеки, які його досі очікують | Куди йде нова робота; обидва взаємодіють в обидва боки |
Compose також несе зміни платформи у формі екрана. Відколи застосунки мусять мати target Android 15, вони малюються від краю до краю, під рядком стану й панеллю навігації; для застосунків із target Android 16 відмови від цього вже немає, жест «назад» типово показує передбачувальну анімацію, а на екранах від 600 dp система ігнорує фіксацію орієнтації — рядок 9. Розкладка, написана для одного телефона у вертикальному положенні, тепер має працювати на планшеті, розкладеному складаному телефоні й у вікні зі змінним розміром. Саме для цього є класи розміру вікна й адаптивні бібліотеки Compose, а сторінка про React Native наводить ті самі дедлайни з боку кросплатформної команди.
Фонова робота: що система дозволяє виконувати
Застосунок, якого немає на екрані, типово не має права далі працювати, і майже кожен реліз Android з 2015 року звужував те, що йому дозволено. Doze в Android 6.0 відкладав мережу й задачі, поки телефон лежить нерухомо. Android 8.0 обмежив фонові сервіси. Android 9 розклав застосунки за кошиками очікування залежно від того, як давно ними користувалися. Android 14 вимагав, щоб кожен сервіс переднього плану декларував тип, і перестав типово давати точні будильники щойно встановленим застосункам. Android 15 обмежив сервіси синхронізації даних шістьма годинами на добу. Понад це кілька виробників додають власні менеджери батареї, що завершують фонову роботу агресивніше за стоковий Android; сайт dontkillmyapp.com відстежує їх за брендами.
| Задача | Що брати | Що обіцяє система |
|---|---|---|
| Робота для екрана, на який дивиться користувач | Корутина у viewModelScope | Працює, доки існує екран, і зупиняється разом із ним — більше нічого не треба |
| Робота, що має колись завершитися, — синхронізація, вивантаження логів | WorkManager з умовами на кшталт «через Wi-Fi» чи «під час зарядки» | Переживає смерть процесу й перезавантаження й виконується, коли дозволить система; періодична робота — не частіше ніж раз на 15 хвилин |
| Те, що користувач запустив і за чим стежить, — навігація, дзвінок, музика, вивантаження відео | Сервіс переднього плану з декларованим типом і видимим сповіщенням | Працює, доки видно сповіщення; на Android 15 сервіси синхронізації даних і обробки медіа зупиняються після шести годин на добу |
| Щось у точний час, який задав користувач, — будильник, нагадування календаря | Точний будильник з AlarmManager | Спрацьовує вчасно навіть у Doze, але з Android 14 дозвіл не надається новим встановленням типово |
| Тримати процесор активним, поки екран вимкнено | Частковий wake lock — коротко й рідко | Нічого: з 1 березня 2026 року Google Play може понизити застосунок, що тримає його задовго |
Практичний наслідок: «працювати у фоні» — ніколи не вимога, яку можна прийняти як написано. Спитайте, що користувачеві потрібно, щоб сталося, і коли йому треба дізнатися, що це сталося. Більшість відповідей — це WorkManager з умовою, сервіс переднього плану зі сповіщенням або push-повідомлення від сервера, яке каже застосунку, що є що забрати. Перевіряйте кожен фоновий шлях на двох-трьох телефонах від виробників, відомих агресивним керуванням батареєю, бо стоковий емулятор — єдине місце, де фонова робота виконується завжди.
Випуск: доріжки, поетапні розгортання й календар Google Play
Google Play дає кожному застосунку чотири доріжки. Внутрішнє тестування охоплює до 100 тестувальників, зазвичай за секунди після додавання збірки. Закрите й відкрите тестування охоплюють більші групи, а продакшн — усіх. Особистий акаунт розробника, створений після 13 листопада 2023 року, мусить провести закрите тестування щонайменше з 12 тестувальниками, підписаними 14 днів поспіль, перш ніж узагалі зможе публікувати в продакшн. Рев’ю зазвичай швидше, ніж в Apple, але воно є, а перевірки політик — декларації дозволів, форма безпеки даних, що відповідає тому, що застосунок робить, target API — автоматизовані й не торгуються.
Поетапне розгортання (staged rolloutоновлення для дедалі більшої частки користувачівВипуск оновлення спершу для невеликої частки користувачів — 1%, потім 5%, 20%, усі — з перевіркою частоти падінь і помилок перед кожним кроком, щоб поганий реліз дійшов до небагатьох, а не до всіх. Воно обмежує, до скількох людей дійде поганий реліз, а не те, чи дійде він узагалі: у магазинах застосунків зупинене розгортання не забирає оновлення в тих, хто його вже має.Усі терміни) випускає оновлення на обраний вами відсоток користувачів, який ви з часом піднімаєте, — 1%, потім 5%, потім 20%, — і його можна зупинити будь-коли. Поки воно триває, Android vitals у Play Console показує частоту падінь і ANR нової версії проти порогів, що впливають на видимість застосунку: частота падінь, які бачить користувач, — 1,09% загалом або 8% на одній моделі телефона, частота ANR — 0,47% загалом або 8% на одній моделі. Поріг на модель — саме той, що ловить фрагментацію: падіння на телефонах одного виробника може бути непомітним у загальному середньому й однаково коштувати застосунку видимості на цих телефонах.
Календар Google Play — вимоги близько до дати цієї сторінки
| З якої дати | Що вимагає | Що це означає в коді |
|---|---|---|
| серп. 2021 | Нові застосунки публікуються як Android App Bundle | Play збирає APK під кожен пристрій і підписує його через Play App Signing |
| 1 листоп. 2025 (відстрочки до 31 трав. 2026) | Застосунки з target Android 15 і новіше підтримують сторінки пам’яті по 16 КБ | Кожна нативна бібліотека в кожній залежності перезібрана з вирівнюванням на 16 КБ |
| 1 берез. 2026 | Надмірні часткові wake lock впливають на видимість у магазині | Wake lock відпускають на кожному шляху, зокрема ті, що беруть SDK |
| 31 серп. 2026 (відстрочки до 1 листоп. 2026) | Нові застосунки й оновлення мають target Android 16 (API 36); наявні застосунки нижче API 35 приховано від нових користувачів на новіших Android | Без відмови від edge-to-edge, predictive back типово, фіксацію орієнтації на великих екранах ігнорують — рядки 9 і 13 |
| 30 верес. 2026 | Верифікація розробників у Бразилії, Індонезії, Сінгапурі й Таїланді для застосунків із магазинів-учасників на сертифікованих пристроях | За застосунком, що поширюється поза Play, однаково має стояти верифікований розробник; Google каже, що правило стане глобальним 2027 року |
| лют. 2027 | Використання пам’яті, пам’ять бітмапів і оптимізація коду можуть впливати на видимість у магазині | Пам’ять, виміряна відносно класів RAM ваших користувачів; увімкнений R8 |
| Target Android 17 (API 37) | Цільовий рівень наступного року, якщо щорічний ритм збережеться | Без відмови від правил орієнтації на великих екранах, дозвіл під час роботи для локальної мережі, static final-поля закриті від рефлексії |
Яка Android-кодова база перед вами
| Що ви бачите в коді | Що це означає |
|---|---|
android:configChanges="orientation|screenSize" на більшості активностей | Хтось заглушив рядки 5 і 9 замість збереження стану; рядки 11–12 однаково його втрачають |
android:screenOrientation="portrait" | Ігнорується на екранах від 600 dp для застосунків із target API 36; з API 37 без відмови |
override fun onBackPressed() | Не викликається для застосунків із target API 36; беріть OnBackPressedCallback |
GlobalScope.launch | Робота, яку ніхто не скасовує, — вона переживає екран, для якого її запустили |
AsyncTask | Застаріле з Android 11; код із часів до корутин |
kotlinCompilerExtensionVersion у build.gradle | Компілятор Compose налаштовано як до Kotlin 2.0; тепер він постачається з Kotlin як Gradle-плагін |
LiveData, XML-розкладки й findViewById | Епоха View; це працює, а нові екрани йдуть у Compose зі StateFlow |
targetSdk нижче 35 | Невидимий для нових користувачів на новіших Android з 31 серп. 2026 |
П’ять кейсів і те, чого в кожному бракувало
Кейси Android мають одну форму: щось, чого застосунок не контролює, — система, спільний компонент, виробник, інший застосунок, — змінюється під ним. Кожен задокументували ті, кого він стосувався: розкриття від безпекової компанії, власна документація й заяви Google, компанії, чиїх користувачів це зачепило.
| Кейс | Що сталося | Чого бракувало |
|---|---|---|
| Stagefright, лип.–серп. 2015 | Помилки в медіабібліотеці Android, досяжні одним MMS, відкрили приблизно 950 мільйонів пристроїв | Шлях оновлень, що не чекав би кожного виробника й оператора |
| Android 7.0, серп. 2016 | Завеликий збережений стан, який доти тихо відкидали, почав валити застосунки з target 7.0 | Малий збережений стан — ідентифікатори, а не дані |
| Google Home, 2020 | Нові фічі писали на Kotlin; NullPointerException, найчастіший тип падіння в Play Console, скоротилися на 33% | Нічого — роботу зробила система типів |
| Android System WebView, 22 берез. 2021 | Автоматичне оновлення WebView поклало Gmail і багато інших застосунків, які нічого не випускали | Моніторинг падінь, що розбиває дані за версією системного компонента, а не лише власною |
| Teams і екстрені дзвінки, груд. 2021 | Teams, встановлений, але без входу, знову й знову реєстрував телефонні акаунти; на деяких Pixel дзвінки на 911 не проходили | API платформи, викликаний у циклі, якого не покривав жоден тест, на тому шляху, який ніхто не може перевірити в продакшні |
Кейс із WebView показує, що це означає на практиці. 22 березня 2021 року оновлення Android System WebView — компонента, що малює вебвміст усередині інших застосунків і оновлюється через Google Play, — автоматично дійшло до телефонів, і застосунки по всій екосистемі почали падати: Gmail, застосунок Google, Amazon, Discord, Outlook, банківські застосунки. Жоден із них нічого не випускав. Google за кілька годин підтвердив проблему й випустив WebView і Chrome 89.0.4389.105, а користувачам порадили оновити обидва. Для команди застосунку урок — у моніторингу: сплеск падінь без релізу за ним — це системний компонент, доки не доведено інше, а звіти про падіння мають нести версію WebView, щоб це показати.
П’ятий кейс — найсерйозніший і найповчальніший. У грудні 2021 року користувач повідомив, що дзвінок на 911 з телефона Pixel не пройшов: телефон показав дзвінок, один раз видав гудок і ні з чим не з’єднався. Google простежив це до ненавмисної взаємодії з Microsoft Teams. Коли Teams був встановлений, але користувач не ввійшов, застосунок знову й знову реєстрував телефонний акаунт у телеком-службі Android, і на деяких Pixel дублікати ламали шлях екстреного дзвінка. Microsoft за кілька днів випустила виправлений Teams, а Google додав власне виправлення платформи в січневе оновлення 2022 року. API платформи, викликаний у циклі, — звичайний баґ. Урок у тому, що виклики застосунку до спільних системних служб дістають до шляхів, яких не дістають власні тести застосунку, — а екстрений дзвінок — шлях, який ніхто не може спробувати в продакшні.
Де це вироджується
Android щедрий до команди, що проєктує з урахуванням того, що застосунком володіє система, і безжальний до тієї, що припускає, ніби процес лишається живим. Мова й набір інструментів добрі; збої приходять від подій із таблиці й від телефонів, яких у команди немає. Майже кожен збій — одне з цього:
- Стан екрана в активності, що спорожнюється на першому ж повороті (рядки 5–10).
- Нічого в збереженому стані — і застосунок повертається порожнім після того, як система забрала пам’ять (рядки 11–12).
- Забагато в збереженому стані — і застосунок падає на шляху у фон.
configChanges, яким глушать поворот замість збереження стану.- Робота в
GlobalScopeі корутини, що ковтають власне скасування. - Потоки, які збирають без огляду на життєвий цикл, — і сенсори й мережа працюють у фоні.
- Стан, прочитаний у композиції, хоча його можна прочитати в розкладці чи малюванні, і нові об’єкти від шару даних на кожне оновлення.
- Фонова робота, яку вважають гарантованою, wake lock, утримані на шляхах помилки, і жодного тесту на телефоні агресивного виробника.
- Тестування лише на флагмані й емуляторі, яке ховає всі проблеми з пам’яттю й кадрами, що є у ваших користувачів.
- Календар Google Play — target API, сторінки по 16 КБ, пороги vitals, — виявлений тоді, коли оновлення відхилили.
Чесна межа — охоплення без контролю. Android працює на більшій кількості видів пристроїв, ніж будь-яка інша система, — від телефонів на 2 ГБ до складаних і вікон на десктопі, — і саме це охоплення є причиною під нього будувати: у більшій частині світу більшість користувачів саме там. Те саме охоплення означає, що застосунок ніколи не знає пристрою, на якому працює, система забирає його, коли захоче, а частини платформи під ним оновлюються за чужим розкладом. Жодна архітектура цього не прибирає. Добра робить кожну подію з таблиці такою, яку можна пережити, вимірює, що стається на телефонах, яких команда не має, і вважає щорічний цільовий рівень Play сталим пунктом плану.
Практична перевірка займає десять хвилин. Прочитайте targetSdk, minSdk і версію Kotlin та знайдіть їх у таблиці календаря. Пошукайте configChanges, GlobalScope, onBackPressed, catch (e: Exception) навколо призупинних викликів і .collect { поза repeatOnLifecycle. Відправте застосунок у фон на найнавантаженішому екрані, виконайте adb shell am kill і відкрийте його знову з «Недавніх». Поверніть кожен екран один раз. Відкрийте Android vitals і прочитайте частоту падінь і ANR для кожної моделі телефона, а не лише загальну. І перевірте, які з ваших SDK беруть wake lock.
Коли це застосовувати
Застосовуйте, коли
- Продукти, чиї користувачі здебільшого на Android: у більшій частині світу це переважна більшість.
- Застосунки з глибокою інтеграцією в систему: фонова синхронізація, віджети, шеринг, власні клавіатури, сервіси доступності.
- Усе, що має прийнятно працювати на слабкому залізі, де в нативної збірки є запас, якого немає в альтернатив.
- Команди, що вже на JVM: Kotlin — коротка дистанція, а Kotlin Multiplatform згодом дозволить поділитися логікою з iOS.
Уникайте, коли
- Будувати обидві платформи нативно однією малою командою: поверхня подвоюється, а людей не більшає.
- Дизайни з розрахунком на фіксований розмір чи орієнтацію екрана: на великих екранах платформа тепер переписує обидва.
- Архітектури, що припускають живий процес, — саме цього припущення Android і не шанує.
- Продукти, які не можуть щороку підіймати цільовий рівень API: тут це жорсткий дедлайн.
Джерела й метод
На чому стоїть ця стаття
- 15 джерел
- 3 задокументовані збої
- Перевірено 13 жовтня 2026 р.
Практики й походження термінів
шістнадцять подій, що виживає, і межі платформи- ДокументаціяParcelables and bundles (відкривається в новій вкладці)Android Developers
- ДокументаціяAndroid 7.0 Behavior Changes (відкривається в новій вкладці)Android Developers
- ДокументаціяAndroid 17 — Behavior changes: all apps (відкривається в новій вкладці)Android Developers
- ДокументаціяAndroid vitals (відкривається в новій вкладці)Android Developers
- ДокументаціяANRs (відкривається в новій вкладці)Android Developers
- ДокументаціяJetpack Compose phases (відкривається в новій вкладці)Android Developers
- ДокументаціяStrong skipping mode (відкривається в новій вкладці)Android Developers
- ДокументаціяAndroid developer verification (відкривається в новій вкладці)Android Developers
- ДокументаціяTarget API level requirements for Google Play apps (відкривається в новій вкладці)Play Console Help
- ДокументаціяKotlin release process (відкривається в новій вкладці)Kotlin Documentation
Збої, на яких вчиться стаття
Stagefright, оновлення WebView і Teams у «П’ять кейсів»- ДослідженняExperts Found a Unicorn in the Heart of Android (відкривається в новій вкладці)Zimperium · 27.07.2015
- СтаттяStagefright (bug) (відкривається в новій вкладці)Wikipedia
- СтаттяPSA: If your Android apps keep crashing, update ‘WebView’ and Chrome (відкривається в новій вкладці)9to5Google · 23.03.2021
- СтаттяMicrosoft Teams app blocks 911 calls on Android, Google issues guidance (відкривається в новій вкладці)Windows Central · 12.2021
Дослідження
Google Home: що Kotlin зробив із часткою збоївДослідження з Claude. Пошук і звірку першоджерел зроблено з Claude (Anthropic). Відбір, текст і перевірку фактів — редакція Anatomy of IT Product.
Було корисно?
Поділіться з тим, хто працює над тією ж задачею.