android message processing source code analysis

I. Introduction
Message processing mechanism mainly involves these categories:
1.Looper
2.MessageQueue
3.Message
4.Handler

Source code analysis

Key source code of Looper.class:

 

//Preservation Looper Object in android For each message queue created in, there is one and only one corresponding to it Looper object 
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//Main thread Looper
private static Looper sMainLooper;
//Message queue
final MessageQueue mQueue;
final Thread mThread;

//This method is called to create a message queue in a child thread
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 main thread calls this method to create a message queue
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

//instantiation Looper,Create a message queue to get the current thread
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

//call loop Method to open the message loop 
public static void loop() { 
    //Get current Looper Object, if null,Throw exception 
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() 
            wasn't called on this thread."); 
     } 
    //Get the current message queue and enter the loop 
    final MessageQueue queue = me.mQueue; 
    for (;;) { 
        //call next()Method to get a message from the message queue, if null,End loop; otherwise, continue(It may block) 
        Message msg = queue.next(); 
        if (msg == null) { 
            return; 
        } 
        ...... 
        try { 
            //call handler Of dispatchMessage(msg)Distribution message  
            msg.target.dispatchMessage(msg); 
        } finally {
        ...... 
        } 
        //Recycle message resources  
        msg.recycleUnchecked(); 
    }
}

//Message loop exit
public void quit() {
    mQueue.quit(false);
}

public void quitSafely() {
    mQueue.quit(true);
}

 

Message loop exit process

It can be seen from the above that the loop() method is a dead loop, which will only end when the next() method of MessageQueue returns null. When is the next() method of MessageQueue null?

In the Looper class we see two ending methods, quit() and quit sale ().
The difference between the two is that the quit() method directly ends the loop and processes all messages in the MessageQueue.
Quitsafety() exits after processing the remaining non delayed messages in the message queue (delayed messages (delayed messages) are recycled directly). Both methods call the quit() method of MessageQueue

 

Key source code of MessageQueue.class:

The two most important methods in MessageQueue are:
1.enqueueMessage() inserts a message into the queue
2.next() takes the message from the queue

/*
*MessageQueue The enqueueMessage method in has two purposes:
*1.Insert message to message queue
*2.Wake up the waiting thread in Looper (if it is an instant message and the thread is blocked)
*/
boolean enqueueMessage(Message msg, long when) {
    //The handler by null,Throw exception
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    //This message is in use
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        //This message queue has been abandoned
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;
        //The first element of the message queue, MessageQueue Member variables in mMessages It points to the head element of the list.
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            //If the header element in this queue is null(Empty queue, usually for the first time),Or if the message is not a delayed message, it needs to be processed immediately,
            //Take the message as a new header, and next Point to the old head. If it is blocked, it needs to wake up.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            //If the message is a delayed message, it is added to the queue,
            //The principle is to add new elements to the list and insert them in chronological order, so as to get an orderly delay message list  
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p;
            prev.next = msg;
        }
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

Message next() {
    //And native Method correlation,When mPtr Return when 0 null,Exit message loop
    final long ptr = mPtr; 
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1;
    //0 No sleep,-1 Go to sleep 
    int nextPollTimeoutMillis = 0;  
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            //Process pending in the current thread Binder Interprocess communication request
            Binder.flushPendingCommands();  
        }
        //native Method,nextPollTimeoutMillis by-1 Go to sleep when
        //Blocking method, mainly through native Layer epoll It is implemented by listening for write events of file descriptors.
        //If nextPollTimeoutMillis=-1,Stuck does not time out.
        //If nextPollTimeoutMillis=0,No blocking, return immediately.
        //If nextPollTimeoutMillis>0,Longest blocking nextPollTimeoutMillis Millisecond(overtime),If a program wakes up during this period, it will return immediately
        nativePollOnce(ptr, nextPollTimeoutMillis); 
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                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 {
                    //Normal extraction message, setting mBlocked = false It means there is no blocking at present
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.Update to sleep
                nextPollTimeoutMillis = -1;
            }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
        }            
        //Non sleep processing IdleHandler Interface 
        for (int i = 0; i < pendingIdleHandlerCount; i++) { 
            final IdleHandler idler = mPendingIdleHandlers[i]; 
            // release the reference to the handler 
            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;
    }        
}

 

Handler.class source code analysis:

/*
*Send a message to the message queue of the thread through the handler class,
*Each Handler object has a Looper object and a MessageQueue object
*/
public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }
    //Obtain Looper object
    mLooper = Looper.myLooper(); 
    if (mLooper == null) {...}
    //Get message queue
    mQueue = mLooper.mQueue;  
    mCallback = callback;
    mAsynchronous = async;
}

/*
*Many sendMessage methods call the same method sendMessageAtTime()
*/
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;
    }
    //Add a message to the message queue
    return enqueueMessage(queue, msg, uptimeMillis); 
}
    
/*
*1.When the callback in Message is not null, the method in the callback in Message is executed. This callback is a Runnable interface.
*2.When the Callback interface in the Handler is not null, the method in the Callback interface is executed.
*3.Execute the handleMessage() method in the Handler directly.
*/
public void dispatchMessage(Message msg) {
    // news Callback The interface is not. null,implement Callback Interface
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            //Handler Callback The interface is not. null,Implementation interface method
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //Processing message
        handleMessage(msg); 
    }
}

Tags: Android

Posted on Thu, 28 Nov 2019 13:46:21 -0800 by dominant