Вопросы на собеседовании: Android-разработчик
100 реальных вопросов с образцовыми ответами и пояснениями для уровня Middle.
Смотреть пример резюме: Android-разработчик →Тренировка флешкарточками
Интервальное повторение · Hunter Pass
Вопросы
Структурированная конкурентность связывает время жизни корутин с явным scope и его иерархией Job.
- Родитель не завершается, пока не завершатся все его дочерние корутины.
- Отмена scope распространяется на дочерние корутины, поэтому работа не переживает своего владельца.
- Необработанная ошибка дочерней корутины обычно отменяет родителя и соседние дочерние корутины.
- Билдеры вроде coroutineScope сохраняют эту структуру, не блокируя текущий поток.
Зачем это спрашивают: Интервьюер проверяет, понимает ли кандидат время жизни, владение и распространение ошибок корутин, а не только их синтаксис.
CoroutineScope предоставляет CoroutineContext для запуска корутин, а Job представляет их жизненный цикл.
- CoroutineContext состоит из элементов, таких как Job, диспетчер, имя и обработчик исключений.
- Билдер наследует контекст scope и добавляет или заменяет элементы, переданные этому билдеру.
- Новая корутина обычно создаёт дочерний Job, связанный с Job из унаследованного контекста.
- Отмена Job у scope отменяет дерево корутин с корнем в этом scope.
Зачем это спрашивают: Интервьюер оценивает, умеет ли кандидат рассуждать о композиции настроек выполнения и владения жизненным циклом корутин.
Используйте супервизию, когда соседние дочерние корутины должны падать независимо, оставаясь под управлением одного родителя.
- С обычным Job необработанная ошибка дочерней корутины отменяет родителя и остальных его детей.
- С SupervisorJob ошибка одного ребёнка не отменяет автоматически соседние корутины или супервизор.
- Каждая дочерняя корутина под супервизией должна явно отдать или обработать свою ошибку, потому что супервизор не превращает её в успех.
- supervisorScope даёт такую же изоляцию ошибок внутри ограниченного suspend-блока.
Зачем это спрашивают: Интервьюер проверяет, умеет ли кандидат осознанно выбирать семантику распространения ошибок, а не считать супервизию универсальной обработкой исключений.
Выбирайте диспетчер по типу работы, а не создавайте потоки вручную.
- Dispatchers.Main выполняет UI-работу в главном потоке Android, а Main.immediate может выполниться сразу, если вызов уже сделан из него.
- Dispatchers.IO предназначен для блокирующего ввода-вывода и может расширяться сильнее пула для CPU-задач.
- Dispatchers.Default рассчитан на CPU-нагрузку, например парсинг или вычисления.
- Хорошо спроектированная suspend-функция сама использует withContext, когда ей нужно перенести блокирующую или тяжёлую вычислительную работу.
Зачем это спрашивают: Интервьюер оценивает, понимает ли кандидат назначение диспетчеров и принцип main-safety на границах API.
Отмена корутин кооперативна, потому что корутина останавливается только после того, как заметит отмену своего Job.
- Стандартные suspend-функции проверяют отмену и выбрасывают CancellationException.
- CPU-циклы без точек приостановки должны периодически вызывать ensureActive или yield.
- CancellationException обычно следует пробрасывать дальше, а не превращать в доменную ошибку.
- Очистку, которая обязана приостанавливаться, можно выполнить в finally, используя NonCancellable только для этого ограниченного участка.
Зачем это спрашивают: Интервьюер проверяет, умеет ли кандидат писать отменяемый код корутин, не проглатывая отмену и не оставляя лишнюю работу.
launch сообщает о необработанном исключении сразу, а async хранит его до ожидания результата Deferred.
- Корневой launch может передать исключение в CoroutineExceptionHandler после применения обычных правил распространения.
- await повторно выбрасывает исключение, сохранённое async, в ожидающем коде.
- Ошибка дочерней корутины всё равно участвует в отмене родителя, даже если её Deferred никто не ожидает.
- try и catch следует располагать вокруг suspend-вызова или точки await, где восстановление действительно имеет смысл.
Зачем это спрашивают: Интервьюер оценивает, понимает ли кандидат, что билдеры корутин меняют способ получения результата, а не базовую иерархию Job.
Suspend-функция может приостановиться, не занимая текущий поток, а блокирующая функция удерживает поток до завершения.
- Модификатор suspend разрешает точки приостановки, но сам по себе не переносит работу с диспетчера вызывающего кода.
- Suspend-функция всё ещё может блокировать, если напрямую вызывает блокирующий API.
- Блокирующий ввод-вывод следует изолировать через withContext с подходящим диспетчером.
- Приостановка сохраняет локальный поток управления с помощью continuation вместо обязательных колбэков.
Зачем это спрашивают: Интервьюер проверяет, избегает ли кандидат ошибочного предположения, что suspend автоматически делает любую операцию асинхронной или безопасной для главного потока.
withContext создаёт производный контекст для suspend-блока, а после него возобновляет вызывающий код в исходном контексте.
- Переданные элементы заменяют элементы с теми же ключами из текущего CoroutineContext, например диспетчер.
- Вызов приостанавливается до завершения блока и напрямую возвращает его результат.
- Он сохраняет структурированную конкурентность, потому что вызывающий код ждёт блок и его дочерние корутины.
- Отмена остаётся связанной с вызывающей корутиной, если Job не заменён намеренно, что требуется редко.
Зачем это спрашивают: Интервьюер оценивает, воспринимает ли кандидат withContext как ограниченное переключение контекста, а не билдер корутины без ожидания результата.
Обычный Flow холодный, потому что его producer-блок запускается отдельно для каждого коллектора.
- Создание Flow не выполняет его upstream-код.
- Каждая подписка повторяет upstream-работу, если Flow явно не разделён через stateIn или shareIn.
- Каждый коллектор получает значения от собственного выполнения flow builder.
- Сбор приостанавливается до завершения Flow или отмены собирающей корутины.
Зачем это спрашивают: Интервьюер проверяет, понимает ли кандидат влияние холодных потоков на выполнение, дублирование работы и жизненный цикл.
StateFlow моделирует наблюдаемое состояние, а SharedFlow представляет настраиваемый горячий поток для рассылки.
- У StateFlow всегда есть текущее значение, которое новый коллектор получает сразу.
- StateFlow подавляет обновления, равные предыдущему значению по проверке equality.
- SharedFlow настраивает replay, дополнительную ёмкость буфера и поведение при переполнении без обязательного начального значения.
- Оба потока не завершаются только из-за исчезновения коллекторов, поэтому временем их жизни управляет владеющий scope.
Зачем это спрашивают: Интервьюер оценивает, умеет ли кандидат выбирать горячий поток по семантике состояния, а не по знакомству с API.
flowOn меняет контекст корутины для upstream-операторов, расположенных перед ним.
- Downstream-сбор продолжается в контексте коллектора, сохраняя безопасность контекста.
- При смене диспетчера flowOn создаёт границу из корутины и канала между upstream и downstream.
- Несколько вызовов flowOn действуют на соответствующие upstream-сегменты, а не глобально на всю цепочку.
- Внутри flow builder не следует вручную переключать контекст для emit, потому что Flow обеспечивает сохранение контекста.
Зачем это спрашивают: Интервьюер проверяет, умеет ли кандидат правильно размещать границы выполнения в цепочке Flow.
Выбирайте оператор по тому, должен ли новый ввод отменять, заменять или дополнять предыдущую работу.
- mapLatest отменяет текущее преобразование, когда приходит новое upstream-значение.
- flatMapLatest переключается на новый внутренний Flow и отменяет сбор предыдущего.
- flatMapMerge параллельно собирает несколько внутренних Flow в пределах ограничения конкурентности.
- Обычного map достаточно, когда каждое значение преобразуется в один результат, не являющийся Flow, и все преобразования должны завершиться.
Зачем это спрашивают: Интервьюер оценивает, понимает ли кандидат семантику отмены и конкурентности распространённых преобразований Flow.
Эти операторы меняют взаимодействие медленного коллектора с более быстрым upstream-производителем.
- buffer позволяет upstream-производству и downstream-обработке перекрываться через канал.
- conflate сохраняет только последнее ожидающее значение, пока коллектор занят.
- collectLatest отменяет текущий блок коллектора при поступлении нового значения.
- Conflation подходит, только если промежуточные значения можно заменить и они не являются обязательными событиями.
Зачем это спрашивают: Интервьюер проверяет, отличает ли кандидат оптимизацию пропускной способности от намеренной потери значений или отмены промежуточной работы.
Собирайте UI-потоки только пока соответствующий Lifecycle находится в активном состоянии.
- repeatOnLifecycle запускает блок сбора в выбранном состоянии и отменяет его, когда lifecycle опускается ниже этого состояния.
- При повторном входе lifecycle в состояние создаётся новый блок, поэтому upstream должен допускать повторный сбор.
- Несколько Flow для параллельного сбора следует запускать отдельными дочерними корутинами внутри repeat-блока.
- В Compose адаптер collectAsStateWithLifecycle обеспечивает соответствующий lifecycle-aware сбор в State.
Зачем это спрашивают: Интервьюер оценивает, умеет ли кандидат связывать сбор потоков с видимостью UI, не сохраняя устаревшие коллекторы.
Используйте callbackFlow, чтобы преобразовать API на колбэках в холодный Flow с безопасной конкурентной отправкой.
- trySend передаёт значения колбэка в канал Flow, не требуя приостановки колбэка.
- awaitClose сохраняет producer активным и снимает регистрацию колбэка при завершении сбора.
- Ошибка регистрации должна закрывать канал с причиной, а не отправлять поддельное значение данных.
- Адаптер запускается для каждого коллектора отдельно, если результирующий Flow явно не разделён.
Зачем это спрашивают: Интервьюер проверяет, умеет ли кандидат переносить жизненный цикл колбэка во Flow без утечки регистраций.
ViewModel должен владеть состоянием экрана и presentation-логикой, не завися от конкретного экземпляра View.
- Он переживает изменения конфигурации, пока его Activity, Fragment или navigation entry остаётся логически живым.
- Он отдаёт неизменяемое наблюдаемое состояние и принимает действия пользователя через явные методы или intents.
- Он делегирует доступ к данным и доменные правила репозиториям или use case, а не превращается в service locator.
- Он не должен хранить ссылки на Activity, Fragment, View или другой короткоживущий Context.
Зачем это спрашивают: Интервьюер оценивает, понимает ли кандидат границы ответственности ViewModel, а не использует его как универсальный singleton.
viewModelScope является CoroutineScope, привязанным к жизненному циклу и отменяемым при очистке ViewModel.
- Его SupervisorJob позволяет соседним корутинам падать независимо.
- Главный диспетчер подходит для запуска presentation-работы, а нижние слои отвечают за main-safe выполнение.
- Корутины переживают изменения конфигурации, потому что сохраняется тот же экземпляр ViewModel.
- Scope не переживает смерть процесса, поэтому для долговечного или восстанавливаемого состояния нужен другой механизм.
Зачем это спрашивают: Интервьюер проверяет, понимает ли кандидат как гарантии владения, так и ограничения viewModelScope.
Предпочитайте StateFlow для состояния на корутинах, а LiveData остаётся полезной для существующих API и кодовых баз, построенных вокруг Lifecycle.
- StateFlow имеет явное текущее значение и поддерживает полный набор операторов Flow.
- LiveData автоматически ограничивает вызовы наблюдателя согласно состоянию LifecycleOwner.
- Для StateFlow нужен lifecycle-aware адаптер, такой как repeatOnLifecycle или collectAsStateWithLifecycle.
- Лучше преобразовать тип на границе UI, чем независимо поддерживать одно состояние сразу в обоих типах.
Зачем это спрашивают: Интервьюер оценивает, понимает ли кандидат семантические различия и работу с lifecycle, а не объявляет один API всегда лучшим.
ViewModel должен изменять приватное состояние и отдавать потребителям только read-only StateFlow.
- MutableStateFlow сохраняет операции записи внутри компонента, владеющего переходами состояния.
- asStateFlow запрещает вызывающему коду присваивать произвольные значения без создания второго потока.
- Атомарная функция update предотвращает потерю изменений, когда преобразования могут выполняться конкурентно.
- Единая неизменяемая модель состояния делает отрисовку и тесты переходов детерминированными.
Зачем это спрашивают: Интервьюер проверяет, применяет ли кандидат владение состоянием и инкапсуляцию к реактивным UI-моделям.
Постоянные условия относятся к состоянию, а для неповторяемых эффектов нужна явная модель доставки.
- StateFlow подходит для отображаемых фактов, таких как контент, выбор и статус загрузки.
- Повтор navigation-события или snackbar после пересоздания может ещё раз выполнить уже завершённое действие.
- Channel с receiveAsFlow даёт очередь с доставкой одному потребителю, а настроенный SharedFlow поддерживает рассылку.
- Если эффект представляет необработанный бизнес-факт, моделируйте и подтверждайте этот факт в состоянии, а не скрывайте его как временное событие.
Зачем это спрашивают: Интервьюер оценивает, умеет ли кандидат предотвращать случайный повтор событий и сохранять важное состояние при пересоздании UI.
Закрытые вопросы
- 21
Какую проблему решает SavedStateHandle во ViewModel?
architecture-components - 22
Как LifecycleOwner и LifecycleObserver помогают создавать lifecycle-aware код?
lifecycle - 23
Почему у Fragment есть собственный Lifecycle и отдельный viewLifecycleOwner?
android-componentslifecycle - 24
Каковы основные обязанности графа Jetpack Navigation?
jetpack - 25
Как следует передавать данные между destinations в Navigation?
- 26
Как Jetpack Navigation обрабатывает deep links?
jetpacknavigation - 27
Как popUpTo, inclusive, saveState и restoreState влияют на back stack навигации?
navigation - 28
Как привязать ViewModel к scope вложенного navigation graph?
architecture-components - 29
Как представляются несколько back stacks для нижней навигации?
navigation - 30
Как Room entities задают ключи и реляционные ограничения?
room - 31
Когда DAO в Room должен возвращать suspend-результат, а когда Flow?
roomcoroutines - 32
Что гарантирует @Transaction в Room?
transactionsroom - 33
Как следует определять миграции схемы Room?
schemamigrationsroom - 34
Как индексы влияют на проектирование запросов Room?
indexesqueriesroom - 35
Чем отличаются @Embedded, @Relation и TypeConverter в Room?
room - 36
Для какой работы предназначен WorkManager?
designjetpack - 37
Как работают constraints и политики повторов в WorkManager?
resiliencejetpack - 38
Что такое unique work и цепочки работ в WorkManager?
jetpack - 39
Когда следует предпочесть CoroutineWorker обычному Worker?
coroutines - 40
Чем отличаются Preferences DataStore и Proto DataStore?
jetpack - 41
Почему значения DataStore следует менять через edit или updateData?
jetpack - 42
Каким правилам жизненного цикла и ошибок должен следовать экземпляр DataStore?
lifecyclejetpack - 43
Как компоненты и scopes Hilt определяют время жизни зависимости?
componentsdependenciesdependency-injection - 44
Когда использовать @Binds, @Provides и qualifiers в Hilt или Dagger?
dependency-injection - 45
Как Dagger строит и проверяет граф зависимостей?
validationdependenciesdependency-injection - 46
Чем MVVM и MVI различаются в организации UI-состояния?
architecture-patternsstate - 47
Как Compose определяет, что нужно перекомпоновать?
compose - 48
Чем отличаются remember и rememberSaveable в Compose?
compose - 49
Что такое state hoisting в Jetpack Compose?
composehoistingjetpack - 50
Когда следует использовать LaunchedEffect, DisposableEffect и SideEffect?
- 51
Как построить offline-ready фичу со списком товаров на MVVM, Room и Jetpack Compose?
jetpackarchitecture-patternscompose - 52
Как реализовать поиск по мере ввода без запроса на каждое нажатие клавиши?
- 53
Как реализовать экран редактирования профиля, который переживает поворот и понятно сообщает об ошибке сохранения?
- 54
Как передать ID записи через Navigation, не передавая весь объект?
- 55
Как добавить deep link на защищённый экран деталей?
- 56
Как выбрать Hilt scopes для repository, ViewModel экрана и временного координатора загрузки?
architecture-componentsdependency-injection - 57
Как разделить растущее Android-приложение на feature modules?
- 58
Как смоделировать платёжную форму на MVI без повторного воспроизведения одноразовой навигации?
formsarchitecture-patterns - 59
Как спроектировать state hoisting для переиспользуемого выбора даты в Compose?
composedesignhoisting - 60
Как реализовать пагинацию с данными из сети и Room?
roomapi - 61
Как выпустить изменение схемы Room с новым обязательным столбцом?
schemaroom - 62
Как сохранить пользу экрана статьи без интернета?
- 63
Как разрешить конфликт синхронизации, если заметку изменили offline на двух устройствах?
- 64
Как надёжно создавать записи offline с последующей синхронизацией?
- 65
Как реагировать на изменение подключения, не делая приложение зависимым от устаревшего флага сети?
react - 66
Как запланировать одноразовую фоновую синхронизацию без двух параллельных запусков?
concurrency - 67
Как реализовать периодическое обновление без лишнего расхода батареи?
- 68
Как обработать видимую пользователю загрузку файла, которая длится несколько минут?
- 69
Как отменить устаревшую сетевую работу после ухода пользователя с экрана?
- 70
Как запустить два независимых запроса и отменить оба при сбое одного критичного запроса?
- 71
Как исследовать ANR из production?
anr - 72
Как не дать работе с базой или разбору JSON заморозить UI?
database - 73
Как диагностировать дёрганую прокрутку на Compose-экране?
composerendering - 74
Как уменьшить лишние recompositions на часто обновляемом Compose-экране?
compose - 75
Как сохранить плавность LazyColumn при перестановке строк с разными layouts?
ui - 76
Как снизить давление на память в ленте с большим количеством изображений?
memory - 77
Как найти и исправить утечку Activity из-за listener?
memoryandroid-components - 78
Как исправить coroutine, которая удерживает уничтоженный экран в памяти?
memorycoroutines - 79
Как не дать Compose-экрану утечь через callback или remembered object с Activity?
composecallbacksandroid-components - 80
Как восстановить многошаговую форму после остановки процесса Android?
formsconcurrency - 81
Как исправить повторную загрузку данных после каждого поворота?
- 82
Как ускорить медленный cold start?
- 83
Как ускорить запрос Room, который тормозит большой экран истории?
queriesroom - 84
Как исследовать API-вызов, который работает в Postman, но падает в Android-приложении?
api - 85
Как спроектировать обработку сетевых ошибок на экране с повтором?
designresilienceerror-handling - 86
Как написать unit test для ViewModel с coroutines и StateFlow?
coroutineshypothesis-testingarchitecture-components - 87
Когда в тесте фичи лучше fake repository, а когда MockK?
mocking - 88
Как протестировать Flow, который отдаёт loading, кешированные и обновлённые данные?
caching - 89
Как протестировать миграцию Room перед релизом?
migrationsroom - 90
Как протестировать Compose-форму без привязки теста к деталям layout?
formscomposeui - 91
Как исправить flaky Espresso test, который продолжает работу до завершения background work?
flakyui-testing - 92
Как протестировать worker синхронизации WorkManager?
jetpack - 93
Как построить preview и capture на CameraX без утечки камеры?
- 94
Как реализовать runtime permissions для доступа к камере?
permissions - 95
Как сделать собственный Compose-контрол доступным?
compose - 96
Как хранить пользовательские настройки через DataStore без блокировки старта?
jetpack - 97
Как безопасно хранить и использовать токен аутентификации в Android-приложении?
authtokens - 98
Как исследовать production crash через Firebase Crashlytics или Datadog?
monitoring - 99
Как сократить время Gradle build в модульном Android-проекте?
build-tools - 100
Как выпустить рискованную мобильную фичу с возможностью отката?
rollback