深入理解Android消息機(jī)制之Handler,Looper,MessageQueue

前言

重新研究了Android Handler,Looper,MessageQueue的源碼,收獲很多,這里記錄一下。文中會(huì)提幾個(gè)問(wèn)題,來(lái)更好的引起思考。

正文

說(shuō)到用來(lái)線(xiàn)程間通信的Handler機(jī)制,我們就會(huì)談及Looper和MessageQueue。簡(jiǎn)單來(lái)說(shuō),Looper就是一個(gè)循環(huán)器,每一個(gè)線(xiàn)程都只有自己的一個(gè)Looper對(duì)象,而每一個(gè)Looper對(duì)象也都會(huì)綁定一個(gè)消息隊(duì)列,Looper的任務(wù)就是負(fù)責(zé)循環(huán)讀取這個(gè)消息隊(duì)列。如果有消息就交給Handler處理,然后Handler調(diào)用handleMessage回調(diào)到主線(xiàn)程去更新UI,而如果隊(duì)列為空則阻塞等待消息返回。而且,Handler的任務(wù)除了剛才說(shuō)的處理消息更新UI,還負(fù)責(zé)在其他線(xiàn)程中發(fā)送消息到消息隊(duì)列中。下面一張引用他人的圖:

圖解
圖解

注意左上角的Handler持有的引用是主線(xiàn)程的,是主線(xiàn)程把Handler對(duì)象的引用傳遞給其他線(xiàn)程的。

以上的東西相信都能理解,下面提幾個(gè)問(wèn)題。
1、如何做到每個(gè)線(xiàn)程只有一個(gè)looper對(duì)象?
這里要引出ThreadLocal的概念了。Looper中有一個(gè)成員變量sThreadLocal。

 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

ThreadLocal不是線(xiàn)程,它是一個(gè)關(guān)于創(chuàng)建線(xiàn)程局部變量的泛型類(lèi),使得各線(xiàn)程能夠保持各自獨(dú)立的一個(gè)對(duì)象,而其他線(xiàn)程訪(fǎng)問(wèn)不到。具體的做法是通過(guò)在初始化Looper的時(shí)候調(diào)用sThreadLocal.set(new Looper())。set方法如下

public void set(T value) {  
    Thread currentThread = Thread.currentThread();  
    Values values = values(currentThread);  
    if (values == null) {  
        values = initializeValues(currentThread);  
    }  
    values.put(this, value);  
}     

即獲取當(dāng)前的線(xiàn)程的ID,為線(xiàn)程實(shí)例化一個(gè)Looper并存儲(chǔ)到這個(gè)線(xiàn)程。所以,每一個(gè)線(xiàn)程有維持著一個(gè)values(里面是一個(gè)table數(shù)組),放著不同于其他線(xiàn)程的一個(gè)Looper,通過(guò)ThreadLocal的set和get方法可以訪(fǎng)問(wèn)(Handler的實(shí)例化其實(shí)內(nèi)部是調(diào)用了sThreadLocal.get()去獲取此線(xiàn)程的Looper,然后通過(guò)send方法就可以往這個(gè)Looper所綁定的消息隊(duì)列中放消息了)。不同的線(xiàn)程中雖然訪(fǎng)問(wèn)的是同一個(gè)ThreadLocal的set和get方法,但它們對(duì)ThreadLocal所做的讀寫(xiě)操作都只是在各自線(xiàn)程的內(nèi)部。這樣就做到了一個(gè)線(xiàn)程擁有唯一一個(gè)Looper。

另外,非UI線(xiàn)程是默認(rèn)沒(méi)有Looper的,如果需要使用Handler就必須為線(xiàn)程創(chuàng)建Looper,并顯式調(diào)用prepare和loop方法。而UI線(xiàn)程,其實(shí)就是ActivityThread,ActivityThread被創(chuàng)建時(shí)就會(huì)初始化Looper,這也是在主線(xiàn)程中默認(rèn)可以使用Handler的原因。

2、消息隊(duì)列是以隊(duì)列的數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)消息?
其實(shí)消息隊(duì)列的內(nèi)部存儲(chǔ)結(jié)構(gòu)并不是真正的隊(duì)列,而是采用單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)消息的,Message對(duì)象內(nèi)部包含一個(gè)next變量,該變量指向下一個(gè)消息對(duì)象。
原因是隊(duì)列的方式是先進(jìn)先出,而消息隊(duì)列中消息是按時(shí)間排序的。所以使用鏈表實(shí)現(xiàn)才能更快的在任意位置插入消息,復(fù)雜度O(1)。

3、消息隊(duì)列具體怎么放消息,取消息?
放消息:
MessageQueue對(duì)象通過(guò)調(diào)用enqueueMessage()放消息,首先會(huì)判斷一下,如果新添加的消息的執(zhí)行時(shí)間when是0,或者其執(zhí)行時(shí)間比消息隊(duì)列頭的消息的執(zhí)行時(shí)間還早,就把消息添加到消息隊(duì)列頭(也就是鏈表頭),否則就找到合適的位置將當(dāng)前消息添加到消息隊(duì)列。最終,消息隊(duì)列中的消息是按when從小到大排序的(when最小的消息在鏈表頭)。

取消息:
取消息是在next()方法中,首先找到消息隊(duì)列頭部的消息或者第一個(gè)異步消息,如果當(dāng)前時(shí)間小于此Message的when,就計(jì)算時(shí)間差并賦值給nextPollTimeoutMillis(到達(dá)時(shí)間再進(jìn)行喚醒),否則返回此消息給Looper。
next()是MessageQueue中的一個(gè)非常重要的函數(shù),簡(jiǎn)單來(lái)說(shuō),它的任務(wù)就是獲取消息隊(duì)列中的下一個(gè)消息,然后并返回給Looper。下面深入到MessageQueue next方法中:

  Message next() {
        //mPtr代表c++層NativeMessageQueue對(duì)象
        final long ptr = mPtr;
        if (ptr == 0) {//表示消息循環(huán)已經(jīng)退出,直接返回
            return null;
        }
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;//下一次執(zhí)行的時(shí)間
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);//在這個(gè)方法中有可能會(huì)發(fā)生線(xiàn)程空閑等待,即當(dāng)nextPollTimeoutMillis=-1時(shí),表示沒(méi)有消息,或者當(dāng)nextPollTimeoutMillis>0,即沒(méi)有到達(dá)下一條消息的執(zhí)行時(shí)間。這兩種情況下都會(huì)調(diào)用到C層的epoll_wait函數(shù)來(lái)等待管道中有內(nèi)容可讀的,直到在enqueueMessage中有新消息入隊(duì),此時(shí)會(huì)調(diào)用nativeWake(mPtr)往管道的寫(xiě)入一個(gè)字符串從而去喚醒此線(xiàn)程.
            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.
                    //查詢(xún)MessageQueue中的下一條異步消息
                    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.
                        //設(shè)置下一次輪詢(xún)消息的超時(shí)時(shí)間
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        // 獲取一條消息,并返回
                        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 {
                    // No more messages.
                    //沒(méi)有消息
                    nextPollTimeoutMillis = -1;
                }
                // Process the quit message now that all pending messages have been handled.
                ////消息正在退出,返回null
                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.
                //執(zhí)行到這里意味著沒(méi)有消息或者消息的執(zhí)行時(shí)間還沒(méi)到達(dá)。在進(jìn)入空閑等待狀態(tài)前,如果應(yīng)用程序注冊(cè)了IdleHandler接口來(lái)處理一些事情,那么就會(huì)先執(zhí)行這里IdleHandler,然后再進(jìn)入等待狀態(tài)
                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);
            }
            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            //執(zhí)行這些注冊(cè)了的IdleHanlder
            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 {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;
            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            //重置nextPollTimeoutMillis為0,因?yàn)橛锌赡茉趫?zhí)行IdleHandler的時(shí)候,已經(jīng)有新的消息加入到消息隊(duì)列中去了
            nextPollTimeoutMillis = 0;
        }
    }

什么時(shí)候會(huì)進(jìn)入阻塞狀態(tài)?
有兩種情況,一是當(dāng)消息隊(duì)列中沒(méi)有消息時(shí),它會(huì)使線(xiàn)程進(jìn)入等待狀態(tài);二是消息隊(duì)列中有消息,但是消息指定了執(zhí)行的時(shí)間,而現(xiàn)在還沒(méi)有到這個(gè)時(shí)間,線(xiàn)程也會(huì)進(jìn)入等待狀態(tài)。


下面提煉Handler,Looper,MessageQueue的整個(gè)設(shè)計(jì),可以更好的理解整個(gè)消息機(jī)制

1、首先是Looper

public class Looper {
    /**
     * 每個(gè)線(xiàn)程都只有一個(gè)Looper對(duì)象
     */
    static ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
     //消息隊(duì)列
    MessageQueue mQueue;
    Looper(){
        mQueue = new MessageQueue();
    }
    /**
     * 實(shí)例化一個(gè)Looper,并存儲(chǔ)到該線(xiàn)程。
     */
    public static void prepare(){
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
    /**
     * 在線(xiàn)程上開(kāi)啟循環(huán)
     */
    public static void loop(){
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        while (true){
            // 獲取下一個(gè)Message,只有當(dāng)存進(jìn)去null的時(shí)候才會(huì)返回null
            // 而當(dāng)隊(duì)列為空時(shí),會(huì)阻塞直到隊(duì)列有Message再返回
            Message msg = mQueue.next();
            //如果取到null,就退出
            if (msg == null)return;
            //交給Message的目標(biāo)Handler去處理
            msg.target.dispatchMessage(msg);
        }
    }
    /**
     * 獲取當(dāng)前線(xiàn)程的Looper
     */
    public static Looper myLooper(){ return sThreadLocal.get();}
}

2、接著是消息隊(duì)列

public class MessageQueue {
    /**
     * 消息隊(duì)列,Android源碼自己實(shí)現(xiàn)了線(xiàn)程安全,這里簡(jiǎn)單模擬原理
     */
    private LinkedBlockingDeque<Message> queue = new LinkedBlockingDeque<>();
    private final Object lock = new Object();
    
    /**
     * 返回一個(gè)message
     */
    Message next(){
        while (true){
            if (queue.peek() !=null){
                //如果有Message,直接返回
                return queue.poll();
            }else {
                //如果沒(méi)有Message,則阻塞,等待加入新Message時(shí)喚醒
                synchronized (lock){
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    /**
     * 向隊(duì)列加入一個(gè)Message,并喚醒前面的wait
     * 這個(gè)函數(shù)可以在任意線(xiàn)程調(diào)用
     */
    void enqueueMessage(Message message){
        synchronized (lock){
            queue.push(message);
            lock.notify();
        }
    }
}

3、Message的定義

public class Message {

    //屬性
    public int what;
    public int arg1;
    public int arg2;
    public Object obj;

    //可以把Runnable接口封裝到消息
    Runnable callback;
    /**
     * 目標(biāo)Handler
     */
    Handler target;

    public Message(Runnable callback, int what) {
        this.callback = callback;
        this.what = what;
    }
}

4、最后是Handler

public class Handler {
    private Looper looper;
    final Callback mCallback;

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

    public Handler(){
        this(null);
    }

    public Handler(Callback callback) {
        looper = Looper.myLooper();
        mCallback = callback;
    }
    
    public void post(Runnable runnable){
        send(new Message(runnable,0));
    }

    //向Looper的消息隊(duì)列中發(fā)送Message
    public void send(Message message){
        looper.mQueue.enqueueMessage(message);
    }

    //在此線(xiàn)程中處理Message
    public void dispatchMessage(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();
    }
    //子類(lèi)需要實(shí)現(xiàn)此方法去處理消息
    public void handleMessage(Message msg) {
    }
}

總結(jié)

分析完源碼后感覺(jué)自己對(duì)消息機(jī)制的理解更深了一點(diǎn),或者說(shuō)印象更深刻了。很多時(shí)候遇到不懂的東西從源碼入手會(huì)有一種豁然開(kāi)朗的感覺(jué)。以上如有誤解之處,請(qǐng)指出。

參考

Android應(yīng)用程序消息處理機(jī)制(Looper、Handler)分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容