- Android Studio 2.3
- API 25
Handler mechanism is analyzed from the source code point of view. It is helpful to use Handler and analyze the related problems of Handler.
Introduction to Handler
A Handler allows sending and processing messages and executing Runnable objects through Message sageQueue of associated threads.
Each Handler instance is bound to a separate thread and its message queue.
You can switch a task to the thread in which Handler is running. One use is for sub-threads to update the UI through Handler.
There are two main uses:
- Plan to execute messages and Runnable at some point in the future
- Plan and execute tasks on other threads
To make good use of Handler, you need to understand its related MessageQueue, Message and Looper; you can't look at Handler in isolation
Handler is like an operator (or like an open window to developers), using MessageQueue and Looper to schedule and process tasks.
// This callback allows you to use Handler without creating a new subclass of Handler public interface Callback { public boolean handleMessage(Message msg); } final Looper mLooper; // Handler holds an example of Looper final MessageQueue mQueue; // Holding message queue final Callback mCallback;
In the Handler constructor, we can see that message queues are related to Looper management
public Handler(Callback callback, boolean async) { // Handling exceptions mLooper = Looper.myLooper(); // Deal with special situations. mQueue = mLooper.mQueue; // Getting a message queue from Looper } public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; // Getting a message queue from Looper mCallback = callback; mAsynchronous = async; }
Android is message-driven, and there are several elements to implement message-driven:
- Representation of a message: Message
- Message queue: MessageQueue
- Message loops for looping out messages for processing: Looper
- Message Processing: The message is processed after the message loop retrieves the message from the message queue: Handler
Initialize message queue
A MessageQueue is created in the Looper constructor
send message
After initializing the good news queue through Looper.prepare, we can call Looper.loop to enter the message loop, and then we can send the message to the message queue.
The message loop takes out the message for processing. Before looking at the message processing, see how the message is added to the message queue.
Message loop
Messages in the Java layer are stored in the member mMessages of the Java layer MessageQueue, and messages in the Native layer are stored in the Native Looper.
In mMessage Envelopes, this can be said to have two message queues, all arranged in time.
Why use a mechanism like Handler?
Because UI operation in Android system is not thread-safe, if multiple threads operate the same component concurrently, it may lead to thread-safe problems.
In order to solve this problem, android has formulated a rule: only UI threads are allowed to modify the attributes of UI components, that is to say, a single-threaded model is required.
This leads to program pseudo-death, or ANR anomaly, if a long time-consuming data update is performed in the UI interface, if it is not completed in 20 seconds.
The program will be forced to shut down. So for example, when another thread wants to modify the UI component, it needs to use the Handler message mechanism.
Several Methods of Handler Sending and Processing Messages
1. Void handleMessage (Message msg): A method of processing messages, which is usually rewritten.
2.final boolean hasMessage(int what): Check whether the message queue contains a message with what attribute as the specified value
3. final Boolean hasMessage (int what, Object object): Check if the message queue contains a message with the specified value of what good object attribute
4.sendEmptyMessage(int what): Send empty messages
5. final Boolean send Empty Message Delayed (int what, long delayMillis): specify how many milliseconds to send empty messages
6.final boolean sendMessage(Message msg): Send the message immediately
7.final boolean sendMessageDelayed(Message msg,long delayMillis): How many seconds later to send a message
The roles of Looper and MessageQueue, which work with Handler, are as follows:
- 1.Handler: It sends messages to Looper-managed MessageQueue and handles the messages Looper distributes to it
- 2.MessageQueue: Manage Message, managed by Looper
- 3.Looper: Each thread has only one Looper. For example, in UI threads, the system initializes a Looper object by default, which manages MessageQueue.
Continuously cancel messages from MessageQueue and distribute the corresponding messages to Handler
Handler.java (frameworks/base/core/java/android/os)
// Before adding a message to the queue, determine if the queue is null public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); } // ...... // Adding messages to the queue private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; // Hadler who designates himself as Message if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
It's not hard to see from here that every Message holds Handler. If Handler holds a reference to Activity, Message is still in the queue after Activity on Destroy.
Because of the strong association between Handler and Activity, Activity can not be reclaimed by GC, leading to memory leak.
Therefore, in Activity on Destroy, Hadler associated with Activity should clear its queue of tasks generated by Activity to avoid memory leaks.
MessageQueue.java (frameworks/base/core/java/android/os)
// Add message boolean enqueueMessage(Message msg, long when) { // Judge and add messages... return true; }
Handler.sendEmptyMessage(int what) process parsing
Get a Message instance and immediately add it to the message queue.
The brief process is as follows
// Handler.java // Send an empty message immediately sendEmptyMessage(int what) // In the method of sending empty messages with a delay of 0, a Message instance is obtained through Message. get (). sendEmptyMessageDelayed(what, 0) // Calculate the scheduled execution time of the message and move on to the next stage sendMessageDelayed(Message msg, long delayMillis) // Here, determine whether the queue is null or not, and return false directly if NULL sendMessageAtTime(Message msg, long uptimeMillis) // Adding messages to the queue enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) // Next, MessageQueue adds messages // MessageQueue.java boolean enqueueMessage(Message msg, long when)
Part of the source code is as follows
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); } public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
Handler cancels task removeCallbacks AndMessages
To cancel the task, call the following method
public final void removeCallbacksAndMessages(Object token) { mQueue.removeCallbacksAndMessages(this, token); }
Remove the message associated with this Handler by calling the Message.recycleUnchecked() method.
Relevant message queues execute cancellation instructions
void removeCallbacksAndMessages(Handler h, Object object)
Introduction to Message and Message Queue
Message
Message belongs to the role of being delivered and used
Message is a "message" that contains descriptions and arbitrary data objects and can be sent to Handler.
Contains two int attributes and an additional object
Although the constructor is public, the best way to get an instance is to call Message. get () or Handler.obtainMessage().
This allows you to retrieve message instances from their recyclable object pool
In general, each Message instance holds a Handler
Partial attribute values
/*package*/ Handler target; // Specified Handler /*package*/ Runnable callback; // Can form a linked list // sometimes we store linked lists of these things /*package*/ Message next;
The method of resetting itself, resetting all attributes
public void recycle() void recycleUnchecked()
Common methods for obtaining Message instances, resulting instances bound to the incoming Handler
/** * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned. * @param h Handler to assign to the returned Message object's <em>target</em> member. * @return A Message object from the global pool. */ public static Message obtain(Handler h) { Message m = obtain(); m.target = h; return m; }
Send a message to Handler
/** * Sends this Message to the Handler specified by {@link #getTarget}. * Throws a null pointer exception if this field has not been set. */ public void sendToTarget() { target.sendMessage(this); // target is a Handler bound to a message }
After calling this method, Handler adds the message to its message queue MessageQueue
MessageQueue
Hold a column of Mesage that can be distributed by Looper.
Generally speaking, Message is added to MessageQueue by Handler.
The MesageQueue method to get the current thread is Looper.myQueue()
Introduction to Looper
Looper is closely related to MessageQueue
A message loop running in a thread. By default, threads do not have message loops managed with them.
To create a message loop, call prepare in the thread, and then call loop. Processing of messages begins until the loop stops.
In most cases, it interacts with message loops through Handler.
Typical examples of Handler and Looper interacting in threads
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); // Prepare a Looper for the current thread // Create an instance of Handler, which retrieves the Loper of the current thread // If the current thread does not have Looper when the Handler is instantiated, an exception RuntimeException is reported mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); // Looper starts running } }
Attributes in Looper
Looper holds MessageQueue; the only main thread, Looper sMainLooper; Looper's current thread, mThread;
sThreadLocal Storing Looper
// sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper; // guarded by Looper.class final MessageQueue mQueue; // Handler retrieves the message queue instance (refer to the Handler constructor) final Thread mThread; // Looper current thread
ThreadLocal is not a thread, its function is to store data in each thread.
Looper method
Prepare method to initialize the current thread to Looper. Qut is called on exit
public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); // The Looper instance is stored in sThreadLocal }
prepare method creates a new Looper and stores it in sThreadLocal sThreadLocal.set(new Looper(quitAllowed))
ThreadLocal < T > class
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
When you want to get the Looper object, get it from sThreadLocal
// Gets the Loper associated with the current thread and returns null public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
Run a message queue on the current thread. The exit method quit() is called after completion.
public static void loop()
Prepare the main thread Looper. The Android environment creates the main thread Looper, which should not be invoked by developers themselves.
UI threads, which are ActivityThread, initialize Looper when ActivityThread is created, which is why Handler is used by default in the main thread.
public static void prepareMainLooper() { prepare(false); // This means that the main thread Looper cannot be exited by the developer synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
Gets the Loper of the main thread. This method can be invoked by our developers when they want to operate on the main thread
public static Looper getMainLooper()
Please refer to: http://rustfisher.github.io/2017/06/07/Android_note/Android-Handler/