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 MessageQueue, and hands them over to 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 data structure queue for 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 event processing
Thread & Handler have a one-to-many relationship

Handler#

Receives 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 and handle events
    public void handleMessage(@NonNull Message msg) {
    }

    // Dispatching, there are three ways
    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, there are three methods for dispatching events, and these three dispatch methods have a sequence; as long as one is processed first, the others will not be used.
Message's own callback
Implement the Callback interface
Override Handler#handleMessage Function
Enqueue messages into 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 Message and then use Send to send it 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 Message
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 resource 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 the specified time
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            // If the 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
sendMessageAtTimeMessage, int : booleanSends an int information specified by the user, to be handled by the user
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, they all call 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 its 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 properties, thus having queue functionalities

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

private native static int nativeInit();

MessageQueue Creation#

Connected to the Looper's constructor, when Looper is created, 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;    // Previous Message is the current Message
                        } else {
                            mMessages = msg.next;    // Switch to the next Message to be used
                        }

                        // Disconnect !! To allow GC to detect and reclaim !!! (reachability analysis)
                        msg.next = null;

                        ... debug message

                        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 users when there are no messages in the MessageQueue and decide how to handle it

// MessageQueue.java

    public static interface IdleHandler {
        // Returning true keeps your set interface alive
		// Returning false removes 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), and its role is to drive

NameParameter : ReturnExplanation
myLoopervoid : LooperGets the Looper of the current Thread
getMainLoopervoid : LooperGets the Looper of the main thread

Looper - ThreadLocal#

Finally, Looper is the driving force behind the entire Handler mechanism; with Looper's push, 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();
    }

}

When you create a thread, you must prepare a Looper for that thread; otherwise, when the Handler sends a message, it will crash (as mentioned above, it will check). The main steps of creation are as follows:
Prepare Looper (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, while 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 in the Handler constructor

// 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 the 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.

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

        // Obtain the current thread's Looper
        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 is synchronized by class.

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 contained within the Looper, and finally enters an infinite loop through 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 {
            // Resolve who is calling dispatchMessage, target is the Handler in 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 by thread). In simple terms, 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.