Вопросы по Android Core (Activity, Services, Broadcasts), архитектурным компонентам Jetpack, Kotlin Coroutines и Dependency Injection.
Android-приложение строится на основе четырех базовых компонентов: 1. **Activity (Активность)**: Элемент интерфейса, представляющий один экран приложения, с которым взаимодействует пользователь. 2. **Service (Служба)**: Компонент для выполнения фоновых задач без предоставления пользовательского интерфейса (например, проигрывание музыки, загрузка файлов). 3. **Broadcast Receiver (Приемник широковещательных сообщений)**: Компонент для прослушивания и реагирования на глобальные системные события или сообщения от других приложений (например, изменение статуса зарядки батареи, включение режима полета). 4. **Content Provider (Поставщик содержимого)**: Компонент для управления доступом к структурированным данным приложения (например, адресной книге) и обмена ими с другими приложениями.
Жизненный цикл Activity управляется операционной системой с помощью колбэков: * `onCreate()`: Вызывается при первом создании Activity. Здесь происходит инициализация данных и макет экрана. * `onStart()`: Activity становится видимой для пользователя. * `onResume()`: Activity выходит на передний план и начинает принимать ввод пользователя. Запуск анимаций и обновлений датчиков. * `onPause()`: Activity частично теряет фокус (например, перекрыта диалоговым окном). Здесь нужно приостанавливать анимации. * `onStop()`: Activity больше не видна пользователю. * `onDestroy()`: Вызывается перед полным уничтожением Activity (при закрытии экрана или нехватке памяти у ОС). * `onRestart()`: Вызывается, если Activity возвращается из остановленного состояния (`onStop`) обратно на передний план.
* **`Service`**: Базовый сервис. Выполняет операции в **главном потоке** (Main Thread) приложения по умолчанию. Требует ручного создания фонового потока, чтобы не заблокировать интерфейс. * **`IntentService`**: Наследник Service. Выполняет задачи последовательно в **отдельном рабочем потоке**. Автоматически завершает свою работу после выполнения всех задач. (Помечен как Deprecated в современных API в пользу WorkManager). * **`Bound Service`**: Служба, к которой могут привязаться другие компоненты приложения (например, Activity) с помощью `bindService()`. Позволяет обмениваться данными через IPC (Inter-Process Communication).
Оба интерфейса используются для сериализации данных при передаче между компонентами (через `Intent` или `Bundle`): * **`Serializable`**: Стандартный Java-интерфейс. Работает на основе рефлексии рантайма. Прост в использовании (не требует написания кода), но работает очень медленно и создает много временных объектов, нагружая Garbage Collector. * **`Parcelable`**: Специфичный для Android интерфейс. Сериализация пишется разработчиком вручную (или генерируется с помощью `@Parcelize` плагина в Kotlin). Работает в разы быстрее, так как данные сериализуются напрямую в бинарный поток IPC.
Корутины (Coroutines) — это легковесные потоки выполнения, встроенные в язык Kotlin на уровне компилятора. * **Преимущества**: * **Легковесность**: Можно запускать сотни тысяч корутин на одном физическом потоке ОС без накладных расходов на переключение контекста процессора. * **Неблокирующий код**: Корутины используют механизм приостановки (`suspend`). Когда корутина ожидает сетевой запрос, она засыпает, освобождая нижележащий поток выполнения для других корутин. * **Структурированная конкурентность (Structured Concurrency)**: Позволяет связывать жизненный цикл запущенных корутин с жизненным циклом компонентов UI (например, через `viewModelScope`).
Оба метода используются для запуска корутин, но имеют разное назначение: * **`launch`**: Запускает корутину по принципу «выстрелил и забыл» (fire-and-forget). Возвращает объект `Job`, с помощью которого можно отменить выполнение, но не возвращает результат работы. * **`async`**: Запускает корутину и ожидает возвращения результата. Возвращает `Deferred<T>` (аналог Promise). Для получения результата нужно вызвать приостанавливающий метод `await()` на объекте Deferred.
Диспечеры (Dispatchers) указывают корутине, на каком именно потоке (или пуле потоков) должен выполняться код: * **`Dispatchers.Main`**: Главный UI-поток Android. Используется для обновления интерфейса и легких задач. * **`Dispatchers.IO`**: Пул потоков для операций ввода-вывода (сетевые запросы, чтение/запись БД, работа с файлами). Автоматически масштабируется. * **`Dispatchers.Default`**: Пул потоков для ресурсоемких вычислений (парсинг JSON, сортировка массивов, обработка изображений). Размер равен числу ядер процессора. * **`Dispatchers.Unconfined`**: Запускает корутину в текущем потоке до первой точки приостановки (не рекомендуется для использования в продакшене).
* **`LiveData`**: Архитектурный компонент Jetpack. Привязан к жизненному циклу Android-компонентов (Lifecycle-aware), обновляет UI только тогда, когда Activity активна. Работает исключительно в главном потоке. * **`StateFlow`**: Горячий Kotlin Flow, представляющий состояние. Всегда хранит последнее значение и требует начального состояния. Рекомендуется как замена LiveData в многоплатформенных и чистых Kotlin проектах. * **`SharedFlow`**: Горячий поток событий (hot stream). Не хранит состояние по умолчанию (но поддерживает replay). Идеален для отправки разовых событий (показ Toast, навигация, вывод SnackBar), которые не должны дублироваться при повороте экрана.
* **XML Layout**: Императивный подход. Элементы UI объявляются в XML, а затем разработчик вручную обновляет их состояние в коде с помощью `findViewById` или ViewBinding. Сложно синхронизировать состояние. * **Jetpack Compose**: Декларативный UI фреймворк на чистом Kotlin. Интерфейс описывается функциями, помеченными `@Composable`. UI автоматически перестраивается (происходит рекомпозиция), когда изменяются связанные с ним переменные состояния (`State`). Упрощает разработку и уменьшает объем шаблонного кода.
Dependency Injection (внедрение зависимостей) — это паттерн проектирования, при котором зависимости объекта передаются ему извне, а не создаются им самим. **Популярные фреймворки**: * **Dagger 2**: Мощный классический фреймворк. Проверяет зависимости на этапе компиляции, генерируя Java-код. Очень быстрый в рантайме, но сложен в настройке. * **Hilt**: Официальная библиотека от Google, построенная поверх Dagger 2 специально для Android. Упрощает инициализацию и связывает зависимости с жизненным циклом Activity/ViewModel. * **Koin / Kodein**: Легковесные сервис-локаторы на чистом Kotlin. Зависимости разрешаются в рантайме (lazy-load), просты в настройке, не требуют компиляции аннотаций.
`WorkManager` — это библиотека Jetpack для выполнения фоновой работы, которая должна быть гарантированно выполнена, даже если приложение закрыто или устройство перезагружено. Он автоматически выбирает оптимальный способ выполнения (JobScheduler, AlarmManager) с учетом ограничений устройства (наличие интернета, зарядка батареи).
* **MVP**: Presenter хранит ссылки на View и Model. Изменения передаются во View императивно через интерфейсы. View и Presenter жестко связаны 1:1. * **MVVM**: ViewModel предоставляет потоки данных (StateFlow, LiveData), на которые подписывается View. View сама реагирует на изменения данных. ViewModel ничего не знает об Activity. * **MVI (Model-View-Intent)**: Развитие MVVM с однонаправленным потоком данных (Unidirectional Data Flow). Пользователь генерирует намерения (Intent), они обрабатываются, создавая новое состояние (State), которое отправляется во View в виде единого объекта.
`Room` — это ORM-надстройка над встроенной базой данных SQLite в Android. На этапе компиляции Room анализирует аннотации `@Database`, `@Dao`, `@Entity`, проверяет SQL-запросы на корректность структуры таблиц и генерирует Java-код с реализацией методов доступа к данным.
ANR — это окно предупреждения системы, возникающее, когда приложение блокирует главный UI поток более чем на 5 секунд (или Broadcast Receiver более чем на 10 секунд). Чтобы избежать ANR, все длительные задачи (сеть, БД, сложные вычисления) необходимо выполнять в фоновых потоках (корутинах с Dispatchers.IO/Default).
* **`FLAG_ACTIVITY_SINGLE_TOP`**: Если запускаемая Activity уже находится на вершине стека (Back Stack), то новая не создается, а вызывается метод `onNewIntent()` у существующей. * **`FLAG_ACTIVITY_CLEAR_TOP`**: Если запускаемая Activity уже есть в стеке, все Activity, находящиеся выше нее, уничтожаются, и она выходит на вершину. По умолчанию она пересоздается, если не скомбинирована с `FLAG_ACTIVITY_SINGLE_TOP`.
* **View Binding**: Замена `findViewById`. Генерирует класс привязки для каждого XML-макета, обеспечивая безопасный доступ к view по ID без риска NullPointerException. * **Data Binding**: Более мощный инструмент, позволяющий связывать переменные данных из кода напрямую с элементами UI в XML-макете, используя специальный синтаксис внутри XML.
* **Cold Flow (холодный поток)**: Ленивый поток. Код внутри флоу не начинает выполняться, пока у потока не появится подписчик (вызов метода `collect`). У каждого подписчика свой собственный запуск потока. * **Hot Flow (горячий поток)**: Активный поток. Выполняет код независимо от наличия активных подписчиков. Рассылает значения всем подписчикам одновременно (как броадкаст). Пример: `SharedFlow`, `StateFlow`.
Navigation Component — это библиотека для управления переходами между экранами приложения (обычно фрагментами). Он использует единый XML-граф навигации (`nav_graph`), автоматизирует анимации переходов, передачу аргументов (Safe Args) и интеграцию с боковым меню или нижней панелью навигации.
Это компоненты, которые умеют следить за изменением состояния жизненного цикла других компонентов (например, Activity или Fragment) и автоматически выполнять действия при наступлении определенных событий (например, отписываться от данных в `onStop()`). Реализуется через интерфейсы `LifecycleObserver`.
Класс `Application` является базовым классом для поддержки глобального состояния всего приложения. Его экземпляр создается самым первым при запуске процесса приложения и уничтожается последним. Используется для глобальной инициализации библиотек (например, Firebase, DI контейнеров, логирования).
* **Stack (Стек)**: Память для выполнения потоков. Хранит локальные переменные примитивных типов и ссылки на объекты. Аллокация происходит мгновенно. * **Heap (Куча)**: Память для хранения самих объектов. Общая для всего приложения. Работа с кучей медленнее, очисткой кучи занимается сборщик мусора (GC).
* **`val`**: Переменная только для чтения (read-only). Ей можно присвоить значение только один раз (аналог `final` в Java). * **`var`**: Мутабельная переменная, значение которой можно изменять многократно в ходе работы программы.
* **`lateinit var`**: Отложенная инициализация переменной. Используется только с `var` ненулевого типа. Разработчик гарантирует компилятору, что инициализирует переменную до первого обращения к ней. * **`by lazy`**: Ленивая инициализация свойства константы `val`. Значение вычисляется ровно один раз при первом обращении к свойству и кэшируется. По умолчанию потокобезопасно.
* **`CoroutineScope`**: Определяет жизненный цикл и границы запущенных внутри него корутин. Содержит в себе `CoroutineContext`. * **`CoroutineContext`**: Набор элементов, конфигурирующих работу корутины. Основные элементы: `Job` (управление жизненным циклом), `CoroutineDispatcher` (выбор потока), `CoroutineName` (имя для отладки), `CoroutineExceptionHandler` (перехват ошибок).
Кооперативная многозадачность означает, что корутины должны явно уступать процессорное время друг другу в точках приостановки (например, `yield()` или `delay()`). Если запустить корутину с бесконечным блокирующим циклом без suspend вызовов, отменить ее обычными методами не удастся.
`Channel` — это потокобезопасный способ передачи потока данных между корутинами (паттерн Очередь / Трубопровод). Одна корутина отправляет данные через метод `send()`, другая принимает через `receive()`. Бывают буферизованные и небуферизованные каналы.
* **`const val`**: Константа времени компиляции (compile-time). Объявляется только на верхнем уровне файла или внутри `companion object`. Ее значение вшивается в код компилятором. * **`val`**: Константа времени выполнения (run-time). Может вычисляться динамически в ходе работы программы.
Расширения позволяют добавлять новые методы в существующие классы (даже системные, код которых нельзя изменить) без необходимости наследоваться от них. Объявляются как обычные функции с префиксом целевого класса (например, `fun String.myMethod()`). Под капотом транслируются в статические методы.
`companion object` — это объект, объявляемый внутри класса, методы и свойства которого можно вызывать напрямую по имени класса (аналог статических членов класса `static` в Java, но являющийся полноценным объектом в рантайме).
`LayoutInflater` считывает XML-файл разметки интерфейса, парсит его структуру тегов и динамически конструирует Java/Kotlin объекты классов View (например, `LinearLayout`, `TextView`), выстраивая их в древовидную иерархию элементов на экране.
Жизненный цикл Activity управляется операционной системой: * `onCreate()`: Первоначальное создание. Разметка UI, привязка данных. * `onStart()`: Activity становится видимой пользователю на экране. * `onResume()`: Activity выходит на передний план, начинает принимать пользовательский ввод. * `onPause()`: Теряет фокус (например, перекрыто полупрозрачным диалогом), ввод заблокирован. * `onStop()`: Полностью скрывается из вида. * `onDestroy()`: Выгрузка из памяти. **Разница между onStart() и onResume()**: В `onStart` Activity видна, но с ней еще нельзя взаимодействовать (она не в фокусе). В `onResume` Activity находится на переднем плане и полностью интерактивна.
В Android для экономии времени запуска приложений и памяти используется специальный родительский процесс — **Zygote**. При старте ОС процесс Zygote запускается, загружает в память все базовые классы Android SDK и системные ресурсы. Когда пользователь запускает приложение, система не создает процесс с нуля, а выполняет системный вызов `fork()` от Zygote. Новый процесс мгновенно получает копию предзагруженных ресурсов. Благодаря оптимизации Copy-on-Write операционной системы, общие ресурсы физически не копируются, а делятся между процессами в памяти.
* **`Service`**: Компонент для фоновых задач. По умолчанию выполняется в **главном (UI) потоке** приложения. Для долгих операций нужно вручную создавать фоновый поток. * **`IntentService`**: Наследник Service. Автоматически создает рабочий поток для обработки входящих намерений (Intents) по очереди и останавливается сам после завершения работы. Помечен как Deprecated. * **`WorkManager`**: Современная библиотека Jetpack для гарантированного выполнения фоновых задач, даже если приложение закрыто или устройство перезагружено. Учитывает условия (наличие интернета, зарядки) и автоматически выбирает оптимальный планировщик (JobScheduler, AlarmManager).
Fragment живет дольше, чем его иерархия View (так как фрагмент может сохраняться в бэкстеке при уничтожении View). Если сохранить ссылку на ViewBinding в поле фрагмента и не занулить ее, возникнет утечка памяти. **Правильный паттерн**: ```kotlin class MyFragment : Fragment() { private var _binding: MyFragmentBinding? = null private val binding get() = _binding!! override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = MyFragmentBinding.inflate(inflater, container, false) return binding.root } override fun onDestroyView() { super.onDestroyView() _binding = null // Предотвращаем утечку памяти! } } ```
Это механизмы обработки сообщений и переключения задач между потоками: * **`MessageQueue`**: Очередь, содержащая сообщения (`Message`) и блоки кода (`Runnable`), которые нужно выполнить. * **`Looper`**: Бесконечный цикл, привязанный к потоку. Он постоянно опрашивает `MessageQueue` на наличие новых задач и передает их на исполнение. * **`Handler`**: Инструмент для отправки задач в `MessageQueue` ассоциированного потока и их обработки. Используется для выполнения фонового кода и передачи результатов обратно в главный (UI) поток (через `Handler(Looper.getMainLooper())`).
* **LiveData / StateFlow**: Архитектурные хранилища данных. Они являются платформенно-независимыми (StateFlow — часть Kotlin Coroutines) и используются на уровне ViewModel для хранения бизнес-состояния. * **`MutableState` (`@State` / `remember`)**: Специальный тип состояния в Jetpack Compose. Он напрямую интегрирован с рантаймом отрисовки Compose. При изменении значения MutableState рантайм автоматически запускает рекомпозицию (перерисовку) тех Composable-функций, которые читали это состояние.
Рекомпозиция — это процесс повторного вызова Composable-функций при изменении данных. * **Интеллектуальная перерисовка**: Compose не перерисовывает весь экран. Он строит дерево рендеринга и точечно обновляет только те функции, чьи аргументы или читаемые `@State` переменные изменились. * **`remember`**: Сохраняет объект в памяти во время рекомпозиции, предотвращая его сброс. * **`derivedStateOf`**: Используется для оптимизации. Генерирует состояние, зависящее от других состояний, но запускает рекомпозицию только тогда, когда результат вычисления реально меняется (например, при скролле списка выше определенного элемента).
Compose делит типы данных на стабильные (Stable) и нестабильные: * **Stable (стабильные)**: Иммутабельные типы (String, примитивы) или классы, которые уведомляют Compose об изменениях. Если аргументы Composable-функции стабильны и не изменились, Compose пропускает (skips) вызов этой функции при рекомпозиции. * **Unstable (нестабильные)**: Мутабельные типы (например, стандартные коллекции `List`, `Map` из Java/Kotlin). Compose не может гарантировать их неизменность, поэтому Composable-функции с нестабильными аргументами перерисовываются при каждой рекомпозиции. Для исправления используют Kotlinx Immutable Collections или аннотацию `@Stable`.
Composable-функции должны быть чистыми (без побочных эффектов). Side Effect — это любое действие, выходящее за рамки отрисовки UI (запрос в сеть, подписка на датчики). Основные функции управления эффектами: * **`LaunchedEffect`**: Запускает корутину при появлении Composable на экране. Перезапускается, если меняются переданные ключи. * **`DisposableEffect`**: Используется для эффектов, требующих очистки (например, подписка на BroadcastReceiver). Содержит блок `onDispose` для отмены подписки. * **`SideEffect`**: Выполняется при каждой успешной рекомпозиции для синхронизации состояния Compose с внешними объектами.
* **Dagger 2**: Фреймворк Dependency Injection, работающий на этапе компиляции (Compile-time). С помощью процессора аннотаций (APT/KSP) Dagger анализирует граф зависимостей и генерирует чистый Java-код (фабрики, провайдеры) для ручного связывания объектов. Не использует медленную рефлексию в рантайме. * **Hilt**: Библиотека-обертка над Dagger 2, созданная Google. Hilt упрощает интеграцию DI в Android, автоматически создавая стандартные контейнеры (Components) для классов Android (Application, Activity, Fragment, ViewModel) и управляя их жизненным циклом, избавляя от написания шаблонного кода (boilerplate).
* **Dependency Injection (Dagger/Hilt)**: Зависимости передаются классу извне. Граф зависимостей проверяется и генерируется на этапе компиляции. Ошибки отсутствия зависимостей выявляются при сборке проекта. Минус — долгая сборка (build time). * **Service Locator (Koin)**: Класс сам запрашивает зависимости у глобального локатора (`by inject()`). Все связи разрешаются динамически во время выполнения приложения (Runtime). Ошибки отсутствия зависимостей упадут в рантайме (crash), но Koin работает быстрее при компиляции и проще в настройке.
Корутины — это легковесные потоки, построенные на концепции **подвешивания (suspension)**. Компилятор преобразует функции с ключевым словом `suspend` в конечный автомат с помощью передачи колбэка (`Continuation`). * При вызове приостанавливающей операции корутина сохраняет текущее состояние (локальные переменные) в объект `Continuation` и освобождает поток ОС для других задач. * Поток не блокируется. Когда асинхронная операция завершается, рантайм вызывает метод `resume()`, возвращая сохраненное состояние корутины в поток выполнения.
* **`launch`**: Запускает корутину по принципу «выстрелил и забыл» (fire-and-forget). Возвращает объект `Job`, с помощью которого можно управлять жизненным циклом корутины (отменить). Исключения внутри `launch` сразу пробрасываются в обработчик ошибок потока. * **`async`**: Запускает корутину, которая должна вернуть результат. Возвращает `Deferred<T>` (наследник `Job`). Чтобы получить результат, нужно вызвать метод `await()`. Если внутри корутины произошла ошибка, она упадет только в момент вызова `await()`.
Structured Concurrency (структурированная конкурентность) гарантирует, что время жизни дочерних корутин привязано к времени жизни их родительской области видимости (`CoroutineScope`). * Если родительская корутина отменяется, все ее дочерние корутины автоматически отменяются. * Родителю нельзя завершить выполнение, пока не завершатся все его дочерние процессы. * Ошибка в дочерней корутине автоматически распространяется вверх по дереву, отменяя родительский Scope (если не используется `SupervisorJob`).
* **`Flow`**: Холодный поток данных (Cold Stream). Код внутри Flow начинает выполняться только тогда, когда вызывается терминальный метод сбора (`collect`). Не хранит историю данных. * **`SharedFlow`**: Горячий поток (Hot Stream). Активен независимо от наличия подписчиков. Может транслировать события нескольким коллекторам одновременно. Настраивается буфер истории (`replay`). * **`StateFlow`**: Специализированный `SharedFlow` с историей в 1 элемент. Всегда хранит текущее состояние (свойство `.value`), требует начального значения и присылает обновления подписчикам только при изменении состояния (distinctUntilChanged).
Android Keystore System используется для безопасного хранения криптографических ключей шифрования. Ключи хранятся в защищенной системной области памяти (Hardware-backed keystore), недоступной для приложений и ядра ОС (например, TEE — Trusted Execution Environment или Secure Element). Приложение может использовать ключ для шифрования, дешифрования или подписи данных, отправляя данные в Keystore API, но сам приватный ключ никогда не покидает защищенный аппаратный модуль, что исключает его кражу вредоносным ПО.
* **`Serializable`**: Стандартный интерфейс Java. Использует рефлексию для сериализации объектов, что создает множество временных объектов и нагружает GC. Работает медленно. * **`Parcelable`**: Специфичный для Android интерфейс. Сериализация пишется вручную (или генерируется с помощью `@Parcelize`), где все поля объекта записываются в бинарный поток данных `Parcel` напрямую. Работает значительно быстрее и тратит меньше памяти.
* **`ViewBinding`**: Простая библиотека для замены `findViewById`. Генерирует класс привязки для каждого XML-макета, предоставляя типы для всех View с ID. Не влияет на скорость сборки и не поддерживает логику внутри XML. * **`DataBinding`**: Более мощный инструмент. Позволяет связывать данные ViewModel напрямую с View в XML-файле разметки с помощью выражений `@{...}`. Требует больше ресурсов процессора на кодогенерацию во время сборки приложения.
Clean Architecture делит код приложения на независимые слои, где зависимости направлены строго снаружи внутрь: 1. **Data Layer (Внешний)**: Источники данных (API, База данных, SharedPreferences). Содержит реализации репозиториев. 2. **Domain Layer (Центральный)**: Чистая бизнес-логика. Содержит интерфейсы репозиториев, сущности (Entities) и сценарии использования (Use Cases). Не зависит от библиотек Android и UI. 3. **Presentation Layer (Внешний)**: Пользовательский интерфейс (UI Compose/Views, ViewModel). Отвечает за отображение данных на экране.
Способы оптимизации размера сборки: * Использовать формат **Android App Bundle (AAB)** вместо APK для сборки под конкретное устройство в Google Play. * Включить сжатие кода и ресурсов R8/Proguard (`minifyEnabled true`, `shrinkResources true`). * Перевести растровые изображения в векторный формат VectorDrawable или использовать формат WebP. * Удалить неиспользуемые локализации ресурсов с помощью `resConfigs`. * Использовать динамическую загрузку тяжелых библиотек или ассетов (Dynamic Delivery).