Message queue java android

Android Thread Message Looper Handler Example

Android’s message queue and queue looper are aimed at the specific thread, a thread can have it’s own message queue and queue looper.

1. Android Message Queue And Looper Introduction.

  1. If you want to send messages between different threads, you need to add a message object in that thread’s message queue. Then the queue looper will fetch the message and process it.
  2. In android development, Activity is commonly used as the main thread. Android OS will create a message queue and queue looper for the main thread automatically.
  3. So you can use Handler to send messages to the Activity class to let it modify the UI component.
  4. Because the UI component is thread-unsafe, only the main thread can modify it.
  5. Please read Android Handler Example to learn more.

2. How To Create Child Thread’s Message Queue And Looper.

  1. However, the worker thread created by default has no message queue and message looper, If you want the worker thread to have a message queue and message looper, you can follow the below steps.
  2. Call Looper.prepare() to create the message queue in the thread.
  3. Create a thread-specified Handler that handles messages in the message queue.
  4. Call Looper.loop() to enter the message loop.
  5. If you want the worker thread to quit the message loop, please call Handler.getLooper().quit().

3. Android Child Thread Message Queue And Looper Example.

If you can not watch the above video, you can see it on the youtube URL https://youtu.be/4hxQ5f0NoR8

  1. From the above video, you can see the below steps.
  2. When the first two buttons are clicked, the main thread will send a message object to the worker thread message queue.
  3. The worker thread read the message object out from the queue and sends a message to the main thread also.
  4. The main thread will display different text according to the worker thread sent messages.
  5. After the “quit child thread looper” button is clicked, the worker thread message looper stopped. And worker thread can not handle any messages. So the text view content will not change also.
  6. activity_child_thread_handler_looper.xml
package com.dev2qa.example; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; public class ChildThreadHandlerLooperActivity extends AppCompatActivity < private int MAIN_THREAD_TASK_1 = 1; private int MAIN_THREAD_TASK_2 = 2; private int CHILD_THREAD_QUIT_LOOPER = 3; private Handler mainThreadHandler; private MyWorkerThread workerThread = null; private Button runTaskOneButton; private Button runTaskTwoButton; private TextView taskStatusTextView; @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_child_thread_handler_looper); setTitle("dev2qa.com - Child Thread Looper Handler Example"); // Create and start the worker thread. workerThread = new MyWorkerThread(); workerThread.start(); // Handle message from main thread message queue. mainThreadHandler = new Handler(Looper.getMainLooper())< @Override public void handleMessage(Message msg) < Log.i("MAIN_THREAD", "Receive message from child thread."); if(msg.what == MAIN_THREAD_TASK_1) < // If task one button is clicked. taskStatusTextView.setText("Task one execute."); >else if(msg.what == MAIN_THREAD_TASK_2) < // If task two button is clicked. taskStatusTextView.setText("Task two execute."); >else if(msg.what == CHILD_THREAD_QUIT_LOOPER) < // If quit child thread looper button is clicked. taskStatusTextView.setText("Quit child thread looper."); >> >; // Get run task buttons. runTaskOneButton = (Button)findViewById(R.id.runTaskOneButton); runTaskTwoButton = (Button)findViewById(R.id.runTaskTwoButton); // Set on click listener to each button. runTaskOneButton.setOnClickListener(new View.OnClickListener() < @Override public void onClick(View view) < // When click this button, create a message object. Message msg = new Message(); msg.what = MAIN_THREAD_TASK_1; // Use worker thread message Handler to put message into worker thread message queue. workerThread.workerThreadHandler.sendMessage(msg); >>); // Please see comments for runTaskOneButton. runTaskTwoButton.setOnClickListener(new View.OnClickListener() < @Override public void onClick(View view) < Message msg = new Message(); msg.what = MAIN_THREAD_TASK_2; workerThread.workerThreadHandler.sendMessage(msg); >>); // Get status info TextView object. taskStatusTextView = (TextView)findViewById(R.id.taskStatusTextView); // Get the quit child thread looper button. Button quitChildThreadLooperButton = (Button)findViewById(R.id.quitChildThreaLooperButton); quitChildThreadLooperButton.setOnClickListener(new View.OnClickListener() < @Override public void onClick(View view) < // Click this button will quit child thread looper. workerThread.workerThreadHandler.getLooper().quit(); >>); > // This child thread class has it's own Looper and Handler object. private class MyWorkerThread extends Thread < // This is worker thread handler. public Handler workerThreadHandler; @Override public void run() < // Prepare child thread Lopper object. Looper.prepare(); // Create child thread Handler. workerThreadHandler = new Handler(Looper.myLooper())< @Override public void handleMessage(Message msg) < // When child thread handler get message from child thread message queue. Log.i("CHILD_THREAD", "Receive message from main thread."); Message message = new Message(); message.what = msg.what; // Send the message back to main thread message queue use main thread message Handler. mainThreadHandler.sendMessage(message); >>; // Loop the child thread message queue. Looper.loop(); // The code after Looper.loop() will not be executed until you call workerThreadHandler.getLooper().quit() Log.i("CHILD_THREAD", "This log is printed after Looper.loop() method. Only when this thread loop quit can this log be printed."); // Send a message to main thread. Message msg = new Message(); msg.what = CHILD_THREAD_QUIT_LOOPER; mainThreadHandler.sendMessage(msg); > > >
com.dev2qa.example I/MAIN_THREAD: Receive message from child thread. com.dev2qa.example I/MAIN_THREAD: Receive message from child thread. com.dev2qa.example I/MAIN_THREAD: Receive message from child thread. com.dev2qa.example I/MAIN_THREAD: Receive message from child thread. com.dev2qa.example I/MAIN_THREAD: Receive message from child thread. com.dev2qa.example I/MAIN_THREAD: Receive message from child thread.
com.dev2qa.example I/CHILD_THREAD: Receive message from main thread. com.dev2qa.example I/CHILD_THREAD: Receive message from main thread. com.dev2qa.example I/CHILD_THREAD: Receive message from main thread. com.dev2qa.example I/CHILD_THREAD: Receive message from main thread. com.dev2qa.example I/CHILD_THREAD: Receive message from main thread. com.dev2qa.example I/CHILD_THREAD: Receive message from main thread. com.dev2qa.example I/CHILD_THREAD: This log is printed after Looper.loop() method. Only when this thread loop quit can this log be printed.

Источник

Читайте также:  Jquery удалить css свойство

Как реализованы Looper, Handler и MessageQueue?

В предыдущих постах мы описали что такое и для чего используются Looper, Handler, и MessageQueue. Иногда на собеседованиях просят написать свою имплементацию этих сущностей. Хоть эти классы и считаются низкоуровневым Android API, они по большей части реализованы обычными средствами Java.

По своей сути Looper, Handler и MessageQueue реализуют шаблон producer/consumer. Тред-продюсер отправляет сообщения через Handler в коллекцию-буфер, реализованную классом MessageQueue. Тред-потребитель блокирован с помощью класса Looper, который ожидает и принимает сообщения из MessageQueue и передает их на обработку хэндлеру.

Первый этап использования этих сущностей – инициализация лупера, которая выполняется методом Looper.prepare() . Этот метод создает объект-looper вызовом приватного конструктора. При вызове конструктора также создается объект MessageQueue, который хранится в приватном поле класса Looper.

После этого метод prepare() сохраняет созданный объект в статическое поле типа ThreadLocal , имеющее package видимость.

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

Статический метод Looper.myLooper() просто достает лупер из переменной ThreadLocal:

public static @Nullable Looper myLooper() return sThreadLocal.get();
>

Метод Looper.myQueue() получает лупер методом myLooper() и возвращает поле queue:

public static @NonNull MessageQueue myQueue() return myLooper().mQueue;
>

В следующем посте разберемся, как реализовано добавление сообщений в очередь.

Тред-продюсер добавляет сообщение в очередь одним из методов post*() или sendMessage*() класса Handler .

Для начала вспомним, что Handler всегда связан с объектом Looper , а значит хэндлер имеет доступ к очереди сообщений ( MessageQueue ) лупера.

Методы post() , postAtTime() , postDelayed() добавляют в очередь сообщений объект Runnable , который будет выполнен тредом-потребителем.
Для этого сначала создается объект Message вызовом приватного метода getPostMessage(Runnable r) . getPostMessage() получает message из пула сообщений методом Message.obtain() и устанавливает runnable в поле callback.

private static Message getPostMessage(Runnable r) Message m = Message.obtain(); 
m.callback = r;
return m;
>

Message.obtain() возвращает объект message из пула, который представляет собой связный список максимальным размером 50 сообщений. Если все сообщения пула используются, то obtain() создает и возвращает новый объект message.

После создания объекта message методы post*() вызывают один из методов sendMessage*() , передавая параметрами созданное сообщение и свои аргументы time или delay .

Вызов метода sendMessage(Message m) делегируется в sendMessageDelayed(m, 0) .

sendMessageDelayed(Message m, long delayMillis) прибавляет значение параметра delayMillis к текущему времени и делегирует вызов в метод sendMessageAtTime(Message m, long uptimeMillis) .

sendMessageAtTime() вызывает приватный метод enqueueMessage() , который устанавливает текущий хэндлер в поле target класса Message и вызывает enqueueMessage() у класса MessageQueue . Этот метод имеет package видимость и не доступен в публичном api.

MessageQueue – это связный список, реализованный с помощью поля next класса Message , которое ссылается на следующее сообщение в списке. Поле next также имеет package видимость.
Сообщения в MessageQueue отсортированы по возрастанию значения поля Message.when. Метод enqueueMessage() проходит по очереди, проверяя значение when каждого из сообщений и вставляет новое сообщение в положенное место очереди.
Код вставки сообщения в очередь в методе enqueueMessage() заключен в synchronized блок, который синхронизирован на this .

В предыдущих постах мы описали инициализацию лупера на потоке-потребителе и реализацию добавления сообщения в очередь потоком-продюсером.
Разберемся, как поток-потребитель получает сообщения из очереди.

Для блокировки потока и ожидания сообщения используется метод loop() .
Метод loop() вызывает метод MessageQueue.next() , который блокирует текущий поток и ожидает появления следующего сообщения.

Метод next() реализует бесконечный цикл, на каждой итерации которого сравнивает текущее время со значением поля when объекта message в голове очереди.
Если SystemClock.uptimeMillis() ≥ msg.when , то next() возвращает сообщение.
Если SystemClock.uptimeMillis() < msg.when , то поток засыпает на время равное when - uptimeMillis .

Допустим поток вычислил when — uptimeMillis и заснул на минуту. Что будет, если хэндлер добавит в очередь новое сообщение со значением when — uptimeMillis равное 5 секунд, пока поток спит?
При вызове MessageQueue.enqueueMessage() сообщение добавляется в очередь и поток-потребитель пробуждается. Метод next() отрабатывает итерацию, в которой устанавливает новое значение времени пробуждения, равное 5 секундам.

Метод loop() , получив сообщение из next() , передает это сообщение на обработку хэндлеру, вызывая метод dispatchMessage(). Лупер получает хэндлер-обработчик из поля target :

msg.target.dispatchMessage(msg)

Метод Handler.dispatchMessage(Message msg) проверяет, установлено ли у message поле callback. В случае если сообщение имеет колбэк, хэндлер запускает его вызовом run() . Если колбэк равен null , хэндлер передает сообщение на обработку методу handleMessage(). По-умолчанию handleMessage() не делает ничего, и реализация этого метода отдается пользователям хэндлера.

Код метода loop() заключен в бесконечный цикл, поэтому после обработки сообщения, выполнение возвращается к вызову MessageQueue.next() и ожиданию следующего сообщения.
Лупер ожидает и обрабатывает сообщения, пока не будет вызван метод quit() или quitSafe() .

Источник

Оцените статью