Moment For Technology

Android messaging

Posted on Aug. 8, 2022, 7:42 p.m. by Sara Leach
Category: android Tag: The front end android

preface

When it comes to Android messaging mechanism, Looper, MessageQueue, Handler and Message are the four objects that come to mind first. Let's take a look at the mechanism of Androd messaging

Looper

Create stars (which preare ())

Process analysis:

  • ActivityThread.main() --Looper.prepareMainLooper()
  • Looper.prepareMainLooper() -- Looper.prepare(boolean quitAllowed)
  • Looper.prepare(boolean quitAllowed) -- new Looper(quitAllowed)
  • new Looper(quitAllowed) -- new new MessageQueue(quitAllowed)

Looper and MessageQueue objects were created through the above process during APP startup

ActivtiyThread.main()

public static void main(String[] args) { ... Prepare (false) creates Looper looper.prepareMainLooper (); prepareMainLooper(); . // Loop to the message looper.loop (); . }Copy the code

Looper.prepareMainLooper()

Public static void prepareMainLooper() {// Call Looper. Prepare () to create Looper prepare(false); synchronized (Looper.class) { if (sMainLooper ! = null) { throw new IllegalStateException("The main Looper has already been prepared."); } // get the created Looper assigned to sMainLooper sMainLooper = myLooper(); }}Copy the code

Looper.prepare()

Private static void prepare(Boolean quitAllowed) {//sThreadLocal obtains the Looper of the current thread. If (sthreadLocal.get ()! = null) { throw new RuntimeException("Only one Looper may be created per thread"); } // Save the current thread's Looper object sthreadLocal. set(new Looper(quitAllowed)); }Copy the code

Looper(boolean quitAllowed)

Private Looper(Boolean quitAllowed) {// create a MessageQueue mQueue = new MessageQueue(quitAllowed); MThread = thread.currentThread (); }Copy the code

Loop message (looper.loop ())

Once the Looper object is created, the looper.loop () method is called and Looper does the real work, polling the Messager messages in the MessageQueue queue

Public static void loop() {// Get the current thread Looper final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); }... // Start message polling for (;;) Message MSG = queue.next(); If (MSG == null) {// if (MSG == null) {return; }... Try {/ / distribute messages according to Message of traget MSG. Target. DispatchMessage (MSG); if (observer ! = null) { observer.messageDispatched(token, msg); } dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } catch (Exception exception) { if (observer ! = null) { observer.dispatchingThrewException(token, msg, exception); } throw exception; } finally { ThreadLocalWorkSource.restore(origWorkSource); if (traceTag ! = 0) { Trace.traceEnd(traceTag); }}... // Message recycling msg.recycleunchecked (); }}Copy the code

Through the above source code analysis:

  • First get the Looper object and throw an exception if it is null
  • Start a loop to retrieve messages
  • Get the message from the message queue via queue.next(), and exit the loop if the message is empty
  • Messages are distributed based on Message's target, which refers to the Handler
  • Call Message's recycleUnchecked() for Message recycling

ThreadLocal

ThreadLocal is an internal data store class that stores data in a specified thread. Once the data is stored, only the specified thread can access the data. Other threads cannot access the data.

In Looper there is an sThreadLocal attribute, which associates the Looper with the thread

public final class Looper {
    static final ThreadLocalLooper sThreadLocal = new ThreadLocalLooper();
}
Copy the code

ThreadLocalMap

There is an array of Entry[] objects in the ThreadLocalMap object. Entry inherits WeakReference, so a weakly referenced ThreadLocal object can be obtained through the GET method of the Entry object. Extends a value Object of type Object and initializes assignment in the constructor. Entry stores a ThreadLocal(key) and its corresponding value. ThreadLoacl is in the form of weak references to avoid memory leaks caused by thread reuse in the thread pool

static class ThreadLocalMap { static class Entry extends WeakReferenceThreadLocal?  { Object value; Entry(ThreadLocal?  k, Object v) { super(k); value = v; }}... private Entry[] table; . }Copy the code

ThreadLocalMap.set()

private void set(ThreadLocal?  key, Object value) { Entry[] tab = table; int len = tab.length; Int I = key.threadLocalHashCode  (len-1); For (Entry e = TAB [I]; for (Entry e = TAB [I]; e ! = null; e = tab[i = nextIndex(i, len)]) { ThreadLocal?  k = e.get(); If (k == key) {e.value = value; if (k == key) {e.value = value; return; If (k == null) {replaceStaleEntry(key, value, I); if (k == null) {replaceStaleEntry(key, value, I); return; TAB [I] = new Entry(key, value); int sz = ++size; // Determine whether the current number of stored objects has exceeded the threshold (value). If so, you need to expand and recalculate all objects. cleanSomeSlots(i, sz)  sz = threshold) rehash(); }Copy the code

ThreadLocal.set()

Public void set(T value) {public void set(T value) { /** Get the current Thread's ThreadLocalMap * from thread.threadlocals the underlying ThreadLocalMap is an Entry[] array consisting of * entries WeakReference * key=ThreadLocal value=Lopper Component object */ ThreadLocalMap map = getMap(t); if (map ! = null) // Set Entry Object key=ThreadLocal, value=Object, Entry array map.set(this, value); Else // createMap(t, value); }Copy the code

The set() method does the following

  • Get current thread
  • Gets the ThreadLocalMap object for the current thread
  • If the obtained map object is not empty, set the value. Otherwise, create the map value

ThreadLocalMap getMap(Thread t)

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
Copy the code

Get the threadLocals variable from the Thread object, which is of type ThreadLocalMap

createMap(Thread t, T firstValue)

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code

ThreadLocal.get()

Public T get() {T = thread.currentThread (); /** Get the current Thread's ThreadLocalMap * from thread.threadlocals the underlying ThreadLocalMap is an Entry[] array consisting of * entries WeakReference * key=ThreadLocal value=Lopper Component object */ ThreadLocalMap map = getMap(t); if (map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e ! = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }Copy the code

The get() method does the following with the above source code

  • Get current thread
  • Gets the ThreadLocalMap object for the current thread
  • Checks whether the obtained map object is empty. If it is empty, the default value is returned
  • Not null, uses the current ThreadLoacl object (this) to get the Entry of ThreadLocalMap, and returns the value that the Entry saved

Message

Message is the Message body, which is responsible for storing various information of the Message, including the Handler object that sends the Message, Message information, Message identity, and so on

attribute

Several commonly used properties

  • What: Indicates the matching code of the message, which is used to distinguish the processing results of different messages
  • Arg1 /arg2: The int Message carries, which can also be set with setData
  • Obj: carry the Message Object information, and to pass Parcelable, otherwise you will be thrown. Java lang. RuntimeException: Can't marshal non-Parcelable objects across processes. Exceptions can also be set using setData
  • Target: The Handler corresponding to this Message, which is used to distribute messages
  • Callback: Processes messages

constant

  • SPoolSync: Acts as a lock to prevent multiple threads from scrambling for resources and causing dirty data
  • SPool: Pointer to the header of the message queue
  • SPoolSize: Length of message queue
  • MAX_POOL_SIZE: the maximum length of messages cached by the message queue

Create Message object obtain()

Public static Message obtain() {// synchronized (sPoolSync) {public static Message obtain() { If empty, there is no Message if (sPool! = null) {// Message of the current node is assigned to m Message m = sPool; // Get the next node of the current Message sPool = m.next; m.next = null; m.flags = 0; // Clear in-use flag // Delete an inserted node sPoolSize--; // Message return m; } } return new Message(); }Copy the code

As you can see from the above code, creating a Message does the following

  • Firstly, the synchronization lock is used to avoid the dirty data caused by multiple threads competing for resources
  • Check whether the current queue header pointer is empty. If it is empty, it indicates that there is no Message in the current queue
  • The current queue header pointer is not null, indicating that there are reusable messages
  • Gets the Message of the current queue header node
  • Set the next node data to sPool
  • Set next of fetched Message to null
  • The current queue length is reduced by one
  • Go back to the imagination

Recycle ()/recycleUnchecked()

Public void recycle() {if (isInUse()) {if (gCheckRecycle) {throw new IllegalStateException("This message cannot be recycled because it " + "is still in use."); } return; // The current message object can be recycleUnchecked(); } void recycleUnchecked() {// Clear the current Message object member variables flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = UID_NONE; workSourceUid = UID_NONE; when = 0; target = null; callback = null; data = null; Synchronized (sPoolSync) {// If the object pool size is smaller than the maximum size, synchronized (sPoolSync) { If (sPoolSize  MAX_POOL_SIZE) {// The current Message is assigned to sPool as the first node. Next = sPool; sPool = this; sPoolSize++; }}}Copy the code

When a Message object is reclaimed, its member variable information is cleared, and then the object is added to the sPool pool as the first node

MessageQueue

Message queues are used to store messages, retrieve messages, and remove messages. Instead of using a List or a Map, it uses Message's internal linked List structure. Then we will study how MessageQueue stores, retrieves and removes messages

Add Message enqueueMessage(Message MSG, long WHEN)

boolean enqueueMessage(Message msg, If (MSG. Target == null) {throw new IllegalArgumentException("Message must ")  have a target."); } synchronized (this) {if (msg.isinuse ()) {throw new IllegalStateException(MSG + "this message is synchronized already in use."); If (McOntract) {IllegalStateException e = new IllegalStateException(MSG. Target + "sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); // recycle messages msg.recycle(); return false; } msg.markInUse(); // Set MSG processing time msg.when = when; // The current queue node Message is assigned to P Message P = mMessages; boolean needWake; / / whether the queue is empty, or Message processing time is zero, or a Message queue node is greater than the current time MSG processing time, directly to join the queue if the last node (p = = null | | the when = = 0 | | the when  p.w hen) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { needWake = mBlocked  p.target == null  msg.isAsynchronous(); Message prev; for (;;) { prev = p; // Fetch the next message p = p.ext; / / according to MSG processing time, determine which node is inserted into the queue in front of the if (p = = null | | the when  p.w hen) {break; } if (needWake  p.isAsynchronous()) { needWake = false; }} next = p; prev.next = msg; } if (needWake) {// Wake up the message loop nativeWake(mPtr); } } return true; }Copy the code

As you can see from the above source code, there are several steps to insert Message

  • Determine if message. target is null and throw once for null
  • Determines whether Message is consumed and throws an exception
  • It checks whether there are messages in the queue or whether the priority of the current message processing is higher than that of the nodes waiting to be processed in the queue. It directly inserts the message into the queue and sets the priority of sending the current message
  • The message is inserted into the appropriate location by comparing the execution time of the message
  • Determines whether to wake up the message loop

Take message next ()

Message next() {       
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; 
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //native方法,nextPollTimeoutMillis为-1时进入睡眠状态
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            //获取当前系统时间
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            //把当前队列最新节点消息赋值给msg
            Message msg = mMessages;
            //开启同步屏障,获取异步消息
            if (msg != null  msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null  !msg.isAsynchronous());
            }
            if (msg != null) {
                //判断当前时间是否小于,msg执行时间
                if (now  msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                      
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    //返回消息
                    return msg;
                }
            } else {
                    
                nextPollTimeoutMillis = -1;
            }
            //退出循环退出
            if (mQuitting) {
                dispose();
                return null;
            }
            //获取空闲IdleHand数量
            if (pendingIdleHandlerCount  0
                     (mMessages == null || now  mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            //判断是否空闲IdleHand数量是否大于零,如果小于零,直接跳过本次轮询
            if (pendingIdleHandlerCount = 0) {
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        //非睡眠状态下处理IdleHandler接口
        for (int i = 0; i  pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; 

            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        pendingIdleHandlerCount = 0;
        
        nextPollTimeoutMillis = 0;
    }
}
Copy the code

As you can see from the above source code, next() does the following

  • If PTR is zero, MessageQueue is released and null is returned
  • Native method, the nextPollTimeoutMillis is -1 to enter the sleep state
  • When MSG. Target == null, the synchronization barrier is enabled, and Message loops through the following message until it reaches the end of the queue or finds an isAsynchronous()==true message, otherwise the first synchronous message will be processed
  • Compare whether the currently imagined execution time is less than the current system time, or return the MSG object, otherwise the current time to when is calculated and saved to nextPollTimeoutMillis
  • Gets the size of the IdleHandl set. If the size of IdleHandl is greater than zero, the queueIdle() method for each IdleHandl is executed otherwise

Exit message quit(Boolean safe)

Void quit(Boolean safe) {// Whether MessageQueue can exit, if not, throw an exception if (! mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; // Set the exit status to mspapers = true; If (safe) {/ / handle all delay message removeAllFutureMessagesLocked (); } else {// Process all messages removeAllMessagesLocked(); } // Wake up the message loop nativeWake(mPtr); }} / / processing delay delay message private void removeAllFutureMessagesLocked () {/ / get the current system time final long now. = SystemClock uptimeMillis (); Message p = mMessages; if (p ! If (p.hen  now) {removeAllMessagesLocked(); } else { Message n; // for (;;) { n = p.next; if (n == null) { return; } if (n.hen  now) {// find the delay message break; } p = n; } p.next = null; Do {p = n; n = p.next; // Message recycling p.ricycleunchecked (); } while (n ! = null); Private void removeAllMessagesLocked() {Message p = mMessages; while (p ! = null) { Message n = p.next; // Message recycling p.ricycleunchecked (); p = n; } mMessages = null; }Copy the code

As you can see from the above source code, quit() has the following steps

  • Set the exit status so that null is returned in the next method, causing looper.loop () to exit the loop
  • Determine how to process messages, processing all delayed messages if safe is true, processing all messages otherwise
  • All messages call message.recycleunchecked () for Message recycling

Handler

Handler is a message distribution object. Handler is a set of mechanisms that Android provides for us to update the UI. It is also a set of message processing mechanism, we can send messages, and can process messages through it.

Create a Handler

 public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
     mLooper = looper;
     mQueue = looper.mQueue;
     mCallback = callback;
     mAsynchronous = async;
 }
Copy the code

Handler is created with Lopper and MessagerQueue bound, so each Handler holds a Looper and MessageQueue. So the child thread sends the message with handler, and the message is sent to the MessageQueue associated with the main thread, The handler is also associated with the main thread Looper and MessageQueue, so the final message processing logic is also on the main thread. The Handler is associated with the Looper of which thread. The message processing logic is executed in the thread related to it, and the direction of the corresponding message is also in the associated MessageQueue. So it's a natural process for the child thread to switch to the main thread.

Send a message

There are many ways to send a message in the Handler class, eventually calling the sendMessageAtTime() method

Public Boolean sendMessageAtTime(@nonnull Message MSG,); long uptimeMillis) { MessageQueue queue = mQueue; // Check whether the message queue is empty. 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(@NonNull MessageQueue queue, @nonnull Message MSG,long uptimeMillis) {//Message binding Handler MSG. Target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); // If (mAsynchronous) {msg.setasynchronous (true); } / / call the MessageQueue. Return of enqueueMessage put a Message in the queue queue. EnqueueMessage (MSG, uptimeMillis); }Copy the code

As you can see from the above source code, the Handler sends the message mainly to do the following steps

  • Calculate the absolute time required to deliver the message
  • Message binds a Handler to handle the Message once it is fetched in the loop() method
  • Add the Message Message to the MessageQueue

Message processing

Public void dispatchMessage(@nonnull Message MSG) {if (MSG. Callback! = null) { handleCallback(msg); } else {// Handler's mCallback is null if (mCallback! If (McAllback.handlemessage (MSG)) {return; } // Handle the message handleMessage(MSG); }}Copy the code

As you can see from the above source code, the Handler does the following steps to process the message

  • When the callback in Message is not null, the method in the callback in Message is executed. This callback is a Runnable interface
  • When the Callback interface in this Handler is not null, the method in the Callback interface is executed
  • Execute the handleMessage() method in the Handler directly

conclusion

The processing flow of Android message mechanism is as follows:

  • Looper and MessageQueue are initialized in the main method of ActvityThread when the application startup/mainline layer starts
  • After Lopper and MessageQueue are initialized, the looper.loop () method is called in the main method of ActvityThread for message polling
  • When using handlers, a Lopper and MessageQueue are bound when the Handler is created
  • The Handler inserts the message into MessageQueue via the sendMessage method
  • The Handler processes the message through the handlerMessage
Search
About
mo4tech.com (Moment For Technology) is a global community with thousands techies from across the global hang out!Passionate technologists, be it gadget freaks, tech enthusiasts, coders, technopreneurs, or CIOs, you would find them all here.