Skip to content

Вопросы на собеседовании: Android-разработчик

100 реальных вопросов с образцовыми ответами и пояснениями для уровня Middle.

Смотреть пример резюме: Android-разработчик

Тренировка флешкарточками

Интервальное повторение · Hunter Pass

Вопросы

concurrencycoroutineskotlin

Структурированная конкурентность связывает время жизни корутин с явным scope и его иерархией Job.

  • Родитель не завершается, пока не завершатся все его дочерние корутины.
  • Отмена scope распространяется на дочерние корутины, поэтому работа не переживает своего владельца.
  • Необработанная ошибка дочерней корутины обычно отменяет родителя и соседние дочерние корутины.
  • Билдеры вроде coroutineScope сохраняют эту структуру, не блокируя текущий поток.

Зачем это спрашивают: Интервьюер проверяет, понимает ли кандидат время жизни, владение и распространение ошибок корутин, а не только их синтаксис.

coroutines

CoroutineScope предоставляет CoroutineContext для запуска корутин, а Job представляет их жизненный цикл.

  • CoroutineContext состоит из элементов, таких как Job, диспетчер, имя и обработчик исключений.
  • Билдер наследует контекст scope и добавляет или заменяет элементы, переданные этому билдеру.
  • Новая корутина обычно создаёт дочерний Job, связанный с Job из унаследованного контекста.
  • Отмена Job у scope отменяет дерево корутин с корнем в этом scope.

Зачем это спрашивают: Интервьюер оценивает, умеет ли кандидат рассуждать о композиции настроек выполнения и владения жизненным циклом корутин.

Используйте супервизию, когда соседние дочерние корутины должны падать независимо, оставаясь под управлением одного родителя.

  • С обычным Job необработанная ошибка дочерней корутины отменяет родителя и остальных его детей.
  • С SupervisorJob ошибка одного ребёнка не отменяет автоматически соседние корутины или супервизор.
  • Каждая дочерняя корутина под супервизией должна явно отдать или обработать свою ошибку, потому что супервизор не превращает её в успех.
  • supervisorScope даёт такую же изоляцию ошибок внутри ограниченного suspend-блока.

Зачем это спрашивают: Интервьюер проверяет, умеет ли кандидат осознанно выбирать семантику распространения ошибок, а не считать супервизию универсальной обработкой исключений.

coroutines

Выбирайте диспетчер по типу работы, а не создавайте потоки вручную.

  • Dispatchers.Main выполняет UI-работу в главном потоке Android, а Main.immediate может выполниться сразу, если вызов уже сделан из него.
  • Dispatchers.IO предназначен для блокирующего ввода-вывода и может расширяться сильнее пула для CPU-задач.
  • Dispatchers.Default рассчитан на CPU-нагрузку, например парсинг или вычисления.
  • Хорошо спроектированная suspend-функция сама использует withContext, когда ей нужно перенести блокирующую или тяжёлую вычислительную работу.

Зачем это спрашивают: Интервьюер оценивает, понимает ли кандидат назначение диспетчеров и принцип main-safety на границах API.

resiliencecoroutines

Отмена корутин кооперативна, потому что корутина останавливается только после того, как заметит отмену своего Job.

  • Стандартные suspend-функции проверяют отмену и выбрасывают CancellationException.
  • CPU-циклы без точек приостановки должны периодически вызывать ensureActive или yield.
  • CancellationException обычно следует пробрасывать дальше, а не превращать в доменную ошибку.
  • Очистку, которая обязана приостанавливаться, можно выполнить в finally, используя NonCancellable только для этого ограниченного участка.

Зачем это спрашивают: Интервьюер проверяет, умеет ли кандидат писать отменяемый код корутин, не проглатывая отмену и не оставляя лишнюю работу.

asyncerror-handling

launch сообщает о необработанном исключении сразу, а async хранит его до ожидания результата Deferred.

  • Корневой launch может передать исключение в CoroutineExceptionHandler после применения обычных правил распространения.
  • await повторно выбрасывает исключение, сохранённое async, в ожидающем коде.
  • Ошибка дочерней корутины всё равно участвует в отмене родителя, даже если её Deferred никто не ожидает.
  • try и catch следует располагать вокруг suspend-вызова или точки await, где восстановление действительно имеет смысл.

Зачем это спрашивают: Интервьюер оценивает, понимает ли кандидат, что билдеры корутин меняют способ получения результата, а не базовую иерархию Job.

coroutines

Suspend-функция может приостановиться, не занимая текущий поток, а блокирующая функция удерживает поток до завершения.

  • Модификатор suspend разрешает точки приостановки, но сам по себе не переносит работу с диспетчера вызывающего кода.
  • Suspend-функция всё ещё может блокировать, если напрямую вызывает блокирующий API.
  • Блокирующий ввод-вывод следует изолировать через withContext с подходящим диспетчером.
  • Приостановка сохраняет локальный поток управления с помощью continuation вместо обязательных колбэков.

Зачем это спрашивают: Интервьюер проверяет, избегает ли кандидат ошибочного предположения, что suspend автоматически делает любую операцию асинхронной или безопасной для главного потока.

android-componentscoroutines

withContext создаёт производный контекст для suspend-блока, а после него возобновляет вызывающий код в исходном контексте.

  • Переданные элементы заменяют элементы с теми же ключами из текущего CoroutineContext, например диспетчер.
  • Вызов приостанавливается до завершения блока и напрямую возвращает его результат.
  • Он сохраняет структурированную конкурентность, потому что вызывающий код ждёт блок и его дочерние корутины.
  • Отмена остаётся связанной с вызывающей корутиной, если Job не заменён намеренно, что требуется редко.

Зачем это спрашивают: Интервьюер оценивает, воспринимает ли кандидат withContext как ограниченное переключение контекста, а не билдер корутины без ожидания результата.

coroutineskotlin

Обычный Flow холодный, потому что его producer-блок запускается отдельно для каждого коллектора.

  • Создание Flow не выполняет его upstream-код.
  • Каждая подписка повторяет upstream-работу, если Flow явно не разделён через stateIn или shareIn.
  • Каждый коллектор получает значения от собственного выполнения flow builder.
  • Сбор приостанавливается до завершения Flow или отмены собирающей корутины.

Зачем это спрашивают: Интервьюер проверяет, понимает ли кандидат влияние холодных потоков на выполнение, дублирование работы и жизненный цикл.

coroutines

StateFlow моделирует наблюдаемое состояние, а SharedFlow представляет настраиваемый горячий поток для рассылки.

  • У StateFlow всегда есть текущее значение, которое новый коллектор получает сразу.
  • StateFlow подавляет обновления, равные предыдущему значению по проверке equality.
  • SharedFlow настраивает replay, дополнительную ёмкость буфера и поведение при переполнении без обязательного начального значения.
  • Оба потока не завершаются только из-за исчезновения коллекторов, поэтому временем их жизни управляет владеющий scope.

Зачем это спрашивают: Интервьюер оценивает, умеет ли кандидат выбирать горячий поток по семантике состояния, а не по знакомству с API.

ci-cd

flowOn меняет контекст корутины для upstream-операторов, расположенных перед ним.

  • Downstream-сбор продолжается в контексте коллектора, сохраняя безопасность контекста.
  • При смене диспетчера flowOn создаёт границу из корутины и канала между upstream и downstream.
  • Несколько вызовов flowOn действуют на соответствующие upstream-сегменты, а не глобально на всю цепочку.
  • Внутри flow builder не следует вручную переключать контекст для emit, потому что Flow обеспечивает сохранение контекста.

Зачем это спрашивают: Интервьюер проверяет, умеет ли кандидат правильно размещать границы выполнения в цепочке Flow.

Выбирайте оператор по тому, должен ли новый ввод отменять, заменять или дополнять предыдущую работу.

  • mapLatest отменяет текущее преобразование, когда приходит новое upstream-значение.
  • flatMapLatest переключается на новый внутренний Flow и отменяет сбор предыдущего.
  • flatMapMerge параллельно собирает несколько внутренних Flow в пределах ограничения конкурентности.
  • Обычного map достаточно, когда каждое значение преобразуется в один результат, не являющийся Flow, и все преобразования должны завершиться.

Зачем это спрашивают: Интервьюер оценивает, понимает ли кандидат семантику отмены и конкурентности распространённых преобразований Flow.

concurrency

Эти операторы меняют взаимодействие медленного коллектора с более быстрым upstream-производителем.

  • buffer позволяет upstream-производству и downstream-обработке перекрываться через канал.
  • conflate сохраняет только последнее ожидающее значение, пока коллектор занят.
  • collectLatest отменяет текущий блок коллектора при поступлении нового значения.
  • Conflation подходит, только если промежуточные значения можно заменить и они не являются обязательными событиями.

Зачем это спрашивают: Интервьюер проверяет, отличает ли кандидат оптимизацию пропускной способности от намеренной потери значений или отмены промежуточной работы.

lifecycle

Собирайте UI-потоки только пока соответствующий Lifecycle находится в активном состоянии.

  • repeatOnLifecycle запускает блок сбора в выбранном состоянии и отменяет его, когда lifecycle опускается ниже этого состояния.
  • При повторном входе lifecycle в состояние создаётся новый блок, поэтому upstream должен допускать повторный сбор.
  • Несколько Flow для параллельного сбора следует запускать отдельными дочерними корутинами внутри repeat-блока.
  • В Compose адаптер collectAsStateWithLifecycle обеспечивает соответствующий lifecycle-aware сбор в State.

Зачем это спрашивают: Интервьюер оценивает, умеет ли кандидат связывать сбор потоков с видимостью UI, не сохраняя устаревшие коллекторы.

callbacks

Используйте callbackFlow, чтобы преобразовать API на колбэках в холодный Flow с безопасной конкурентной отправкой.

  • trySend передаёт значения колбэка в канал Flow, не требуя приостановки колбэка.
  • awaitClose сохраняет producer активным и снимает регистрацию колбэка при завершении сбора.
  • Ошибка регистрации должна закрывать канал с причиной, а не отправлять поддельное значение данных.
  • Адаптер запускается для каждого коллектора отдельно, если результирующий Flow явно не разделён.

Зачем это спрашивают: Интервьюер проверяет, умеет ли кандидат переносить жизненный цикл колбэка во Flow без утечки регистраций.

architecture-components

ViewModel должен владеть состоянием экрана и presentation-логикой, не завися от конкретного экземпляра View.

  • Он переживает изменения конфигурации, пока его Activity, Fragment или navigation entry остаётся логически живым.
  • Он отдаёт неизменяемое наблюдаемое состояние и принимает действия пользователя через явные методы или intents.
  • Он делегирует доступ к данным и доменные правила репозиториям или use case, а не превращается в service locator.
  • Он не должен хранить ссылки на Activity, Fragment, View или другой короткоживущий Context.

Зачем это спрашивают: Интервьюер оценивает, понимает ли кандидат границы ответственности ViewModel, а не использует его как универсальный singleton.

architecture-components

viewModelScope является CoroutineScope, привязанным к жизненному циклу и отменяемым при очистке ViewModel.

  • Его SupervisorJob позволяет соседним корутинам падать независимо.
  • Главный диспетчер подходит для запуска presentation-работы, а нижние слои отвечают за main-safe выполнение.
  • Корутины переживают изменения конфигурации, потому что сохраняется тот же экземпляр ViewModel.
  • Scope не переживает смерть процесса, поэтому для долговечного или восстанавливаемого состояния нужен другой механизм.

Зачем это спрашивают: Интервьюер проверяет, понимает ли кандидат как гарантии владения, так и ограничения viewModelScope.

architecture-componentscoroutines

Предпочитайте StateFlow для состояния на корутинах, а LiveData остаётся полезной для существующих API и кодовых баз, построенных вокруг Lifecycle.

  • StateFlow имеет явное текущее значение и поддерживает полный набор операторов Flow.
  • LiveData автоматически ограничивает вызовы наблюдателя согласно состоянию LifecycleOwner.
  • Для StateFlow нужен lifecycle-aware адаптер, такой как repeatOnLifecycle или collectAsStateWithLifecycle.
  • Лучше преобразовать тип на границе UI, чем независимо поддерживать одно состояние сразу в обоих типах.

Зачем это спрашивают: Интервьюер оценивает, понимает ли кандидат семантические различия и работу с lifecycle, а не объявляет один API всегда лучшим.

immutabilitycoroutinesarchitecture-components

ViewModel должен изменять приватное состояние и отдавать потребителям только read-only StateFlow.

  • MutableStateFlow сохраняет операции записи внутри компонента, владеющего переходами состояния.
  • asStateFlow запрещает вызывающему коду присваивать произвольные значения без создания второго потока.
  • Атомарная функция update предотвращает потерю изменений, когда преобразования могут выполняться конкурентно.
  • Единая неизменяемая модель состояния делает отрисовку и тесты переходов детерминированными.

Зачем это спрашивают: Интервьюер проверяет, применяет ли кандидат владение состоянием и инкапсуляцию к реактивным UI-моделям.

state

Постоянные условия относятся к состоянию, а для неповторяемых эффектов нужна явная модель доставки.

  • 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