Skip to content

Most visited

Recently visited

navigation

Процессы и потоки

Когда компонент приложения запускается при отсутствии других работающих компонентов , система Android запускает новый процесс Linux для приложения с одним потоком выполнения. По умолчанию все компоненты одного приложения работают в одном процессе и потоке (называется «главным потоком»). Если компонент приложения запускается при наличии процесса для этого приложения (так как существует другой компонент из приложения), тогда компонент запускается в этом процессе и использует тот же поток выполнения. Однако можно организовать выполнение других компонентов приложения в отдельных процессах и создавать дополнительный поток для любого процесса.

В этом документе обсуждается работа процессов и потоков в приложении Android.

Процессы

По умолчанию все компоненты одного приложения работают в одном процессе, и большинство приложений не должно менять это поведение. Однако, если необходимо контролировать, к какому процессу принадлежат определенный компонент, можно сделать это в файле манифеста.

Запись манифеста для каждого типа элементов компонента —<activity>, <service>, <receiver> и <provider> —поддерживает атрибут android:process, позволяющий задавать процесс, в котором следует выполнять этот компонент. Можно установить этот атрибут так, чтобы каждый компонент выполнялся в собственном процессе, или так, чтобы только некоторые компоненты совместно использовали один процесс. Можно также настроить процесс android:process так, чтобы компоненты разных приложений выполнялись в одном процессе, при условии что приложения совместно используют один идентификатор пользователя Linux и выполняют вход с одним сертификатом.

Элемент <application> также поддерживает атрибут android:process, позволяющий задать значение по умолчанию, которое применяется ко всем компонентам.

Android может остановить процесс в некоторой точке, когда не хватает памяти и она необходима другим процессам, которые обслуживают пользователя в данный момент. Работа компонентов приложения, работающих в этом процессе, последовательно останавливается. Процесс для этих компонентов запускается повторно, когда для них появляется работа.

Принимая решение о прерывании процессов, система Android взвешивает их относительную важность для пользователя. Например, более вероятно выключение процессов, содержащих действия, которые не отображаются на экране, по сравнению с процессом, содержащим видимые действия. Следовательно, решение о прерывании процесса зависит от состояния компонентов, работающих в этом процессе. Ниже обсуждаются правила, на основании которых принимается решение о выборе прерываемых процессов.

Жизненный цикл процесса

Система Android пытается сохранять процесс приложения как можно дольше, но в конечном счете вынуждена удалять старые процессы, чтобы восстановить память для новых или более важных процессов. Чтобы определить, какие процессы сохранить, а какие удалить, система помещает каждый процесс в «иерархию важности» на основе компонентов, выполняющихся в процессе, и состояния этих компонентов. Процессы с самым низким уровнем важности исключаются в первую очередь, затем исключаются процессы следующего уровня важности и т. д., насколько это необходимо для восстановления ресурсов системы.

В иерархии важности предусмотрено пять уровней. В следующем списке представлены различные типы процессов в порядке важности (первый процесс является наиболее важным и удаляется в последнюю очередь):

  1. Процесс переднего плана

    Процесс, необходимый для текущей деятельности пользователя. Процесс считается процессом переднего плана, если выполняется любое из следующих условий:

    • Он содержит действие Activity, с которым взаимодействует пользователь (вызван метод Activity onResume()).
    • Он содержит службу Service, связанную с действием, с которым взаимодействует пользователь.
    • Он содержит службу Service, которая выполняется "на переднем плане", — службу, которая называется startForeground().
    • Он содержит службуService, которая выполняет один из обратных вызовов жизненного цикла (onCreate(), onStart() или onDestroy()).
    • Он содержит ресивер BroadcastReceiver, который выполняет метод onReceive().

    Обычно одновременно работает лишь несколько процессов переднего плана. Они уничтожаются только в крайнем случае, если памяти остается так мало, что они не могут продолжать совместную работу. Обычно в этот момент устройство достигло состояния разбиения памяти на страницы, поэтому для того, чтобы пользовательский интерфейс откликался на действия пользователя, необходимо удаление некоторых процессов на переднем плане.

  2. Видимые процессы

    Процессы, которые не содержат компонентов переднего плана, но могут влиять на отображение на экране. Процесс считается видимым, если выполняется любое из следующих условий:

    • Он содержит действие Activity, которое не находится на переднем плане, но видно пользователю (вызван метод onPause()). Например, это может происходить, если действие на переднем плане запустило диалоговое окно, которое позволяет видеть предыдущее действие позади него.
    • Он содержит службу Service, связанную с видимым действием или действием переднего плана.

    Видимый процесс считается исключительно важным, его следует удалять только в случае, если требуется сохранить работу всех процессов переднего плана.

  3. Служебный процесс

    Процесс, который выполняет службу, запущенную с помощью метода startService(), и не попадает ни в одну из двух категорий более высокого уровня. Хотя служебные процессы не связаны непосредственно с тем, что видит пользователь, они обычно выполняют важные для пользователя действия (например, воспроизводят музыку в фоновом режиме или загружают данные в сеть), поэтому система сохраняет их выполнение, если памяти достаточно для их работы наряду со всеми видимыми процессами и процессами переднего плана.

  4. Фоновый процесс

    Процесс, содержащий действия, которые не видны пользователю в настоящее время (вызван метод onStop() действия). Эти процессы не оказывают непосредственного воздействия на работу пользователя, и система может удалить их в любой момент, чтобы освободить память для процессов переднего плана, видимых или служебных процессов. Обычно выполняется множество фоновых процессов, поэтому они хранятся в списке LRU (недавно использованные), чтобы процессы, содержащие самые недавние действия, которые видел пользователь, удалялись в последнюю очередь. Если для действия правильно реализованы методы жизненного цикла, и действие сохраняет текущее состояние, удаление процесса этого действия не оказывает видимого воздействия на работу пользователя, так как когда пользователь возвращается к этому действию, оно восстанавливает все элементы своего видимого состояния. Информацию о сохранении и восстановлении состояния см. в документе Действия .

  5. Пустой процесс

    Процесс, не содержащий никаких компонентов активного приложения. Единственная причина сохранять процесс такого типа — это кэширование, которое улучшает время следующего запуска компонента в этом процессе. Система часто удаляет эти процессы для равномерного распределения всех системных ресурсов между кэшем процесса и кэшем базового ядра.

Система Android относит процесс к максимально высокому уровню на основе важности компонентов, активных в процессе в текущее время. Например, если процесс содержит служебное и видимое действие, процесс считается видимым, а не служебным процессом.

Кроме того, уровень процесса может быть повышен, поскольку имеются другие процессы, зависимые от него. Например, процесс, обслуживающий другой процесс, не может иметь уровень ниже уровня обслуживаемого процесса. Например, если поставщик контента в процессе A обслуживает клиента в процессе B или служебный процесс A связан с компонентом в процессе B, процесс A всегда считается не менее важным, чем процесс B.

Так как процесс, выполняющий службу, оценивается выше процесса с фоновыми действиям, действие, запускающее долговременную операцию, может запустить службу для этой операции, а не просто создать рабочий поток, особенно в случае, если операция продлится дольше действия. Например, действие, которое загружает изображение на веб-сайт, должно запустить службу для выполнения загрузки, так что загрузка может продолжаться в фоновом режиме даже после выхода пользователя из действия. Использование службы гарантирует, что операция будет иметь приоритет не ниже «служебного процесса», независимо от того, что происходит с действием. По этой же причине ресиверы должны использовать службы, а не просто ставить в поток операции, требующие много времени для выполнения.

Потоки

При запуске приложения система создает поток выполнения для приложения, который называется «главным». Этот поток очень важен, так как он отвечает за диспетчеризацию событий на виджеты соответствующего интерфейса пользователя, включая события графического представления. Он также является потоком, в котором приложение взаимодействует с компонентами из набора инструментов пользовательского интерфейса Android (компонентами из пакетов android.widget и android.view). По существу, главный поток — это то, что иногда называют потоком пользовательского интерфейса.

Система не создает отдельного потока для каждого экземпляра компонента. Все компоненты, которые выполняются в одном процессе, создают экземпляры в потоке пользовательского интерфейса, и системные вызовы каждого компонента отправляются из этого потока. Поэтому методы, которые отвечают на системные обратные вызовы (такие как метод onKeyDown() для сообщения о действиях пользователя или метод обратного вызова жизненного цикла), всегда выполняются в потоке пользовательского интерфейса процесса.

Например, когда пользователь нажимает кнопку на экране, поток пользовательского интерфейса вашего приложения отправляет событие нажатия в виджет, который, в свою очередь, устанавливает кнопку в нажатое состояние и отправляет запрос на аннулирование в очередь событий. Поток пользовательского интерфейса исключает запрос из очереди и уведомляет виджет, что он должен отобразиться повторно.

Когда приложение выполняет интенсивную работу в ответ на действия пользователя, эта одиночная модель потока может показывать плохую производительность, если приложение реализовано неправильно. То есть, если все происходит в потоке пользовательского интерфейса, выполнение долговременных операций, таких как сетевой доступ или запросы к базе данных, будет блокировать весь пользовательский интерфейс. Когда поток заблокирован, не могут обрабатываться никакие события, включая события изменения отображения. С точки зрения пользователя приложение выглядит зависшим. Хуже того, если поток пользовательского интерфейса заблокирован более нескольких секунд (в настоящее время около 5 секунд), отображается печально известное диалоговое окно «приложение не отвечает». После этого недовольный пользователь может выйти из вашего приложения и удалить его.

Кроме того, набор инструментов пользовательского интерфейса Andoid не является потокобезопасным. Поэтому, вы не должны работать с пользовательским интерфейсом из рабочего потока. Манипуляции с пользовательским интерфейсом необходимо выполнять из потока пользовательского интерфейса. Таким образом, существует только два правила однопоточной модели Android:

  1. Не блокируйте поток пользовательского интерфейса
  2. Не обращайтесь к набору инструментов пользовательского интерфейса Android снаружи потока пользовательского интерфейса

Рабочие потоки

Вследствие описанной выше однопоточной модели для динамичности пользовательского интерфейса ваших приложений очень важно не блокировать поток пользовательского интерфейса. Если требуется выполнять операции, занимающие некоторое время, обязательно выполняйте их в отдельных потоках (»фоновых» или «рабочих» потоках).

Например, ниже приведен код контроля нажатий, который загружает изображение из отдельного потока и отображает их в виджете ImageView:

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
            mImageView.setImageBitmap(b);
        }
    }).start();
}

На первый взгляд, он должен работать хорошо, так как он создает новый поток для обработки сетевой операции. Однако, он нарушает второе правило однопоточной модели: не обращайтесь к набору инструментов пользовательского интерфейса Android снаружи потока пользовательского интерфейса — этот пример изменяет ImageView из рабочего потока, а не из потока пользовательского интерфейса. Это может привести к неопределенному и непредвиденному поведению, отследить которое будет трудно.

Для устранения этой проблемы Android предлагает несколько путей доступа к потоку пользовательского интерфейса из других потоков. Ниже приведен список полезных методов:

Например, можно исправить приведенный выше код с помощью метода View.post(Runnable):

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

Теперь реализация является потокобезопасной: сетевая операция выполняется из отдельного потока, тогда как ImageView работает из потока пользовательского интерфейса.

Однако по мере роста сложности, код такого типа может становиться запутанным и сложным для поддержания. Чтобы обрабатывать более сложные взаимодействия с рабочим потоком, можно использовать метод Handler в рабочем потоке для обработки сообщений, поступающих из потока пользовательского интерфейса. Вероятно, самым лучшим решением является расширение класса AsyncTask, которое упрощает выполнение заданий рабочего потока, которые должны взаимодействовать с пользовательским интерфейсом.

Использование AsyncTask

Метод AsyncTask позволяет выполнять асинхронную работу в пользовательском интерфейсе. Он выполняет операции блокирования в рабочем потоке и затем публикует результаты в потоке пользовательского интерфейса без необходимости самостоятельно обрабатывать потоки и/или обработчики.

Для использования этого метода необходимо создать подкласс AsyncTask и реализовать метод обратного вызова doInBackground(), который работает в пуле фоновых потоков. Чтобы обновить пользовательский интерфейс, следует реализовать метод onPostExecute(), который доставляет результат из doInBackground() и работает в потоке пользовательского интерфейса, так что вы можете безопасно обновлять пользовательский интерфейс. Задача выполняется через вызов метода execute() из потока пользовательского интерфейса.

Например, можно реализовать предыдущий пример с помощью метода AsyncTask следующим образом:

public void onClick(View v) {
    new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
    /** The system calls this to perform work in a worker thread and
      * delivers it the parameters given to AsyncTask.execute() */
    protected Bitmap doInBackground(String... urls) {
        return loadImageFromNetwork(urls[0]);
    }

    /** The system calls this to perform work in the UI thread and delivers
      * the result from doInBackground() */
    protected void onPostExecute(Bitmap result) {
        mImageView.setImageBitmap(result);
    }
}

Теперь пользовательский интерфейс защищен и код стал проще, так как работа разделена на часть, которая должна выполняться в рабочем потоке, и часть, которая должна выполняться в потоке пользовательского интерфейса.

Прочитайте статью AsyncTask, чтобы полностью понять использование этого класса. Здесь приведен краткий обзор его работы:

Предупреждение! Другая проблема, с которой вы можете столкнуться при использовании рабочего потока, состоит в непредсказуемом перезапуске действия вследствие изменения конфигурации в режиме выполнения, (например, когда пользователь изменяет ориентацию экрана), что может разрушить рабочий поток. Чтобы увидеть, как можно сохранить задание во время одного из подобных перезапусков и как правильно отменить задание при разрушении действия, изучите исходный код примера приложения Shelves.

Потокобезопасные методы

В некоторых ситуациях реализованные методы могут вызываться из нескольких потоков и, следовательно, должны быть написаны с сохранением потокобезопасности.

В первую очередь это относится к методам, которые можно вызывать удаленно, например, к методам в связанной службе. Когда вызов метода реализуется в классе IBinder, происходящем из того же процесса, в котором выполняется IBinder, метод выполняется в потоке вызывающего метода. Однако, когда вызов происходит из другого процесса, метод выполняется в потоке, выбранном из пула потоков, которые система поддерживает в том же процессе, что и IBinder (он не выполняется в потоке пользовательского интерфейса процесса). Например, поскольку метод onBind() службы будет вызываться из потока пользовательского интерфейса процесса службы, методы, реализованные в объекте, который возвращает onBind() (например, подкласс, который реализует методы RPC), будут вызываться из потоков в пуле. Так как служба может иметь несколько клиентов, несколько потоков из пула могут одновременно использовать один и тот же метод IBinder. Поэтому методы IBinder должны быть реализованы с сохранением потокобезопасности.

Аналогичным образом поставщик контента может получать запросы данных, которые происходят из другого процесса. Хотя классы ContentResolver и ContentProvider скрывают подробности управления взаимодействием процессов, методы ContentProvider, которые отвечают на эти запросы, —методы query(), insert(), delete(), update() и getType() —вызываются из пула потоков в процессе поставщика контента, а не в процессе потока пользовательского интерфейса. Поскольку эти методы могут вызываться из любого числа потоков одновременно, они также должны быть реализованы с сохранением потокобезопасности.

Взаимодействие процессов

Система Android предлагает механизм взаимодействия процессов (IPC) с помощью удаленного вызова процедуры (RPC), при котором метод вызывается действием или другим компонентом приложения, но выполняется удаленно (в другом процессе) с возвратом всех результатов вызывающему компоненту. Это влечет разложение вызова метода и его данных до уровня, понятного операционной системе, передачу его из локального процесса и адресного пространства удаленному процессу и адресному пространству, а затем повторную сборку и восстановление вызова. После этого возвращенные значения передаются в обратном направлении. Система Android содержит все коды для выполнения этих механизмов IPC, так что вы можете сосредоточиться на определении и реализации программного интерфейса RPC.

Для выполнения IPC приложение должно быть привязано к службе с помощью метода bindService(). Дополнительные сведения представлены в разделе Службы руководства для разработчиков.

This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Follow Google Developers on WeChat

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a short survey?
Help us improve the Android developer experience.
(Sep 2017 survey)