- Android- Handlers, Loopers, MessageQueue Basics
- Handlers and Loopers Architecture:
- Handler Threads:
- Message Queue
- Looper:
- Handler:
- Message:
- How to send data to such a Handler thread?
- MessageQueue::next
- Adding Messages to Queue:
- MessageQueue.enqueueMessage(Message )
- Android. Собеседование #7. Многопоточность. Handler. Looper.
- 1. Особенности работы потоков в Android приложении?
- 2. Что такое Looper и MessageQueue?
- 3. Что такое Handler и HandlerThread?
- Understanding Android Core: Looper, Handler, and HandlerThread
- Use Cases:
- Let’s start the exploration/revision with a questionnaire.
Android- Handlers, Loopers, MessageQueue Basics
May 23, 2020 · 6 min read
Probably the most important APIs of Android suited for Multithreading and offloading the tasks to worker threads- Handlers and Loopers.
The Android architecture has the Main Thread AKA UI thread, which updates the UI after every 16ms frame. Failure to update within this window will reflect as the “lag”, and even worse if it fails for 5secs, then the “ Application not responding” shown to the user- marking the app crash. To avoid these types of issues, please resort to offloading the heavy tasks to worker threads.
To explain: th e Android architecture has one main/UI thread which should ideally only be responsible for updating the UI, and other tasks should be done on separate threads(called worker threads). These worker threads upon completion should send the result back to the main thread and the main thread can then use it to update the UI, etc.
There are some Android APIs to do so like Async tasks, Services(Intent services actually), Executors, etc, etc.
But there are even cooler APIs available and are the core of all the above-mentioned APIs- Handlers and Loopers. Even Async and Intent Services rely heavily on them. Let’s see how they work.
Handlers and Loopers Architecture:
For the thread to receive any message from other threads, the most common pattern we follow is that it should read from the common data structure(usually Queues), to which other threads can write. The same is being followed here.
Handler Threads:
These are the threads that can receive messages from any other thread. Main/UI thread is also a Handler thread.
As explained above, such a thread must need a Message Queue(data structure in which other threads can write to), and a mechanism to read this queue infinitely(until stopped).
Looper is a mechanism which loops through the message Queue infinitely until stopped.
Message Queue
Each HandlerThread has one Message Queue into which other threads write data to(via Handlers), and it has a Looper which keeps on reading from it.
Looper:
A Looper is associated with every handler Thread and a Message Queue.
It keeps on reading the MessageQueue for any new Messages, and once it reads a message it will delegate that to the corresponding Callback(Handler).
Handler:
The handler is the exposed API to work with Loopers/MessageQueues. It basically exposes methods to interact with the Looper architecture, like writing messages to MQ, Setting callbacks to handle the message once Looper reads it, etc, etc.
So Each handler will need a Looper(so that it knows in which MQ to write data to since Looper has the reference to the MQ).
Message:
The Messages which can be sent to the HandlerThread. To avoid the number of Messages initializes, there is a pool maintained. Every time the looper is stopped, or the message is read by Looper, it is recycled(i.e. its fields are cleared off), and added to the pool. Next time we want to create a new Message, we get it from the pool( Message.obtain() method does that). This way the Android prevents us to initialize new Message instances every time.
The message is the LinkedList:
Line 1: Its next points to the next Message.
Line 2: It has long when signifying the timeInMillis of when should message be processed/read.
Please note that the LinkedList of these messages should be processed by their when, so it’s sorted on when.
When adding a new msg, we iterate the LinkedList to find the particular slot to insert this message and then insert it there.
Sending Data to UI thread(which is the Handler Thread too):
Say a worker thread(created at line 1) wants to send data(123) to UI thread:
Line 1: Creates a new worker thread.
Line 4: Get the Looper associated with the UI thread
Line 5:Create the Callback which will be called by UI thread, once it reads the message we are going to send to it.
Line 12: Create Handler with the Main Thread’s Looper, so that we write the message to Main thread’s MQ.
Line 13: Create an empty Message from the pool of messages( to avoid GC overload).
Line 14: Put some value into the Empty message — 123.
Line 15: Setting the above handler(which has callback defined) as the Message Target, which will be called once the message is read by the UI looper.
Line 16: Write the Message to the MQ via Main Looper via Handler.
This is pretty easy to write to Main thread from worker thread, as the Main thread is itself a handler thread which has its Looper/MQ etc defined.
But what if we want to write our own Handler Thread to which other threads can communicate?
In that case we need to initialize Loopers, MQ, etc ourselves.
So make a Thread a Handler thread, we need to do:
1. Looper. prepare() → basically initializes the Looper and creates an MQ for it.
2. Looper. loop() → This will start the looper, which will keep on reading the Message Queue until Stopped.
Line 2: This will get the MQ for that Looper.
Line 8: Read from MQ infinitely until stopped(in that case msg read from Queue will be null).
Line 9: gets blocked until the message has arrived in the MQ. queue.next will be blocked until it sends a msg, it will send null only if Looper is stopped(using Looper.quit() method).
Line 15: Once msg is read, send it to its callback(defined in msg.target and hence in Handler’s callback).
Line 17: Once msg is handled, recycle it to clear its data off, and add it to the pool.
These above 2 methods are sufficient to create a Handler Thread.
Another easy way is to extend the Thread by HandlerThread, which under the hood calls these 2 above methods.
This is the HandlerThread run method: which calls Looper.prepare — then Looper.loop and in between onLooperPrepared to do something in this thread.
How to send data to such a Handler thread?
Simple, instead of passing the MaineLooper to the Handler, pass the Worker Handler Thread’s Looper like:
Line 6 is the only difference, here the looper belongs to the Handler thread we created and not that of the default UI thread.
Let’s explore the working of other Methods too:
MessageQueue::next
Looper.loop depends heavily on Queue.next() method. Below is the stripped-down version of next:
MessageQueue has the LinkedList of Messages(sorted on Message.when). Message.when corresponds to the time this message should be processed.
Line 6: Get the LinkedList of messages written into the Queue.
Line 7 and 8: If the message exists and message read has when before the current time(I.e. it is eligible to be processed), read it.
Line 10 and 11: LinkedList points to the next message now, as this message is read and could be removed from the Linked list(its next is set to null).
line 12: set this message in use, so that no further action can be done on it, like updating it, etc.
line 13: return it to the Looper. This will not happen until any message is read, as there is an infinite loop enclosing this code at line 2.
Line 17 and 18: Returns null only if it is stopped.
Adding Messages to Queue:
This is done via handler.sendMessage() or Handler.post()
2 types of messages can be sent to HandlerThread:
a. Messages: which have when, data, target, etc fields. Upon being read by the Looper, it delegates it to msg.target to handle the message. This will run on the HandlerThread, and not on the worker thread which sends this message.
b. Runnable: Upon read by the Looper, the Runnable.run() is called inside the Handler Thread. Under the hood this Runnable is converted into the empty message, and this runnable is set as its callback. So that this runnable also acts as a Message.
Line 2 wraps this Runnable into the Message, and its the Message which gets written into the MQ.
As shown, this basically calls queue.enqueueMessage to write this message into the mQueue(MessageQueue for this handler thread).
MessageQueue.enqueueMessage(Message )
Line 2: If it’s already being read(next() sets this boolean true), or already added into Queue(line 13), then don’t add the same message again.
Line 7–10: If the Queue/Looper is quitting, then don’t add msgs into it.
Line 13: Set this msg to be in use so that it’s not added into the queue again.
Line 16: If this msg is to be processed before or at the current time, add it at the front.
Line 17–18: LinkedList code to add msg at the head.
Line 21–27: Find the slot to add this msg based on its when, as the LL is sorted on its when. This is a general LinkedList iteration code.
Please note that all the above methods are stripped-down to ease the understanding.
Источник
Android. Собеседование #7. Многопоточность. Handler. Looper.
1. Особенности работы потоков в Android приложении?
В приложении на Android создается главный, так называемый UI поток, который обрабатывает основные события в приложении, такие как взаимодействие с пользовательским интерфейсом (например, нажатие на кнопку), графические изменения, создание широковещательного сообщения и т.д. UI поток работает на протяжении всего жизненного цикла приложения.
При запуске нового потока, в случае необходимости изменить UI в нашем приложении, например цвет какого-либо TextView, необходимо использовать средства, которые имеют доступ к UI потоку, иначе произойдёт падение нашего приложения. Доступ к UI имеет только главный поток приложения.
Вся логика приложения, которая занимает продолжительное время, например обращение к базе данных, сложные вычисления, REST запросы и т.д. должны выполняться в отдельном потоке, иначе приложение зависнет и примерно через 5 секунд вызовет ANR (application not respoding) ошибку.
2. Что такое Looper и MessageQueue?
MessageQueue – класс, который хранит список из объектов Message, которые должны быть выполнены по принципу FIFO. и взаимодействует с Looper для обработки сообщений.
Looper – класс, предназначенный для обработки очереди MessageQueue, в которую Handler отправляет сообщения (объекты Message) о необходимости обработать изменения в приложении. После запуска приложения, в его основном потоке создаётся экземпляр Looper, главный UI Looper приложения, который при помощи метода loop() запускает бесконечный цикл и проверяет, появились ли новые сообщения в MessageQueue.
При создании нового потока, необходимо обязательно создать looper внутри него, при помощи последовательного вызова методов Looper.prepare() → Looper.loop(), иначе при попытке изменить данные в UI потоке приложения, произойдёт ошибка.
Для завершения работы Looper необходимо вызвать метод quitSafely() для того чтобы завершить работу Looper и быть уверенным в том, что все сообщения в MessageQueue были обработаны.
3. Что такое Handler и HandlerThread?
Handler это класс, который предназначен для управления потоками в приложении. Получает сообщения от Looper из MessageQueue и он же отправляет сообщения в MessageQueue. С его помощью мы можем связать UI поток нашего приложения с другими потоками.
При создании нового объекта Handler, по умолчанию он привязывается к этому потоку и MessageQueue в этом же потоке. Т.е. если мы создаём новый Thread и в нём создаём Handler, то он не будет иметь доступа к UI потоку приложения. Для взаимодействия с UI потоком, необходимо инициализировать looper в этом потоке, либо предоставить Main Looper для нашего Handler
HandlerThread – класс, который наследуется от Thread. Уже включает в себя Looper, MessageQueue и готов для создания нового потока, с последующей передачей данных в UI поток.
Источник
Understanding Android Core: Looper, Handler, and HandlerThread
This Article covers Android Looper, Handler, and HandlerThread. These are among the building blocks of Android OS.
In my own experience, I have used them in a very limited context until recently. My use case involved sending tasks to the main/ui thread, primarily to update the UI from any other thread. The other aspects of the multi-threaded operation were handled through alternate ways like ThreadPoolExecutor, IntentService, and AsyncTask.
MultiThreading and task running are old subjects. Java itself has java.util.concurrent package and Fork/Join framework to facilitate it. Several libraries have been written to streamline asynchronous operations. RxJava is the most popular library today for reactive programming and designing an asynchronous application.
So, why am I writing about the old school?
Looper , Handler , and HandlerThread are the Android’s way of solving the problems of asynchronous programming. They are not old school, but a neat structure on which a complex android framework is built.
For new developers, it’s highly recommended to understand the principles behind them and experienced one’s should revisit this topic to recollect the minor details.
I have also created a video tutorial for this subject, and I highly recommend to watch it. Click here to watch now .
Use Cases:
- The main thread in Android is built with a Looper and Handlers . So, the understanding of it is essential to create an unblocked responsive UI.
- The developers writing libraries cannot afford to use third party libraries because of the library size. So, for them, the best option is to utilize the existing available resource. Writing own solution for it may not always get that level of efficiency and optimization.
- The same argument can also be made for companies/individuals shipping out SDKs. The clients can have varied implementations, but all of them will share the common android framework APIs.
- Understanding them fully will enhance the capacity to follow the Android SDK and package classes in general.
Let’s start the exploration/revision with a questionnaire.
I expect the reader to have the basic understanding of java threads. If you need, then get a quick overview of java Thread and Runnable.
What is the problem with java thread?
Java threads are one-time use only and die after executing its run method.
Can we improve upon it?
The Thread is a double edged sword. We can speed up the execution by distributing the tasks among threads of execution, but can also slow it down when threads are in excess. Thread creation in itself is an overhead. So, the best option is to have an optimum number of threads and reuse them for tasks execution.
Model for thread reusability:
- The thread is kept alive, in a loop via it’s run() method.
- The task is executed serially by that thread and is maintained in a queue (MessageQueue).
- The thread must be terminated when done.
What is the Android’s way of doing it?
The above model is implemented in the Android via Looper , Handler , and HandlerThread . The System can be visualized to be a vehicle as in the article’s cover.
- MessageQueue is a queue that has tasks called messages which should be processed.
- Handler enqueues task in the MessageQueue using Looper and also executes them when the task comes out of the MessageQueue .
- Looper is a worker that keeps a thread alive, loops through MessageQueue and sends messages to the corresponding handler to process.
- Finally Thread gets terminated by calling Looper’s quit() method.
One thread can have only one unique Looper and can have many unique Handlers associated with it.
Creating Looper and MessageQueue for a Thread:
A thread gets a Looper and MessageQueue by calling Looper.prepare() after its running. Looper.prepare() identifies the calling thread, creates a Looper and MessageQueue object and associate the thread with them in ThreadLocal storage class. Looper. loop() must be called to start the associated looper. Similarly, the looper must be terminated explicitly through looper.quit() .
Creating Handler for a Thread:
A Handler gets implicitly associated with the thread that instantiates it via thread’s Looper , but we can explicitly tie it to a thread by passing the thread’s looper in the constructor of the Handler .
Sending messages to the MessageQueue via Handler can be done by two modes:
- Message : It is a class that defines various useful methods to deal with message data. To send an object we set the obj variable.
2. Runnable : A runnable can also be posted in the MessageQueue . Ex: posting and running a task in the main thread.
In the above example, we create a Handler and provide Looper associated with the main thread. This associate this handler to the main thread. When we post the Runnable , it gets queued in the main thread’s MessageQueue and then executed in the main thread.
Handler is capable of message manipulation in a wide variety of ways, which can found here: https://developer.android.com/reference/android/os/Handler.html
Creating an own thread and providing Lopper and MessageQueue is not the right way to deal with the problem. So, Android has provided HandlerThread ( subclass of Thread ) to streamline the process. Internally it does the same things that we have done but in a robust way. So, always use HandlerThread .
One of the ways to create the HandlerThread is to subclass it and most of the time you will be using this method.
Note: We have instantiated the Handler when the onLooperPrepared() is called. So, that Handler can be associated with that Looper .
- Looper is only prepared after HandlerThread’s start() is called i.e. after the thread is running.
- A Handler can be associated with a HandlerThread , only after it’s Looper is prepared.
Other way to create the HandlerThread:
Note: HandlerThread needs to call myHandlerThread.quit() to free the resources and stop the execution of the thread.
I would suggest practicing the above codes, so you can grasp their little details.
I have created an example project for Post Office simulation. Post Office is built upon HandlerThread and Clients communicate with the help of the Post Office. A Simulator class creates few Client Bots and delegate their communication to the MainActivity, which renders it in a live feed.
I have also created a video tutorial for this subject, and I highly recommend to watch it. Click here to watch now .
Learning is a journey, let’s learn together!
Источник