1. Введение
Каждый, кто пробует разработку под Android, обнаруживает важность параллелизма . Единственный способ создать отзывчивое приложение — оставить поток пользовательского интерфейса настолько свободным, насколько это возможно, позволяя выполнять всю тяжелую работу асинхронно фоновыми потоками.
Из-за дизайна Android управление потоками с использованием только пакетов java.lang.thread
и java.util.concurrent
может быть очень сложным. Использование низкоуровневых потоковых пакетов с Android означает, что вам нужно беспокоиться о сложной синхронизации, чтобы избежать условий гонки. К счастью, сотрудники Google проделали тяжелую работу и создали несколько отличных инструментов, облегчающих нашу работу: AsyncTask
, IntentService
, Loader
, AsyncQueryHandler
и CursorLoader
, а также классы HaMeR Handler
, Message
и Runnable
. Есть много отличных вариантов для выбора, каждый со своими плюсами и минусами.
Много было сказано об объекте AsyncTask
, и многие люди используют его в качестве решения серебряной пули для параллелизма на Android. Он чрезвычайно полезен для коротких операций, прост в реализации и, вероятно, является наиболее популярным подходом к параллелизму на Android. Если вы хотите узнать больше об AsyncTask
, ознакомьтесь со следующими сообщениями AsyncTask
+.
Однако AsyncTask
не должен быть единственным инструментом на вашем поясе инструментов.
Для длительных операций, для сложных проблем параллелизма или для достижения большей эффективности в некоторых ситуациях вы должны выбрать другое решение. Если вам нужно больше гибкости или эффективности, чем обеспечивает AsyncTask
, вы можете использовать платформу HaMeR ( Handler
, Message
& Runnable
). В этом руководстве мы рассмотрим структуру HaMeR, одну из самых мощных моделей параллелизма, доступных на Android, и узнаем, когда и как ее использовать. В следующем уроке я покажу вам, как кодировать приложение, чтобы опробовать некоторые возможности HaMeR.
В следующем разделе будет представлена важность фоновых потоков для системы Android. Если вы знакомы с этой концепцией, не стесняйтесь пропустить ее и перейдите непосредственно к обсуждению структуры HaMeR в разделе 3.
2. Отзывчивость через фоновые потоки
Когда приложение Android запускается, первый поток, созданный его процессом, является основным потоком, также известным как поток пользовательского интерфейса, который отвечает за обработку всей логики пользовательского интерфейса. Это самая важная тема приложения. Он отвечает за обработку всего взаимодействия с пользователем, а также за «связывание» движущихся частей приложения. Android относится к этому очень серьезно, и если ваш поток пользовательского интерфейса застревает, работая над задачей дольше нескольких секунд, приложение вылетает.
[Поток пользовательского интерфейса] очень важен, потому что он отвечает за отправку событий в соответствующие виджеты пользовательского интерфейса, включая события рисования. Это также поток, в котором ваше приложение взаимодействует с компонентами из набора инструментов Android UI (компоненты из пакетов
android.widget
иandroid.view
). Таким образом, основной поток также иногда называют потоком пользовательского интерфейса. — Процессы и потоки , Руководство разработчика Android
Проблема в том, что почти весь код в приложении Android будет по умолчанию выполняться в потоке пользовательского интерфейса. Поскольку задачи в потоке выполняются последовательно, это означает, что ваш пользовательский интерфейс может «зависнуть» и перестать отвечать на запросы при обработке какой-либо другой работы.
Длительные задачи, вызываемые в пользовательском интерфейсе, вероятно, будут фатальными для вашего приложения, и появится диалоговое окно ANR (приложение не отвечает). Даже небольшие задачи могут поставить под угрозу взаимодействие с пользователем, поэтому правильный подход — удалить как можно больше работы из потока пользовательского интерфейса с помощью фоновых потоков. Как уже было сказано, есть много способов решить эту проблему, и мы исследуем инфраструктуру HaMeR, одно из основных решений, предоставляемых Android для решения этой проблемы.
3. Рамки HaMeR
Платформа HaMeR позволяет фоновым потокам отправлять сообщения или публиковать исполняемые объекты в поток пользовательского интерфейса и в MessageQueue
любого другого потока через обработчики. HaMeR относится к Handler
, Message
и выполнению. Есть также некоторые другие важные классы, которые работают вместе с HaMeR: Looper
и MessageQueue
. Вместе эти объекты отвечают за облегчение управления потоками в Android, заботятся о синхронизации и предоставляют простые методы для фоновых потоков для взаимодействия с пользовательским интерфейсом и другими потоками.
Вот как классы в платформе HaMeR сочетаются друг с другом.
-
Looper
запускает цикл сообщений в потоке, используяMessageQueue
. -
MessageQueue
содержит список сообщений, которые должны быть отправленыLooper
. -
Handler
позволяет отправлять и обрабатыватьMessage
иRunnable
вMessageQueue
. Может использоваться для отправки и обработки сообщений между потоками. -
Message
содержит описание и данные, которые могут быть отправлены обработчику. -
Runnable
представляет задачу для выполнения.
С помощью инфраструктуры HaMeR потоки могут отправлять сообщения или публиковать выполняемые объекты либо себе, либо потоку пользовательского интерфейса. HaMeR также способствует взаимодействию фоновых потоков через Handler
.
3.1. Класс Handler
Handler
— рабочая лошадка HaMeR. Он отвечает за отправку объектов Message
(сообщение данных) и публикацию объектов Runnable
(сообщение задачи) в MessageQueue
связанный с MessageQueue
. После доставки задач в очередь, обработчик получает объекты из Looper
и обрабатывает сообщения в соответствующее время, используя связанный с ним Handler
.
Handler
может использоваться для отправки или публикации объектов Message
и Runnable
между потоками, если такие потоки совместно используют один и тот же процесс. В противном случае будет необходимо создать межпроцессное взаимодействие (IPC) , методологию, которая выходит за рамки данного руководства.
Создание обработчика
Handler
всегда должен быть связан с Looper
, и это соединение должно быть установлено во время его создания. Если вы не предоставите Looper
Handler
, он будет привязан к Looper
текущего потока.
1
2
3
4
5
|
// Handler uses current Thread’s Looper
Handler handler = new Handler();
// Handler uses the Looper provides
Handler handler = new Handler(Looper);
|
Имейте в виду, что Handler
всегда связан с Looper
, и это соединение является постоянным и не может быть изменено после установления. Однако поток Looper
может иметь ассоциации с несколькими Handler
. Также важно отметить, что Looper
должен быть активным до того, как он будет связан с Handler
.
3.2. Looper и MessageQueue
Совместная работа между Looper
и MessageQueue
в потоке Java создает цикл задач, которые обрабатываются последовательно. Такой цикл будет поддерживать поток, пока он ждет, чтобы получить больше задач. С потоком может быть связан только один Looper
и один MessageQueue
; однако для каждого потока может быть несколько обработчиков. Обработчики отвечают за обработку задач в очереди, и каждая задача знает, какой обработчик отвечает за ее обработку.
3.3. Подготовка темы для HaMeR
Пользовательский интерфейс или основной поток является единственным видом потока, который по умолчанию уже имеет Handler
, Looper
и MessageQueue
. Другие потоки должны быть подготовлены с этими объектами, прежде чем они смогут работать с платформой HaMeR. Сначала нам нужно создать Looper
который уже включает MessageQueue
и прикрепить его к потоку. Вы можете сделать это с подклассом Thread
следующим образом.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
// Preparing a Thread for HaMeR
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
// adding and preparing the Looper
Looper.prepare();
// the Handler instance will be associated with Thread’s Looper
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
// Starting the message queue loop using the Looper
Looper.loop();
}
}
|
Однако более просто использовать вспомогательный класс с именем HandlerThread
, который имеет Looper
и MessageQueue
встроенный в MessageQueue
Java, и готов получить обработчик.
1
2
3
4
5
6
7
8
|
// The HandlerThread class includes a working Looper
public class HamerThread extends HandlerThread {
// you just need to add the Handler
private Handler handler;
public HamerThread(String name) {
super(name);
}
}
|
4. Проводка Runnables
Runnable
— это интерфейс Java, который имеет много применений. Это можно понимать как отдельную задачу, выполняемую в Thread
. У него есть один метод, который должен быть реализован, Runnable.run()
, для выполнения задачи.
1
2
3
4
5
6
7
|
// Declaring a Runnable
Runnable r = new Runnable() {
@Override
public void run() {
// the task goes here
}
};
|
Существует несколько вариантов публикации Runnable
на Handler
.
-
Handler.post(Runnable r)
: добавитьRunnable
вMessageQueue
. -
Handler.postAtFrontOfQueue(Runnable r)
: добавитьRunnable
в началоMessageQueue
. -
Handler. postAtTime(Runnable r, long timeMillis)
Handler. postAtTime(Runnable r, long timeMillis)
: добавьтеRunnable
вMessageQueue
который будет вызываться в определенное время. -
Handler. postDelayed(Runnable r, long delay)
Handler. postDelayed(Runnable r, long delay)
: добавьтеRunnable
в очередь, которая будет вызываться по истечении определенного времени.
1
2
3
4
5
6
7
8
9
|
// posting a Runnable on a Handler
Handler handler = new Handler();
handler.post(
new Runnable() {
@Override
public void run() {
// task goes here
}
});
|
Также можно использовать обработчик пользовательского интерфейса по умолчанию для публикации Runnable
вызывающего Activity.runOnUiThread()
.
1
2
3
4
5
6
7
8
|
// posting Runnable using the UI Handler
Activity.runOnUiThread(
new Runnable() {
@Override
public void run(){
// task to perform
}
});
|
Важно помнить некоторые вещи о Runnable
s. В отличие от Message
, Runnable
не может быть переработан — как только его работа выполнена, он мертв. Поскольку он является частью стандартного пакета Java, Runnable
не зависит от Handler
и может вызываться в стандартном Thread
с помощью метода Runnable.run()
. Тем не менее, этот подход не имеет ничего общего с платформой HaMeR и не будет разделять ее преимущества.
5. Отправка сообщений
Объект Message
определяет сообщение, содержащее описание и некоторые произвольные данные, которые могут быть отправлены и обработаны с помощью Handler
. Message
идентифицируется с помощью int
определенного в Message.what()
. Message
может содержать два других аргумента int
и Object
для хранения данных разных типов.
-
Message.what
:int
идентифицирующийMessage
-
Message.arg1
: произвольный аргументint
-
Message.arg2
: произвольный аргументint
-
Message.obj
:Object
для хранения различных видов данных
Когда вам нужно отправить сообщение, а не создавать его с нуля, рекомендуется использовать извлеченное сообщение непосредственно из глобального пула с помощью Message.obtain()
или Handler.obtainMessage()
. Существуют несколько различных версий этих методов, которые позволяют вам получить Message
соответствии с вашими потребностями.
Обычно Handler.obtainMessage()
используется для отправки сообщения в фоновый поток. Вы будете использовать Handler
связанный с Looper
этого потока, чтобы получить Message
и отправить его в фоновый поток, как в примере ниже.
1
2
3
4
5
6
|
int what = 0;
String hello = «Hello!»;
// Obtaining Message associated with background Thread
Message msg = handlerBGThread.obtainMessage(what, hello);
// Sending the Message to background Thread
handlerBGThread.sendMessage(msg);
|
В классе Message
есть много классных методов, и я советую вам поближе взглянуть на документацию .
5.1. sendMessage()
Аналогично тому, как мы можем публиковать Runnable
, есть несколько вариантов отправки Message
:
-
Handler.sendMessage( Message msg )
: добавитьMessage
вMessageQueue
. -
Handler.sendMessageAtFrontOfQueue( Message msg )
: добавитьMessage
в началоMessageQueue
. -
Handler.sendMessageAtTime( Message msg, long timeInMillis )
: добавитьMessage
в очередь в определенное время. -
Handler.sendMessageDelayed ( Message msg, long timeInMillis )
: добавитьMessage
в очередь по истечении определенного промежутка времени.
5.2. Обработка сообщений с помощью обработчика
Объекты Message
отправленные Looper
, обрабатываются обработчиком с помощью метода Handler.handleMessage
. Все, что вам нужно сделать, это расширить класс Handler
и переопределить этот метод для обработки сообщений.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
public class MessageHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
// handle ‘Hello’ msg
case 0:{
String hello = (String) msg.obj;
System.out.println(hello);
break;
}
}
}
}
|
6. Заключение
Платформа HaMeR может помочь улучшить параллельный код вашего приложения Android. AsyncTask
это может показаться странным, если сравнивать с простотой AsyncTask
, но открытость HaMeR может быть преимуществом, если используется правильно.
Помнить:
-
Handler.post()
используются, когда отправители знают, какие операции нужно выполнить. -
Handler.sendMessage ()
используются, когда получатель знает, какую операцию выполнить.
Чтобы узнать больше о многопоточности в Android, вас может заинтересовать книга Андерса Горанссона « Эффективная многопоточность Android : методы асинхронной обработки для приложений Android ».
6.1. Что дальше?
В следующем уроке мы продолжим изучение инфраструктуры HaMeR с практическим подходом, создав приложение, демонстрирующее различные способы использования этой среды параллелизма Android. Мы создадим это приложение с нуля, пробуя различные возможности, такие как связь между потоками, общение с потоком пользовательского интерфейса, а также отправка сообщений и публикация Runnable
с задержками.
До скорого!