Created by AIImproved by people
Riqli · living documents · updated continuously
Where AI knowledge meets human practice.
Share with friends

Advanced Android
Введение в многопоточность в Android
Каждое Android-приложение имеет main thread (UI thread), который отвечает за обработку пользовательского ввода и отрисовку интерфейса. Выполнение тяжелых операций на этом потоке вызывает Application Not Responding (ANR).
Для создания фонового потока можно использовать Thread с Runnable или наследование Thread с переопределением run(). Однако создание потока на каждую операцию неэффективно — лучше использовать пул потоков или HandlerThread.
Коммуникация между потоками: Handler, Looper, MessageQueue
Handler позволяет отправлять и обрабатывать Message и Runnable, связанные с MessageQueue конкретного потока. Looper — это бесконечный цикл, который извлекает сообщения из очереди и передает их Handler'у.
Сервисы: подробный жизненный цикл и процессы
Процесс в Android — это контейнер, в котором выполняются потоки. Приложение по умолчанию запускается в одном процессе. Процессы имеют иерархию важности: Foreground > Visible > Service > Background > Empty.
Можно указать, чтобы компонент (Activity, Service) работал в отдельном процессе, добавив атрибут android:process в манифест. Однако многопроцессность усложняет коммуникацию: вместо прямых вызовов методов приходится использовать AIDL или Messenger.
Foreground Services и Notifications
Foreground Service — это сервис, который пользователь активно осознает (например, музыкальный плеер). Такой сервис должен отображать постоянное уведомление в панели (Notification), и система с меньшей вероятностью его убьет.
IntentService: упрощенная обработка фоновых задач
IntentService — это базовый класс, который автоматически создает фоновый поток (HandlerThread) и очередь для обработки Intent. После обработки всех задач сервис останавливается сам.
Broadcast Receivers: системные и кастомные
BroadcastReceiver позволяет приложению реагировать на системные события (смена сети, заряд батареи) и кастомные события от других приложений. Метод onReceive() должен выполняться быстро (до 10 секунд), иначе система сочтет ресивер зависшим.
LocalBroadcastManager и безопасные внутриприкладные события
LocalBroadcastManager — это утилита для отправки/приема Broadcast Intents в пределах одного приложения. Данные не покидают ваше приложение, что безопаснее и эффективнее глобальных системных broadcast'ов.
Уведомления (Notifications): PendingIntent и действия
PendingIntent — это токен, который позволяет другому приложению (например, системе) выполнить действие от имени вашего приложения. Используется в уведомлениях, виджетах и AlarmManager.
Для создания Notification используется Notification.Builder, который требует иконку (setSmallIcon()), заголовок (setContentTitle()) и текст (setContentText()). Начиная с Android 8, необходимо указать NotificationChannel.
Виджеты: List Widget и RemoteViewsFactory
Для создания списка в виджете нужно использовать RemoteViewsService и RemoteViewsFactory. Обычный ListAdapter не подходит, так как виджет работает в другом процессе.
Чтобы обрабатывать клики на элементах списка в виджете, нужно вызвать setOnClickFillInIntent() для RemoteViews, а не setOnClickPendingIntent. Это позволяет заполнить Intent уникальными данными (позиция, ID) для каждого элемента.
Kotlin: свойства, нул-безопасность и области видимости
Kotlin устраняет NullPointerException на уровне типа: типы по умолчанию non-null. Для nullable переменных нужно использовать оператор ?. (safe call) или !! (force unwrap) с осторожностью.
Тестирование: покрытие граничных случаев и исключений
При тестировании важно проверять не только happy path, но и граничные условия: null, пустые коллекции, максимальные/минимальные значения, неправильный формат данных. Это помогает отловить скрытые баги.
Bound Services: двойная привязка и Messenger
Bound service позволяет клиентам взаимодействовать с сервисом через интерфейс IBinder. Если сервис одновременно является started и bound, он не будет уничтожен, пока не выполнит свою работу и не отвяжутся все клиенты.
Для межпроцессного взаимодействия (IPC) с сервисом вместо прямых вызовов методов нужно использовать Messenger. Клиент отправляет Message объект, а сервис отвечает через replyTo.
Паттерн Model-View-Presenter (MVP) для тестирования
Паттерн MVP отделяет логику (Presenter) от Android-фреймворка (View — Activity/Fragment). Это позволяет unit-тестировать презентер без эмулятора, используя моки для интерфейса View.
Robolectric: теневая реализация Android SDK
Robolectric — это фреймворк, который подменяет стандартные Android-классы на свои Shadow-реализации во время выполнения тестов на JVM, позволяя тестировать код, зависимый от фреймворка.
Тестирование пользовательского интерфейса (UI Testing)
UI-тесты (инструментальные) запускаются на реальном устройстве или эмуляторе. Фреймворк Espresso автоматически синхронизируется с потоком UI, избавляя от ручных задержек (Thread.sleep()).
Композиция против наследования при тестировании
Принцип композиции (has-a) предпочтительнее наследования (is-a) для тестируемого кода. Это позволяет легко подменять зависимости на mock-объекты и соблюдать Single Responsibility Principle.
Что произойдет, если выполнить сетевой запрос на main thread в Android?
Приложение выбросит исключение NetworkOnMainThreadException (на API > 3.0) или зависнет с ошибкой ANR.
Android запрещает блокирующие сетевые операции на UI thread, чтобы гарантировать отзывчивость интерфейса. Для загрузки данных необходимо создавать фоновые потоки (Thread, AsyncTask, RxJava, Coroutines) или использовать сервисы.
Запрос выполнится, но медленно
Приложение автоматически создаст фоновый поток
В чем разница между передачей Runnable в конструктор Thread и созданием класса, наследующего Thread?
Оба способа позволяют выполнить код в новом потоке, но наследование Thread используется реже, так как нарушает принцип композиции над наследованием.
Использование Runnable является более гибким: вы можете запустить один и тот же Runnable в разных потоках или использовать в ThreadPoolExecutor. Наследование Thread связывает код выполнения с самим потоком, что уменьшает возможность повторного использования.
Runnable нельзя прервать, а Thread можно
Thread наследуется только от Thread, а Runnable можно применять к любому классу
Как создать Handler, связанный с фоновым потоком, а не с main thread?
Вызвать Looper.prepare() в фоновом потоке, создать Handler, затем вызвать Looper.loop().