Когда запускается компонент приложения и в приложении не работают другие компоненты, система Android запускает новый процесс Linux для приложения с одним потоком выполнения. По умолчанию все компоненты одного и того же приложения выполняются в одном и том же процессе и потоке, называемом основным потоком.
Если компонент приложения запускается и для этого приложения уже существует процесс, поскольку другой компонент приложения уже запущен, то компонент запускается внутри этого процесса и использует тот же поток выполнения. Однако вы можете организовать запуск различных компонентов вашего приложения в отдельных процессах и создать дополнительные потоки для любого процесса.
В этом документе обсуждается, как процессы и потоки работают в приложении Android.
Процессы
По умолчанию все компоненты приложения выполняются в одном процессе, и большинство приложений не меняют эту ситуацию. Однако если вы обнаружите, что вам необходимо контролировать, какому процессу принадлежит определенный компонент, вы можете сделать это в файле манифеста.
Запись манифеста для каждого типа элемента компонента — <activity>
, <service>
, <receiver>
и <provider>
— поддерживает атрибут android:process
, который может указывать процесс, в котором выполняется компонент. Вы можете установить этот атрибут так, чтобы каждый компонент работает в своем собственном процессе или так, что некоторые компоненты используют общий процесс, а другие нет.
Вы также можете настроить android:process
так, чтобы компоненты разных приложений выполнялись в одном процессе, при условии, что приложения используют один и тот же идентификатор пользователя Linux и подписаны одними и теми же сертификатами.
Элемент <application>
также поддерживает атрибут android:process
, который можно использовать для установки значения по умолчанию, применимого ко всем компонентам.
Android может решить завершить процесс в какой-то момент, когда ресурсы потребуются другим процессам, которые более непосредственно обслуживают пользователя. Компоненты приложения, работающие в завершающемся процессе, соответственно уничтожаются. Процесс снова запускается для этих компонентов, когда для них есть работа.
Принимая решение о том, какие процессы завершить, система Android взвешивает их относительную важность для пользователя. Например, он легче завершает процесс, выполняющий действия, которые больше не видны на экране, по сравнению с процессом, выполняющим видимые действия. Таким образом, решение о завершении процесса зависит от состояния компонентов, работающих в этом процессе.
Подробности жизненного цикла процесса и его связи с состояниями приложения обсуждаются в разделе «Процессы и жизненный цикл приложения» .
Темы
При запуске приложения система создает поток выполнения приложения, называемый основным потоком . Этот поток очень важен, поскольку он отвечает за отправку событий соответствующим виджетам пользовательского интерфейса, включая события рисования. Кроме того, почти всегда это поток, в котором ваше приложение взаимодействует с компонентами из пакетов android.widget
и android.view
набора инструментов Android UI. По этой причине основной поток иногда называют потоком пользовательского интерфейса . Однако в особых обстоятельствах основной поток приложения может не совпадать с потоком его пользовательского интерфейса. Дополнительную информацию см. в разделе Аннотации к теме .
Система не создает отдельный поток для каждого экземпляра компонента. Все компоненты, выполняющиеся в одном процессе, создаются в потоке пользовательского интерфейса, и системные вызовы каждого компонента отправляются из этого потока. Следовательно, методы, которые реагируют на системные обратные вызовы, такие как onKeyDown()
для сообщения о действиях пользователя или метод обратного вызова жизненного цикла, всегда выполняются в потоке пользовательского интерфейса процесса.
Например, когда пользователь касается кнопки на экране, поток пользовательского интерфейса вашего приложения отправляет событие касания виджету, который, в свою очередь, устанавливает его нажатое состояние и отправляет запрос на недействительность в очередь событий. Поток пользовательского интерфейса удаляет запрос из очереди и уведомляет виджет о необходимости перерисовки.
Если вы не реализуете свое приложение должным образом, эта однопоточная модель может привести к снижению производительности, когда ваше приложение выполняет интенсивную работу в ответ на взаимодействие с пользователем. Выполнение длительных операций в потоке пользовательского интерфейса, таких как доступ к сети или запросы к базе данных, блокирует весь пользовательский интерфейс. Когда поток заблокирован, никакие события не могут быть отправлены, включая события рисования.
С точки зрения пользователя приложение зависает. Хуже того, если поток пользовательского интерфейса блокируется более чем на несколько секунд, пользователю отображается диалоговое окно « Приложение не отвечает » (ANR). Тогда пользователь может решить выйти из вашего приложения или даже удалить его.
Имейте в виду, что набор инструментов пользовательского интерфейса Android не является потокобезопасным. Поэтому не манипулируйте своим пользовательским интерфейсом из рабочего потока. Выполняйте все манипуляции с пользовательским интерфейсом из потока пользовательского интерфейса. В однопоточной модели Android существуют два правила:
- Не блокируйте поток пользовательского интерфейса.
- Не обращайтесь к набору инструментов пользовательского интерфейса Android извне потока пользовательского интерфейса.
Рабочие потоки
Из-за этой однопоточной модели для оперативности пользовательского интерфейса вашего приложения крайне важно не блокировать поток пользовательского интерфейса. Если вам нужно выполнить операции, которые не являются мгновенными, обязательно выполняйте их в отдельных фоновых или рабочих потоках. Просто помните, что вы не можете обновлять пользовательский интерфейс из любого потока, кроме пользовательского или основного потока.
Чтобы помочь вам следовать этим правилам, Android предлагает несколько способов доступа к потоку пользовательского интерфейса из других потоков. Вот список методов, которые могут помочь:
В следующем примере используется View.post(Runnable)
:
Котлин
fun onClick(v: View) { Thread(Runnable { // A potentially time consuming task. val bitmap = processBitMap("image.png") imageView.post { imageView.setImageBitmap(bitmap) } }).start() }
Ява
public void onClick(View v) { new Thread(new Runnable() { public void run() { // A potentially time consuming task. final Bitmap bitmap = processBitMap("image.png"); imageView.post(new Runnable() { public void run() { imageView.setImageBitmap(bitmap); } }); } }).start(); }
Эта реализация является потокобезопасной, поскольку фоновая операция выполняется из отдельного потока, а ImageView
всегда осуществляется из потока пользовательского интерфейса.
Однако по мере роста сложности операции такой код может усложниться и его будет сложно поддерживать. Чтобы обрабатывать более сложные взаимодействия с рабочим потоком, вы можете рассмотреть возможность использования Handler
в рабочем потоке для обработки сообщений, доставленных из потока пользовательского интерфейса. Полное объяснение того, как планировать работу в фоновых потоках и обмениваться данными с потоком пользовательского интерфейса, см. в разделе Обзор фоновой работы .
Потокобезопасные методы
В некоторых ситуациях реализуемые вами методы вызываются из более чем одного потока и поэтому должны быть написаны с учетом потокобезопасности.
В первую очередь это справедливо для методов, которые можно вызывать удаленно, например методов привязанного сервиса . Когда вызов метода, реализованного в IBinder
происходит в том же процессе, в котором выполняется IBinder
, метод выполняется в потоке вызывающего объекта. Однако когда вызов исходит из другого процесса, метод выполняется в потоке, выбранном из пула потоков, который система поддерживает в том же процессе, что и IBinder
. Он не выполняется в потоке пользовательского интерфейса процесса.
Например, в то время как метод onBind()
службы вызывается из потока пользовательского интерфейса процесса службы, методы, реализованные в объекте, который возвращает onBind()
, например подкласс, реализующий методы удаленного вызова процедур (RPC), вызываются из потоков. в бассейне. Поскольку служба может иметь более одного клиента, несколько потоков пула могут одновременно использовать один и тот же метод IBinder
, поэтому методы IBinder
должны быть реализованы так, чтобы быть потокобезопасными.
Аналогично, поставщик контента может получать запросы данных, исходящие из других процессов. Классы ContentResolver
и ContentProvider
скрывают детали управления межпроцессным взаимодействием (IPC), но методы ContentProvider
, отвечающие на эти запросы, — методы query()
, insert()
, delete()
, update()
и getType()
— вызываются из пула потоков процесса поставщика контента, а не из потока пользовательского интерфейса этого процесса. Поскольку эти методы могут быть вызваны из любого количества потоков одновременно, они также должны быть реализованы так, чтобы быть потокобезопасными.
Межпроцессное взаимодействие
Android предлагает механизм IPC с использованием RPC, в котором метод вызывается действием или другим компонентом приложения, но выполняется удаленно в другом процессе, при этом любой результат возвращается обратно вызывающему объекту. Это влечет за собой декомпозицию вызова метода и его данных до уровня, понятного операционной системе, передачу его из локального процесса и адресного пространства в удаленный процесс и адресное пространство, а затем повторную сборку и повторное выполнение вызова там.
Возвращаемые значения затем передаются в обратном направлении. Android предоставляет весь код для выполнения этих транзакций IPC, поэтому вы можете сосредоточиться на определении и реализации интерфейса программирования RPC.
Чтобы выполнить IPC, ваше приложение должно выполнить привязку к службе с помощью bindService()
. Дополнительную информацию см. в обзоре услуг .