When an application component starts and the application doesn't have any other components running, the Android system starts a new Linux process for the application with a single thread of execution. By default, all components of the same application run in the same process and thread, called the main thread.
Если компонент приложения запускается, и для этого приложения уже запущен отдельный процесс, поскольку другой компонент приложения уже запущен, то компонент запускается внутри этого процесса и использует тот же поток выполнения. Однако вы можете организовать запуск различных компонентов вашего приложения в отдельных процессах и создать дополнительные потоки для любого процесса.
В этом документе рассматривается принцип работы процессов и потоков в Android-приложении.
Процессы
By default, all of an application's components run in the same process, and most applications don't change this. However, if you find that you need to control which process a certain component belongs to, you can do so in the manifest file.
The manifest entry for each type of component element— <activity> , <service> , <receiver> , and <provider> —supports an android:process attribute that can specify a process the component runs in. You can set this attribute so that each component runs in its own process or so that some components share a process while others don't.
You can also set android:process so that components of different applications run in the same process, provided that the applications share the same Linux user ID and are signed with the same certificates.
Элемент <application> также поддерживает атрибут android:process , который можно использовать для установки значения по умолчанию, применяемого ко всем компонентам.
Android might decide to shut down a process at some point, when resources are required by other processes that are more immediately serving the user. Application components running in the process that's shut down are consequently destroyed. A process is started again for those components when there's work for them to do.
Принимая решение о завершении процессов, система Android учитывает их относительную важность для пользователя. Например, она с большей вероятностью завершит процесс, выполняющий действия, которые больше не отображаются на экране, чем процесс, выполняющий видимые действия. Таким образом, решение о завершении процесса зависит от состояния компонентов, работающих в этом процессе.
Подробности жизненного цикла процесса и его связи с состояниями приложения обсуждаются в разделе «Процессы и жизненный цикл приложения» .
Нити
При запуске приложения система создает для него поток выполнения, называемый основным потоком . Этот поток очень важен, поскольку он отвечает за отправку событий соответствующим виджетам пользовательского интерфейса, включая события отрисовки. Кроме того, это почти всегда поток, в котором ваше приложение взаимодействует с компонентами из пакетов android.widget и android.view из Android UI toolkit. По этой причине основной поток иногда называют потоком пользовательского интерфейса . Однако при определенных обстоятельствах основной поток приложения может не совпадать с потоком пользовательского интерфейса. Для получения дополнительной информации см. раздел «Аннотации потоков» .
Система не создает отдельный поток для каждого экземпляра компонента. Все компоненты, работающие в одном процессе, создаются в потоке пользовательского интерфейса, и системные вызовы к каждому компоненту обрабатываются из этого потока. Следовательно, методы, реагирующие на системные обратные вызовы — такие как onKeyDown() для сообщения о действиях пользователя или метод обратного вызова жизненного цикла — всегда выполняются в потоке пользовательского интерфейса процесса.
For example, when the user touches a button on the screen, your app's UI thread dispatches the touch event to the widget, which in turn sets its pressed state and posts an invalidate request to the event queue. The UI thread dequeues the request and notifies the widget to redraw itself.
Если вы не реализуете свое приложение должным образом, эта однопоточная модель может привести к низкой производительности, когда ваше приложение выполняет интенсивную работу в ответ на взаимодействие с пользователем. Выполнение длительных операций в потоке пользовательского интерфейса, таких как доступ к сети или запросы к базе данных, блокирует весь пользовательский интерфейс. Когда поток заблокирован, никакие события, включая события отрисовки, не могут быть отправлены.
From the user's perspective, the application appears to stop responding. Even worse, if the UI thread is blocked for more than a few seconds, the user is presented with the " application not responding " (ANR) dialog. The user might then decide to quit your application or even uninstall it.
Bear in mind that the Android UI toolkit is not thread-safe. So, don't manipulate your UI from a worker thread. Do all manipulation to your user interface from the UI thread. There are two rules to Android's single-thread model:
- Не блокируйте поток пользовательского интерфейса.
- Не используйте инструментарий пользовательского интерфейса 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() . Дополнительную информацию см. в разделе «Обзор служб» .