What we should know about Handler

Handler can help us perform tasks in a specific thread. We can also use Handler to plan a task to be executed at some point in the future. Handler will queue the tasks we give it for execution in a specific thread. If we want to do some tasks in a certain thread, we can specify the Looper of the Handler, and then the Handler will add the task to the message queue corresponding to the Looper.

        val handler = Handler(Looper.getMainLooper())
        val runnable = Runnable {
    
    
            println("Hello world")
        }
        handler.postDelayed(runnable,3_000L)

Create Looper

When we call it in a thread Looper.prepare(), we will create one for this thread Message Queue.

Looper.getMainLooper()The relevant code:
This is the main function called when the application starts. It doesn't matter where the main function is written. In the computer operating system, it has been agreed that the entry point of the program is main, so you will also see that it is static. The main function of the android application is written in the file ActivityThread.java. The following is the Looper used to prepare the main thread when enabling startup:

    public static void main(String[] args) {
    
    
       	...
        Looper.prepareMainLooper();
        ...
     }

prepareMainLooper()Function:
see not! It calls the prepare() function. Prepare Looper for current thread

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

prepare()Function:
Create a Looper for the current thread (main thread), because it is the main thread, so quitAllowed must pass false. Otherwise, the messages in the Message Queue are at risk of being destroyed.

    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));
    }

When creating a Looper, a MessageQueue will be created for the current thread:

    private Looper(boolean quitAllowed) {
    
    
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
 void quit(boolean safe) {
    
    
        if (!mQuitAllowed) {
    
    
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
    
    
            if (mQuitting) {
    
    
                return;
            }
            mQuitting = true;

            if (safe) {
    
    
                removeAllFutureMessagesLocked();
            } else {
    
    
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

The created Looper will be saved:

    public void set(T value) {
    
    
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

After the prepare() function call is completed, the Looper creation work is completed. The call myLooper()is to get the Looper back and assign it to sMainLooper. Looper.getMainLooper() gets this value:

    public static @Nullable Looper myLooper() {
    
    
        return sThreadLocal.get();
    }
 public static @Nullable Looper myLooper() {
    
    
        return sThreadLocal.get();
    }
public T get() {
    
    
        Thread t = Thread.currentThread();
        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();
    }

After the application is started, a Looper is created for the main thread immediately, and Looper.getMainLooper()the Looper of the main thread can be obtained by passing it.

public static Looper getMainLooper() {
    
    
        synchronized (Looper.class) {
    
    
            return sMainLooper;
        }
    }

By the way, the MessageQueue created by Looper is a linked list whose elements are Message:

public final class MessageQueue {
    
    
    @UnsupportedAppUsage
    Message mMessages;
    
    ...
	@UnsupportedAppUsage
	Message next() {
    
    
        ...
        for (;;) {
    
    
            ...
            synchronized (this) {
    
    
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
    
    
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
    
    
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
    
    
                    if (now < msg.when) {
    
    
                        ...
                    } else {
    
    
                        // Got a message.
                        ...
                        if (prevMsg != null) {
    
    
                            prevMsg.next = msg.next;
                        } else {
    
    
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        ...
                        msg.markInUse();
                        return msg;
                    }
                } 
				...
            }
        }
    }
}

The relationship between Handler and Looper

What is the use of passing the Looper of the main thread to the Handler? First, Handler is android.osan ordinary class in the package.
We create a Handler instance that references the Looper of the main thread:

val handler = Handler(Looper.getMainLooper())
public Handler(@NonNull Looper looper) {
    
    
    this(looper, null, false);
}

Through the reference to the main thread Looper, the Handler also refers to the MessageQueue of the main thread Looper:

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

How does Handler use Looper? I use the above as an example to illustrate:

handler.postDelayed(runnable,3_000L)
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
    
    
   return sendMessageDelayed(getPostMessage(r), delayMillis);
}

getPostMessage() generates a Message from our tasks (tasks are written in Runnable):

private static Message getPostMessage(Runnable r) {
    
    
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

Message.obtain() will first try to find one from the message pool, if not, create a new one. The message pool is a linked list:

    public static Message obtain() {
    
    
        synchronized (sPoolSync) {
    
    
            if (sPool != null) {
    
    
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

Our task is placed on the callback field of the Message, and the target of the Message will be our Handler object, please continue reading:

Message{
    
    
	...
    @UnsupportedAppUsage
    /*package*/ Runnable callback;
        @UnsupportedAppUsage
    /*package*/ Handler target;
    ...
}

sendMessageAtTime() function, this is a transition, make some preparations:

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    
    
        if (delayMillis < 0) {
    
    
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

Handler takes out Looper's MessageQueue, ready to do the final message enqueue operation:

    public boolean sendMessageAtTime(@NonNull 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;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

The Handler object gives its own reference to the target field of the created Message object, and then sends the Message to the queue through the MessageQueue object of the Looper object:

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
    
    
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
    
    
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

So far, the Handler has completed its work, so when will the Message in the Looper's MessageQueue be taken out for operation?

Message execution

There is such a line at the end of the main function Looper.loop();, so that the android application will not exit, so it will continue to loop. Otherwise the main function will be terminated.

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

        Looper.loop();

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

The loop() function will take out the Looper of the current thread. For example, if the current thread is the main thread, then myLooper() will get back the Looper() of the main thread.

public static void loop() {
    
    
        final Looper me = myLooper();
        ...
        for (;;) {
    
    
            if (!loopOnce(me, ident, thresholdOverride)) {
    
    
                return;
            }
        }
    }

Take a Message from the MessageQueue of the Looper of the current thread, and then send the Message to the Handler object recorded in the target field of the Message for processing:

    private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
    
    
        Message msg = me.mQueue.next(); // might block
        ...
        try {
    
    
            msg.target.dispatchMessage(msg);
          ...  
        } 
        
        ...

        return true;
    }

Handler's dispatchMessage() method, if the callback is not null, it will be executed. The callback records our tasks:

 public void dispatchMessage(@NonNull Message msg) {
    
    
        if (msg.callback != null) {
    
    
            handleCallback(msg);
        } else {
    
    
            if (mCallback != null) {
    
    
                if (mCallback.handleMessage(msg)) {
    
    
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
    
    
        message.callback.run();
    }

If msg.callback is null and a callback is passed when creating the Handler object, the callback will be called first and then the method handleMessage():

public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async)
    public void handleMessage(@NonNull Message msg) {
    
    
    }

handleMessage()is an empty method, so practice in this area often requires us to rewrite it, for example:

val handler = object: Handler(Looper.getMainLooper()){
    
    
            override fun handleMessage(msg: Message) {
    
    
                println("Hello world")
            }
        }
        
val msg = Message.obtain()
handler.sendMessage(msg)

So far we know that the key to which thread the task is executed in is where prepare() is executed, that is, for which thread the Looper is created, and the task sent to this Looper will be executed in the corresponding thread.

What exactly does runOnUiThread do?

class Activity  ... {
    
    
	...
	 final Handler mHandler = new Handler();
	 ...
    public final void runOnUiThread(Runnable action) {
    
    
        if (Thread.currentThread() != mUiThread) {
    
    
            mHandler.post(action);
        } else {
    
    
            action.run();
        }
    }
}
    public final boolean post(@NonNull Runnable r) {
    
    
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

When the Activity instance is created, a Handler instance is also constructed, runOnUiThread()which is to use this Handler instance to find tasks from the Looper of the main thread.

Handler's knowledge is roughly like this, and we will share more other knowledge involved in the future.

Guess you like

Origin blog.csdn.net/weixin_40763897/article/details/128962456