I am just a Android dish chicken, write technical articles just to summarize their recent learning knowledge, never dare to be a teacher, if there is an incorrect place please point out, thank you!
606 pages of new Android interview questions with answers, you can click to obtain.
This article is based on the native Android 9.0 source code to parse Android message mechanism: frameworks/base/core/java/android/os/Handler.java frameworks/base/core/java/android/os/Looper.java frameworks/base/core/java/android/os/MessageQueue.java frameworks/base/core/java/android/os/Message.java frameworks/base/core/java/android/app/ActivityThread.javaCopy the code
1. An overview of the
We know that we can’t do time-consuming operations on the main thread of Android, such as network access and data processing, because if the main thread processing time exceeds the system limit, the application will not respond. In practice, however, processing time-consuming tasks is unavoidable, and it is often necessary to update some UI controls after processing time-consuming tasks to display the results. In this scenario, the most common solution is to perform time-consuming operations in a new thread, and then notify the main thread of relevant UI updates. This is where the Android messaging mechanism is needed.
What exactly is a messaging mechanism? To put it simply, Android messaging mechanism is a set of “message” as the intermediary to achieve task switching between threads or tasks in the same thread on demand execution mechanism, which involves message sending, storing messages, message loop and message distribution and processing.
This article will first demonstrate how to use the Android messaging mechanism through a simple example, and then analyze the source code to further understand the internal implementation of the messaging mechanism, and finally explain some of the attention points of using the Android messaging mechanism.
2. First look at Android messaging
To start with a simple example of how Android messaging works in practice, let’s directly use the previously mentioned scenario where the child thread processes a time-consuming task and notifies the main thread to update the UI after the task is completed. The example code is as follows:
Public class MainActivity extends Activity {// Define Handler and Thread local variables private Handler mHandler; private Thread mThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler = new Handler() { @Override public void handleMessage (Message msg) { // 4\. Switch (msg.what) {// Perform uI-related operations here, such as displaying processing results. }}}; mThread = new Thread() { @Override public void run() { // 2\. Sleep for a period of time to simulate child threads processing time-consuming tasks. try { Thread.sleep(30000); } catch (InterruptedException ie) { ie.printStackTrace(); } / / 3 \. Send the message mHandler. SendEmptyMessage (0); }}; // 1\. Start child thread mthread.start (); }}Copy the code
In the example code, the logical flow is marked by serial number, that is, the child thread is started and the task is processed inside the thread, the message is sent to the main thread through the Handler after the task is processed, and finally the message is processed and the UI is updated in the main thread.
The Android messaging mechanism looks pretty simple. Just use Handler to send messages and process them. Is it really that simple? Of course not! As mentioned earlier, there are several key points involved in the messaging mechanism: send messages, store messages, message loops, and distribute processing messages. In this example, we only see send messages and process messages, not store messages and message loops.
This is because the Handler in this example uses messages sent and stored in the message queue in the main thread. The creation and circulation of the message queue are automatically performed by the system when the main thread is created, which is transparent to us and is not good for understanding the overall flow of the message mechanism.
Here is a more general example from which message queue creation and message loop opening can be clearly seen:
class LooperThread extends Thread { public Handler mHandler; Public void run() {// Initializes a Looper object that creates a message queue inside. Looper.prepare(); MHandler = new Handler() {public void handleMessage(Message MSG) {// Handle messages in the Message queue. }}; // If the message loop is started, the message will be fetched from the message queue and wait for the arrival of new messages when there are no messages. Looper.loop(); }}Copy the code
Combining these two examples, we have seen how the Android messaging mechanism works. We have also seen the process of sending a message, creating a message queue, opening a message loop, and processing a message. Here is a more intuitive “message passing flowchart” :
You can see the entire message passing process through the flowchart, as well as the classes involved at different stages:
- Message sending: Pass
Handler
To the associatedMessageQueue
Send a message; - Message storage: The sending of messages as
Message
Stored in the form ofMessageQueue
; - Message loop: Pass
Looper
Kept fromMessageQueue
When there is no message in the queue, it blocks waiting for a new message. - Message distribution and processing:
Looper
Get the message and distribute it toHandler
Process.
3. Understand the Android messaging mechanism
As mentioned above, the message delivery process is mainly divided into “send message”, “store message”, “message loop” and “message distribution and processing”. I intended to explain each stage separately according to this process, but in the concrete writing, I found that each stage is not completely separated. For example, before talking about “sending messages”, we should first understand “message storage structure” and “message loop start”, and “message distribution” is the function of “message loop”.
Because of the interrelationship between these phases, there is no way to explain Android messaging strictly in the order in which it is delivered. After much deliberation, I decided to walk through the logic behind the generic Android messaging example described above.
Here’s another general example:
class LooperThread extends Thread { public Handler mHandler; Public void run() {// 1\. Initializes a Looper object that creates a message queue. Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // 4\. Process messages in message queues. }}; // 2\. If the message loop is started, the message will be taken out from the message queue. If there is no message, the message will be blocked waiting for the arrival of new messages. Looper.loop(); } / / 3 \. Send the message mHandler. SendEmptyMessage (0); }Copy the code
The following sections are based on the key points of the “messaging mechanism” identified in different numbers in the sample code.
3.1 Message Carrier
“Message” is the carrier of information in the Android Message mechanism, which contains the data to be transmitted in the entire Message transmission process. To understand the “Message mechanism”, we must first understand the Message carrier class Message:
/** * Defines a message containing a description and arbitrary data object that can be * sent to a {@link Handler}. This object contains two extra int fields and an * extra object field that allow you to not do allocations in many cases. * * <p class="note">While the constructor of Message is public, the best way to get * one of these is to call {@link #obtain Message.obtain()} or one of the * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull * them from a pool of recycled objects.</p> */ public final class Message implements Parcelable { ... }Copy the code
The Android framework’s declaration of Message carrier is brief, but conveys two core and most important messages:
Message
The role of:Message
Contains description information and data objects and is sent in the messaging mechanismHandler
The data object mainly consists of two integer fields and an object field through which information can be passed. The cost of integers is minimal, so use integer fields to pass information wherever possible.
/** * User-defined message code so that the recipient can identify * what this message is about. Each {@link Handler} has its own name-space * for message codes, so you do not need to worry about yours conflicting * with other handlers. */ public int what; /** * arg1 and arg2 are lower-cost alternatives to using * {@link #setData(Bundle) setData()} if you only need to store a * few integer values. */ public int arg1; public int arg2; /** * An arbitrary object to send to the recipient. When using * {@link Messenger} to send the message across processes this can only * be non-null if it contains a Parcelable of a framework class (not one * implemented by the application). For other data transfer use * {@link #setData}. * * <p>Note that Parcelable objects here are not supported prior to * the {@link android.os.Build.VERSION_CODES#FROYO} release. */ public Object obj;Copy the code
Message
The way to create: thoughMessage
There are public constructors, but it is recommended to use the ones providedobtain
Series of functions to obtainMessage
Object, which recycles objects from the cache pool rather than creating new objects, avoiding creating too many objects in memory and possible performance problems.
/** * Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases. */ Public static Message obtain() {synchronized (sPoolSync) {// Obtain Message objects from the cache pool if there are available objects in the cache pool. if (sPool ! = null) {// Get the object in the cache and move the buffer pool pointer back. Message m = sPool; sPool = m.next; m.next = null; // Clear the mark m.flags = 0; // Clear in-use flag // Update the current cache pool size sPoolSize--; return m; }} // Create a new Message object when no object is available in the cache pool. return new Message(); }Copy the code
Message contains a series of obtain functions that can be used to obtain objects in different scenarios, but this is the core, and is called internally by all other functions. If you are interested, you can review the source code for yourself.
The obtain function obtains Message objects from the cache pool. When were the objects added to the cache pool? Since the objects in the cache pool are objects that can be reused, it is clear that the Message object is added to the cache when it is no longer needed, taken out of the MessageQueue and distributed to the Handler, using the recycle-unchecked function:
/** * Recycles a Message that may be in-use. * Used internally by the MessageQueue and Looper when disposing of queued Messages. */ void recycleUnchecked() {// Set the flag bit to "in use", which will be cleared when removed from the cache. flags = FLAG_IN_USE; // The information in the Message object is no longer meaningful and is emptied before being put into the cache pool. what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = -1; when = 0; target = null; callback = null; data = null; Synchronized (sPoolSync) {// Only a certain number of Message objects are cached in the cache pool, 50 by default. If (sPoolSize < MAX_POOL_SIZE) {// Place the object at the head of the cache pool's list. next = sPool; sPool = this; // Update the cache pool size in time. sPoolSize++; }}}Copy the code
3.2 Creating a Message Queue
The creation of a message queue is critical to messaging, determining how messages are accessed during delivery. But threads do not have message queues by default and cannot loop messages within them. To start a message loop for a thread, use the Looper class, which creates a message queue for the associated thread and starts a message loop by calling the Prepare () interface:
/** * Class used to run a message loop for a thread. Threads by default do * not have a message loop associated with them; to create one, call * {@link #prepare} in the thread that is to run the loop, And then * {@link #loop} to have it process messages until the loop is stopped. */ public final class Looper {// omit unconnected code // sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); // Internal message queues and associated threads final MessageQueue mQueue; final Thread mThread; // Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, And end it by calling * {@link #quit()}. */ public static void prepare() { prepare(true); } private static void prepare(Boolean quitAllowed) {// If the current thread already has a Looper object, throw an exception. if (sThreadLocal.get() ! = null) { throw new RuntimeException("Only one Looper may be created per thread"); } // Create a Looper object and associate it with the thread. sThreadLocal.set(new Looper(quitAllowed)); } // Private constructor to create a message queue and get the current thread object. private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }Copy the code
As you can see, looper.prepare () simply creates a MessageQueue object internally and associates it with the current thread, while ensuring that there is only one MessageQueue per thread.
MessageQueue is a structure used to store message objects.
/**
* Low-level class holding the list of messages to be dispatched by a
* {@link Looper}. Messages are not added directly to a MessageQueue,
* but rather through {@link Handler} objects associated with the Looper.
*
* <p>You can retrieve the MessageQueue for the current thread with
* {@link Looper#myQueue() Looper.myQueue()}.
*/
public final class MessageQueue { ... }
Copy the code
MessageQueue is a class that holds a list of message objects that are added through handlers associated with Looper and eventually distributed by Looper. There is one key piece of information that we should pay special attention to: List of messages, does this tell us that even though the name of this class is Queue, it’s not a queue, it’s a list? Indeed, MessageQueue is accessed internally using a one-way linked list method, which will be seen later in the process of parsing Message access, but will not be described in detail here.
3.3 Starting a Message Loop
Now that the “message queue” is created, can you add message objects directly to it? Looper.loop() : looper.loop () : looper.loop ()
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void Loop () {// Get the current thread's Looper object, throw an exception if it fails to get. final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } // Get the message queue for the current thread. final MessageQueue queue = me.mQueue; // Start an infinite loop to listen for messages on the queue {// Get the message object in the message queue, block the wait if there is no message object. Message msg = queue.next(); // might block if (MSG == null) {// Might block if (MSG == null) { } // omit the extraneous try {// distribute the message to the appropriate handler. msg.target.dispatchMessage(msg); dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } finally { if (traceTag ! = 0) { Trace.traceEnd(traceTag); }} // omit extraneous code // reclaim the message object and put it into the message cache pool for future reuse. msg.recycleUnchecked(); }}Copy the code
This code itself is quite complex, I omit the part of the code irrelevant to the core logic, for the convenience of everyone to read and understand, the core logic is to use an “infinite loop” to listen to the message queue, when found available messages and distribute processing, if not, wait.
3.4 Sending and storing Messages
The Message Queue has been created, the Message Loop has been started, and you can finally send messages.
To send a message, we need to use the Handler class, which send and POST series methods can be used to “send a message”, the core method is the same, here with post method to explain the process of sending a message:
/** * Causes the Runnable r to be added to the message queue. * The runnable will be run on the thread to which this handler is * attached. * * @param r The Runnable that will be executed. * * @return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) {private static Message getPostMessage(Runnable r) { // This callback will play a role in subsequent message distribution processing. Message m = Message.obtain(); m.callback = r; return m; } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } // Convert the delay time to absolute time for subsequent execution. return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public Boolean sendMessageAtTime(Message MSG, long uptimeMillis) {// Create a Message queue using looper.prepare (). 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) {// Set the destination of the Message queue for subsequent Message distribution. msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queuemessage. EnqueueMessage (MSG, uptimeMillis); }Copy the code
Through a series of calls, the Handler may end MessageQueue. EnqueueMessage () to store the message to the message queue, MessageQueue internal is how to store the object was sent message?
Boolean enqueueMessage(Message MSG, long WHEN) {// Throw an exception if the target of the Message object is null, because this means that the Message cannot be distributed, // is an invalid Message object. if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } // An exception is thrown while the message is in use. The message cannot be used concurrently. if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) {// The message object is recovered while exiting the message loop. if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; / / the message object is added to the message queue if an appropriate position (p = = null | | the when = = 0 | | the when < p.w hen) {/ / New head, Wake up the event queue if blocked. // If the message queue is empty or the current message object is the latest, it is placed directly at the head of the list. msg.next = p; // Update list pointer mMessages = MSG; NeedWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; // Find the appropriate insertion location according to the time information in the message object. { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; }} next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr ! // Wake up the wait. The message loop can continue to get messages, which may have been blocked. if (needWake) { nativeWake(mPtr); } } return true; }Copy the code
3.5 Message Distribution
Have a new message in the message queue and message loop awakened, the message in the message queue can be removed and distributed to the appropriate handler, it can be seen in “open message loop” section, using the MSG. Target. DispatchMessage (MSG), A target is a Handler object that looks directly at the distribution process:
Public void dispatchMessage(Message MSG) {// The Message object is generated from the Runnable wrapper, and the callback is not null. if (msg.callback ! = null) { handleCallback(msg); } else {// mCallback is set in the Handler constructor or not. if (mCallback ! If (McAllback.handlemessage (MSG)) {if (McAllback.handlemessage (MSG)) { Handler.handleMessage will then be unable to continue processing the message. return; }} // Call Handler's handleMessage to process the message. Subclasses implement this method. handleMessage(msg); }} private static void handleCallback(Message Message) {// Callback in Message is Runnable. Run (). message.callback.run(); } /** * Callback interface you can use when instantiating a Handler to avoid * having to implement your own subclass of Handler. */ public interface Callback { /** * @param msg A {@link android.os.Message Message} object * @return True if No further handling is desired */ // The callback method of the Handler that returns a value for message interception. public boolean handleMessage(Message msg); } /** * Subclasses must implement this to receive messages. public void handleMessage(Message msg) { }Copy the code
The distribution of messages is prioritized:
- The first consideration is to hand over
Message.callback
To process if it is throughpost
Messages sent by a series of functions will go here to be processed while passing throughsend
By default, messages sent by a series of functions do not have this callback interface; - if
Message.callback
If it doesn’t exist, consider giving itHandler.callback
In the process, messages can be intercepted by returning values; - if
Handler.callback
Does not exist or does exist but is not intercepted during the processing of the messageHandler.handleMessage
This interface requires subclass implementation and is the most commonly used place to process messages in practice.
Here, the transmission process of the message is basically finished, we can combine the previous flow chart carefully figure out, I believe it can be rightAndroid
Better understanding of messaging mechanisms.
4. Expand your knowledge
4.1 Main thread message loop creation
Said before there is no message queue a thread by default, cannot open in its internal message loop, but we directly in practical work often used in the main thread Handler for message sending and processing, and normal operation, this is because the main thread has created message queue at start-up time and open the message loop, It’s just that the process is transparent and we don’t perceive it.
Those of you who know the Activity startup process already know where the creation process is. That’s right, in ActivityThread. Don’t worry if you don’t know the startup process. Here, you can simply use the ActivityThread as the entry point for your Activity and look directly at the entry function:
/** * This manages the execution of the main thread in an * application process, scheduling and executing activities, * broadcasts, and other operations on it as the activity * manager requests. * * {@hide} */ public final class ActivityThread extends ClientTransactionHandler {public static void main(String[] args) {// Record the start of the session, which is used to check and debug performance problems through systrace. Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain"); // Create message queue looper.prepareMainLooper (); ActivityThread thread = new ActivityThread(); thread.attach(false, startSeq); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // You can use systrace to observe the execution of this code. // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); // Start the message loop looper.loop (); // The main thread message loop does not exit, if this means an accident, throw an exception. throw new RuntimeException("Main thread loop unexpectedly exited"); }}Copy the code
The code structure is similar to the generic Android message mechanism example, where you see the creation of a message queue and the opening of a message loop. The difference is that the main thread creates a message queue using Looper. PrepareMainLooper:
/** * Initialize the current thread as a looper, marking it as an * application's main looper. The main looper for your application * is created by the Android environment, so you should never need * to call this function yourself. See also: {@link #prepare()} */ public static void prepareMainLooper() {// Start a message loop that cannot exit. synchronized (Looper.class) { if (sMainLooper ! = null) { throw new IllegalStateException("The main Looper has already been prepared."); } // return the main thread looper object sMainLooper = myLooper(); }}Copy the code
The message loop created by the main thread cannot exit because it handles many important transactions, such as callbacks to the Activity life cycle. Exiting will result in an exception, which will be explained in more detail when we explain the Activity startup process.
4.2 Memory Leaks
Java garbage collection mechanism for each engaged in Java developers should not strange, we also know that not all object memory can be recycled in time, if the garbage collector to recycle some objects, but because they also by other object references, so these objects cannot be recycled, which is the main cause of memory leaks.
Is there a memory leak when you use Android messaging? Let’s start with a common usage:
public class MainActivity extends Activity { private TextView mTextView = null; private Handler mMyHandler = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // initialize the controller mTextView = (TextView) findViewById(R.ids.sample_text); // Initialize Handler object mMyHandler = new MyHandler(); // Start a delay message and mMyHandler executes after 3000ms. mMyHandler.sendEmptyMessageDelayed(0, 3000); } private class MyHandler extends Handler {@override public void handleMessage(Message MSG) {// Execute a Message to update the control in the main thread. if (mTextView ! = null) { mTextView.setText("execute message"); }}}; @Override public void onDestroy() { super.onDestroy(); }}Copy the code
In this example, MyHandler is in the form of the Activity’s internal class, so mMyHandler needs to hold a reference to the external class object, and mMyHandler is referenced as a target by the Message object it sends. The end result is that the Activity is referenced indirectly by Message. Since this Message needs to be executed after a certain amount of delay, if the Activity exits before then, but its reference is held by Message, it cannot be reclaimed by the system, resulting in a memory leak.
Since the Activity is leaking memory by being referenced by Message, is there a way to keep it from holding the reference? Of course, this can be avoided by using a “static inner class”, because a “static inner class” does not need to hold a reference to an external class object.
public class MainActivity extends Activity { private TextView mTextView = null; private Handler mMyHandler = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.sample_text); // Initialize the Handler object and pass in the main thread as a parameter. mMyHandler = new MyHandler(mTextView); // Start a delay message and mMyHandler executes after 3000ms. mMyHandler.sendEmptyMessageDelayed(0, 3000); } private static class MyHandler extends Handler {// Holds external object variables by weak reference. private WeakReference<TextView> mTextViewRef = null; // Initializes the weak-reference object and holds the correct object reference thereafter. public MyHandler(TextView textView) { mTextViewRef = new WeakReference<>(textView); } @override public void handleMessage(Message MSG) {// Execute a Message to update the control in the main thread. if (mTextViewRef ! = null && mTextViewRef.get() ! = null) { mTextViewRef.get().setText("execute message"); }}}; @Override public void onDestroy() { super.onDestroy(); / / exit situation messages in the queue mMyHandler removeCallbacksAndMessages (null); }}Copy the code
The combination of “static inner class” and “weak reference” allows you to access variables without holding references to external class objects, and to remove messages from the message queue when the Activity exits, further avoiding the risk of memory leaks.
This is just one of the ways to avoid memory leaks, but there must be other ways to do this as well, so you can explore for yourself.
5. To summarize
This article explains how to use the Android message mechanism, the overall process and the implementation principle of each stage, in the end also mentioned the main thread message loop creation and error use of memory leakage and avoid methods, hoping to help you learn the message mechanism.
Past review:
- Alibaba internal Jetpack treasure book accidentally leaked! The ultimate classic, the ceiling of Android’s architectural components
- Big guy is strong, bytedance high workers stay up half a month finishing the “componentized actual combat learning manual”, all is the essence!
- Git8.3k star, one hundred thousand words Android mainstream open source framework source code analysis, must disk