banner
fwrite

fwrite

好好生活
twitter
github
email

Handler mechanism

Overview#

ClassDescription
HandlerThe place where events are actually processed
LooperLoops to check for events to be dispatched, retrieves events from the MessageQueue, and hands them over to the Handler for processing (if the queue is empty, it goes to sleep)
MessageQueueStores tasks to be done, generally only allows saving objects of the same type, a queue structure for storing Messages
MessageThe event that needs to be processed

One Thread corresponds to one Looper
One Looper corresponds to one MessageQueue
One MessageQueue contains multiple Messages
Each Message corresponds to at most one Handler for processing events
Thread & Handler have a one-to-many relationship

Handler#

Accepts and processes Messages
Stores Messages in the MessageQueue

// Handler.java

public class Handler {
    ...

    final Looper mLooper;
    final MessageQueue mQueue;
    final Callback mCallback;

    ...

     public interface Callback {
        boolean handleMessage(@NonNull Message msg);
    }

    // Typically used for users to override event handling
    public void handleMessage(@NonNull Message msg) {
    }

    // Dispatching, there are three methods
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
}

In the above dispatching of events, there are three methods, and these three dispatch methods have a specific order; as long as one is handled first, the subsequent methods will not be used.
Message's own callback
Implement the Callback interface
Override Handler#handleMessage Function
Enqueue messages to the MessageQueue through Handler, there are two series of methods: Post & Send, and the sending time can be controlled
Post series: Convert scattered information into Messages and then use Send to send out

FunctionFunctionality
boolean post(Runnable r)Directly sends information
boolean postAtTime(Runnable, long updatetimeMillis)Sends the information at a fixed (specified) time
Send series: Parameters are directly Messages
FunctionFunctionality
boolean sendEmptyMessage(int what)Directly sends information
boolean sendMessageArFrontOfQueue(Message msg)Sends the message to the front of the message queue
boolean sendMessageAtTime(int what, long updatetimeMillis)Sends the information at a fixed (specified) time
boolean sendMessageDelayed(Message msg, long delayMillis)Sends the information after a delay of a few milliseconds

Post implementation method

// Handler.java

public class Handler {

    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    // Wraps the callback and sends it out
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();        // Obtain Message resources to avoid constant creation, can reuse resources
        m.callback = r;        // Directly set the Runnable callback function
        return m;
    }

    // Finally, through calculation of current time + extension, call sendMessageAtTime function
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    // Sends information at a specified time
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            // If this Thread does not have a specified MessageQueue, an error will be thrown
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

}

Sending Information Methods#

NameParameter : ReturnExplanation
postRunnable : booleanAccepts a Runnable object as a Message
postAtTimeRunnable, long : booleanAccepts a Runnable object as a Message and sends it after a specified wait time
sendEmptyMessageint : booleanSends an int information specified by the user, to be handled by the user themselves
sendMessageAtTimeMessage, int : booleanSends an int information specified by the user, to be handled by the user themselves
sendMessageDelayedMessage, long : booleanSends Message after a specified wait time (delay)

Post & Send#

// Send ...
    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }
    
// Post ...
    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
    
// Ultimately all will sendMessageAtTime
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {    // 1. 
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

Processing Message#

From dispatchMessage, it can be seen that it has a preset order for processing Messages
msg.callback (Runnable)
mCallback (interface)
handleMessage (method)

public void handleMessage(Message msg) {
     // User overrides
 }
  
 /**
  * Handle system messages here.
  */
 public void dispatchMessage(Message msg) {
     // callback has the highest priority
     if (msg.callback != null) {		// msg.callback is Runnable
         handleCallback(msg);
     } else {
         if (mCallback != null) {
             if (mCallback.handleMessage(msg)) {
                 return;
             }
         }
         handleMessage(msg);
     }
 }

MessageQueue & Message#

As the name suggests, it is a FIFO queue, created through native method (Native) memory pointer

private native static long nativeInit();
    
MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

Queue Methods#

NameParameter : ReturnExplanation
enqueueMessageMessage, long : booleanPush into the queue within the specified time
nextvoid : MessagePull an element from the queue
removeMessagesHandler, int, Object : voidRemove specific elements
removeMessagesHandler, Runnable, Object : voidRemove specific elements

MessageQueue#

Since MessageQueue is a Queue, it has FIFO functionality, thus having queue capabilities

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

private native static int nativeInit();

MessageQueue Creation#

Connected to the Looper's constructor, when the Looper is created, the MessageQueue is created together

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

Looper Obtains Message Through MessageQueue#

// MessageQueue.java

    Message next() {

        // If already disposed, mPtr will be set to 0
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration

        // Time for the next message
        int nextPollTimeoutMillis = 0;

        // Entering an infinite loop, meaning next will cause a block
        for (;;) {
            ... omitted part

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();

                Message prevMsg = null;    // Previous message
                Message msg = mMessages;    // Current message

                if (msg != null && msg.target == null) {    // target is the Handler

                    // Found a message that can be sent
                    do {
                        prevMsg = msg;
                        msg = msg.next;    // Switch to the next Message
                    } while (msg != null && !msg.isAsynchronous());
                }

                // Check the Message again
                if (msg != null) {

                    // Check the time
                    if (now < msg.when) {    // The time for this message has not yet arrived
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        // Calculate remaining time
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Time is up, and there is a Message
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;    // The previous Message is the current Message
                        } else {
                            mMessages = msg.next;    // Switch to the next Message to be used
                        }

                        // Disconnect !! So that GC can detect and reclaim !!! (reachability analysis)
                        msg.next = null;

                        ... debug messages

                        msg.markInUse();    // Mark this Message as already used
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

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

                ... omitted IdleHandler
            }

            ... omitted part
        }

IdleHandler#

IdleHandler is an interface within MessageQueue that can notify the user when there are no messages in the MessageQueue and decide how to handle it

// MessageQueue.java

    public static interface IdleHandler {
        // Returns true, allowing your set interface to continue to exist
		// Returns false to remove your set interface
        boolean queueIdle();
    }

Similarly, analyzing the next method, it can be seen that a MessageQueue can set up to four IdleHandlers

// MessageQueue.java

    Message next() {

        // If already disposed, mPtr will be set to 0
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration

        // Time for the next message
        int nextPollTimeoutMillis = 0;

        // Entering an infinite loop, meaning next will cause a block
        for (;;) {
            ... omitted part

            synchronized (this) {

                ... omitted part

                // Check if there are IdleHandlers set
                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;
                }

                // At most set 4 Idle Handlers
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            
            // Loop
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    // Notify the interface when there are no messages
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                // Return false to remove
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            ... omitted part
        }
    }

Looper#

It is a key component of messages (Handler, Message, MessageQueue), its role is to drive

NameParameter : ReturnExplanation
myLoopervoid : LooperObtains the Looper of the current Thread as the main one
getMainLoopervoid : LooperObtains the Looper of the main thread

Looper - ThreadLocal#

Finally, Looper serves as the driving force for the entire Handler mechanism; with Looper's push, the Handler can normally receive and send messages.
Looper contains a MessageQueue (constructed in the constructor)

// android/os/Looper.java

public final class Looper {

    // Private function
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

}

Whenever you create a thread, you must prepare a Looper for that thread; otherwise, when the Handler sends messages, it will crash (as mentioned earlier, it will check). The main steps for creation are as follows:
Looper preparation (prepare function)
Create Handler to send & receive messages
Looper starts operating (loop function)

class LooperThread extends Thread {

    private Handler handler;

    public void run() {
        // 1
        Looper.prepare();

        // 2.
        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                //... Process Message
            }

        }

        // 3.
        Looper.loop();
    }

}

ThreadLocal is briefly mentioned here; ThreadLocal is for thread isolation, using the thread Thread as the Key to store, and the Value is the data you want to copy to each thread (each child thread holds one data)

// android/os/Looper.java

public final class Looper {

    // Copying thread data is Looper
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null)     // A thread can only prepare once
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // Set Looper for the current Thread
        sThreadLocal.set(new Looper(quitAllowed));
    }
}

When you create a Handler, it will obtain the Looper through the current thread during the Handler construction

// Handler

    public Handler(@Nullable Callback callback, boolean async) {
        ...
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }


// -----------------------------------------------------------------
// Looper 

    public static @Nullable Looper myLooper() {
        // sThreadLocal is static and will obtain Looper through the current Thread as Key
        return sThreadLocal.get();
    }

Looper & Handler#

The relationship between Looper and Handler can be seen from the new Handler() constructor; it obtains the current thread's Looper through the myLooper method in the ThreadLocal.

// Handler construct
    public Handler() {
        this(null, false);
    }
    
    public Handler(Callback callback, boolean async) {
        ... omitted

        // Obtain the Loop of the current thread
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
// Looper...
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

Looper & UI Thread#

In Android, the main thread of an Activity is ActivityThread’s main()

public static void main(String[] args) {
        ...

        Looper.prepareMainLooper(); //"1. "

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {    
            //"2. "
            sMainThreadHandler = thread.getHandler();
        }

        ...

        // End of event ActivityThreadMain.
        ...
        
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

The main thread's Looper cannot be closed and uses class synchronization.

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

// Properties...
    private static Looper sMainLooper;  // guarded by Looper.class

Looper#loop, first obtains the Looper owned by the current thread, then retrieves the MessageQueue within the Looper, and finally enters an infinite loop over the entire message queue.

// Looper.java

public static void loop() {
        final Looper me = myLooper();

        ...

        final MessageQueue queue = me.mQueue;

        ...

        for (;;) {
            // 
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ...
            try {
            // Resolving who is calling dispatchMessage, target is the Handler in the MessageQueue
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...

            msg.recycleUnchecked();
        }
    }

ThreadLocal#

The main function of ThreadLocal is to achieve thread isolation (isolate data for threads). Simply put, you can think of ThreadLocal as a Map (but it is not actually a Map); its Key is Thread, and the Value is a generic type.

// The set() method is used in Looper.prepare() internally
    public void set(T value) {
        Thread t = Thread.currentThread();    //"1. "
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }


// The get() method is used in the Handler() constructor internally
public T get() {
        Thread t = Thread.currentThread();    //"2. "
        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();
    }

It uses the currently calling Thread.currentThread as the Key to store, and the Value is the generic type.
According to the current thread (currentThread), it retrieves the Value.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.