Когда компонент приложения запускается, и в приложении не запущены другие компоненты, система Android запускает новый процесс Linux для этого приложения с одним потоком выполнения. По умолчанию все компоненты одного и того же приложения работают в одном процессе и потоке, называемом основным потоком.
Если компонент приложения запускается, и для этого приложения уже запущен отдельный процесс, поскольку другой компонент приложения уже запущен, то компонент запускается внутри этого процесса и использует тот же поток выполнения. Однако вы можете организовать запуск различных компонентов вашего приложения в отдельных процессах и создать дополнительные потоки для любого процесса.
В этом документе рассматривается принцип работы процессов и потоков в Android-приложении.
Процессы
По умолчанию все компоненты приложения работают в одном процессе, и большинство приложений это не меняют. Однако, если вам необходимо контролировать, к какому процессу принадлежит тот или иной компонент, вы можете сделать это в файле манифеста.
В манифесте для каждого типа компонента — <activity> , <service> , <receiver> и <provider> — поддерживается атрибут android:process , который может указывать процесс, в котором работает компонент. Вы можете установить этот атрибут так, чтобы каждый компонент работал в своем собственном процессе, или так, чтобы одни компоненты использовали один процесс, а другие — нет.
Также можно установить android:process таким образом, чтобы компоненты разных приложений работали в одном процессе, при условии, что приложения используют один и тот же идентификатор пользователя Linux и подписаны одними и теми же сертификатами.
Элемент <application> также поддерживает атрибут android:process , который можно использовать для установки значения по умолчанию, применяемого ко всем компонентам.
Android может принять решение о завершении процесса в какой-то момент, когда ресурсы потребуются другим процессам, которые в первую очередь обслуживают пользователя. Компоненты приложения, работающие в завершенном процессе, соответственно уничтожаются. Процесс для этих компонентов запускается снова, когда у них появляется работа.
Принимая решение о завершении процессов, система Android учитывает их относительную важность для пользователя. Например, она с большей вероятностью завершит процесс, выполняющий действия, которые больше не отображаются на экране, чем процесс, выполняющий видимые действия. Таким образом, решение о завершении процесса зависит от состояния компонентов, работающих в этом процессе.
Подробности жизненного цикла процесса и его связи с состояниями приложения обсуждаются в разделе «Процессы и жизненный цикл приложения» .
Нити
При запуске приложения система создает для него поток выполнения, называемый основным потоком . Этот поток очень важен, поскольку он отвечает за отправку событий соответствующим виджетам пользовательского интерфейса, включая события отрисовки. Кроме того, это почти всегда поток, в котором ваше приложение взаимодействует с компонентами из пакетов android.widget и android.view из Android UI toolkit. По этой причине основной поток иногда называют потоком пользовательского интерфейса . Однако при определенных обстоятельствах основной поток приложения может не совпадать с потоком пользовательского интерфейса. Для получения дополнительной информации см. раздел «Аннотации потоков» .
Система не создает отдельный поток для каждого экземпляра компонента. Все компоненты, работающие в одном процессе, создаются в потоке пользовательского интерфейса, и системные вызовы к каждому компоненту обрабатываются из этого потока. Следовательно, методы, реагирующие на системные обратные вызовы — такие как onKeyDown() для сообщения о действиях пользователя или метод обратного вызова жизненного цикла — всегда выполняются в потоке пользовательского интерфейса процесса.
Например, когда пользователь касается кнопки на экране, поток пользовательского интерфейса вашего приложения отправляет событие касания виджету, который, в свою очередь, устанавливает состояние нажатия и отправляет запрос на аннулирование в очередь событий. Поток пользовательского интерфейса извлекает запрос из очереди и уведомляет виджет о необходимости перерисовки.
Если вы не реализуете свое приложение должным образом, эта однопоточная модель может привести к низкой производительности, когда ваше приложение выполняет интенсивную работу в ответ на взаимодействие с пользователем. Выполнение длительных операций в потоке пользовательского интерфейса, таких как доступ к сети или запросы к базе данных, блокирует весь пользовательский интерфейс. Когда поток заблокирован, никакие события, включая события отрисовки, не могут быть отправлены.
С точки зрения пользователя, приложение, похоже, зависает. Хуже того, если поток пользовательского интерфейса блокируется более чем на несколько секунд, пользователю отображается диалоговое окно « Приложение не отвечает » (ANR). После этого пользователь может решить закрыть ваше приложение или даже удалить его.
Имейте в виду, что Android UI Toolkit не является потокобезопасным. Поэтому не изменяйте пользовательский интерфейс из рабочего потока. Все изменения пользовательского интерфейса выполняйте из потока UI. В однопоточной модели Android действуют два правила:
- Не блокируйте поток пользовательского интерфейса.
- Не используйте инструментарий пользовательского интерфейса Android вне потока, отвечающего за пользовательский интерфейс.
Рабочие потоки
Из-за однопоточной модели крайне важно для быстродействия пользовательского интерфейса вашего приложения не блокировать основной поток интерфейса. Если вам необходимо выполнять операции, которые не происходят мгновенно, обязательно выполняйте их в отдельных фоновых или рабочих потоках. Просто помните, что вы не можете обновлять пользовательский интерфейс из какого-либо потока, кроме основного потока интерфейса.
Чтобы помочь вам следовать этим правилам, Android предлагает несколько способов доступа к потоку пользовательского интерфейса из других потоков. Вот список методов, которые могут помочь:
Следующие примеры демонстрируют, как перенести задачу в фоновый поток и обновить поток пользовательского интерфейса после завершения задачи:
Котлин
// Kotlin coroutines implementation. fun onClick(v: View) { // Launch a coroutine in the lifecycle scope (e.g., in an Activity or Fragment). lifecycleScope.launch { // Run the blocking task on the IO dispatcher. val bitmap = withContext(Dispatchers.IO) { BitmapFactory.decodeFile("image.png") } // Back on the main thread, update the UI. imageView.setImageBitmap(bitmap) } }
Java
// Java Executor implementation. // (executorService is assumed to be defined elsewhere). public void onClick(View v) { executorService.execute(() -> { // Run the heavy task on a background thread. Bitmap bitmap = BitmapFactory.decodeFile("image.png"); // Update the View on the UI thread. imageView.post(() -> imageView.setImageBitmap(bitmap)); }); }
Данная реализация потокобезопасна, поскольку фоновые операции выполняются в отдельном потоке, в то время как управление ImageView всегда осуществляется из потока пользовательского интерфейса.
Однако по мере роста сложности операции подобный код может стать сложным и трудным в сопровождении. Для обработки более сложных взаимодействий с рабочим потоком можно рассмотреть возможность использования Handler в рабочем потоке для обработки сообщений, поступающих из потока пользовательского интерфейса. Полное объяснение того, как планировать работу в фоновых потоках и взаимодействовать с потоком пользовательского интерфейса, см. в разделе «Обзор фоновой работы» .
Потокобезопасные методы
В некоторых ситуациях реализованные вами методы вызываются из нескольких потоков, поэтому их необходимо писать потокобезопасными.
Это в первую очередь справедливо для методов, которые можно вызывать удалённо, например, для методов в привязанной службе . Когда вызов метода, реализованного в IBinder происходит в том же процессе, в котором работает IBinder , метод выполняется в потоке вызывающего процесса. Однако, когда вызов происходит в другом процессе, метод выполняется в потоке, выбранном из пула потоков, который система поддерживает в том же процессе, что и IBinder . Он не выполняется в потоке пользовательского интерфейса этого процесса.
Например, в то время как метод onBind() сервиса вызывается из потока пользовательского интерфейса процесса сервиса, методы, реализованные в объекте, который возвращает onBind() , такие как подкласс, реализующий методы удаленного вызова процедур (RPC), вызываются из потоков пула. Поскольку у сервиса может быть более одного клиента, более одного потока пула могут одновременно обращаться к одному и тому же методу IBinder , поэтому методы IBinder должны быть реализованы таким образом, чтобы быть потокобезопасными.
Аналогично, поставщик контента может получать запросы данных, исходящие из других процессов. Классы ContentResolver и ContentProvider скрывают детали управления межпроцессным взаимодействием (IPC), но методы ContentProvider , отвечающие на эти запросы — методы query() , insert() , delete() , update() и getType() — вызываются из пула потоков в процессе поставщика контента, а не из потока пользовательского интерфейса этого процесса. Поскольку эти методы могут вызываться из любого количества потоков одновременно, они также должны быть реализованы как потокобезопасные.
Межпроцессное взаимодействие
Android предлагает механизм межпроцессного взаимодействия (IPC) с использованием RPC, при котором метод вызывается активностью или другим компонентом приложения, но выполняется удаленно в другом процессе, при этом результат возвращается вызывающей стороне. Это включает в себя декомпозицию вызова метода и его данных до уровня, понятного операционной системе, передачу их из локального процесса и адресного пространства в удаленный процесс и адресное пространство, а затем повторную сборку и воспроизведение вызова там.
Затем возвращаемые значения передаются в обратном направлении. Android предоставляет весь код для выполнения этих межпроцессных операций, поэтому вы можете сосредоточиться на определении и реализации программного интерфейса RPC.
Для выполнения межпроцессного взаимодействия (IPC) ваше приложение должно привязаться к службе с помощью bindService() . Дополнительную информацию см. в разделе «Обзор служб» .