Android事件驅(qū)動Handler-Message-Looper解析

前言

關(guān)于這方面的知識,網(wǎng)上已經(jīng)有很多。為啥這里還要寫呢?知其然,也要知其所以然,handler-message-looper的設(shè)計很巧妙,我們了解為啥要這么設(shè)計,對我們設(shè)計程序的時候也有裨益。這篇文章闡述怎么結(jié)合現(xiàn)象與原理分析事件驅(qū)動的本質(zhì),相信即使沒看過相關(guān)知識的同學(xué)也能快速理解。
通過這篇文章你將知道:

1、如何進行線程切換
2、消息循環(huán)原理
3、子線程消息循環(huán)
4、鏈表實現(xiàn)棧和隊列的

線程切換

  • Q1 為什么需要線程切換

1、多線程能夠提高并發(fā)效率,多線程的引入勢必會有線程切換
2、多線程同時引入了線程安全問題,Android為了安全、簡單地繪制UI,規(guī)定繪制UI操作只能在一個固定的線程里操作,該線程在app啟動時創(chuàng)建,我們稱之為UI線程(也稱做主線城,后續(xù)簡單起見,統(tǒng)一叫做主線城)。非主線程我們稱之為子線程,當(dāng)我們在子線程里進行耗時操作,并準備好需要展示的數(shù)據(jù)時,這時候需要切換到主線程更新UI
線程安全問題請移步真正理解Java Volatile的妙用

  • Q2 如何切換線程
    先看如何創(chuàng)建并啟動一個線程
                Thread t1 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        doTask1();
                    }
                });
                t1.start();

                Thread t2 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        doTask2();
                    }
                });
                t2.start();

如上所示,t2需要等待t1執(zhí)行完畢再執(zhí)行,那么t2怎么知道t1完畢呢?有兩種方式

1、t1主動告訴t2自己已執(zhí)行完畢,屬于等待通知方式(wait&notify、condition await&signal)
2、t2不斷去檢測t1是否完畢,屬于輪詢方式

假設(shè)t1是子線程,t2是主線程,那么t1采用何種方式通知t2呢?采用第一種方式:等待-通知,通過Handler-Message-Looper實現(xiàn)等待-通知。

Handler發(fā)送Message

經(jīng)典例子

    Button button;
    TextView tv;

    private Handler handler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            tv.setText(msg.arg1 + "");
            return false;
        }
    });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.start_thread_one);
        tv = findViewById(R.id.tv);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(()->{
                    Message message = Message.obtain();
                    message.arg1 = 2;
                    handler.sendMessage(message);
                }).start();
            }
        });
    }

主線程里構(gòu)造handler,子線程里構(gòu)造message,并使用handler將message發(fā)送出去,在主線程里執(zhí)行handleMessage方法就可以更新UI了。很明顯,使用handler來切換線程我們只需要兩步:

1、主線程里構(gòu)造handler,重寫回調(diào)方法
2、子線程里發(fā)送message告知主線程執(zhí)行回調(diào)方法

看到此,你可能疑惑的是:主線程怎么就知道執(zhí)行handleMessage方法?既然子線程發(fā)送了message,那么主線程應(yīng)該有個地方接收到該message啊?看來信使是message,接下來進入源碼一探究竟。Android Studio關(guān)聯(lián)源碼請移步:Android Studio關(guān)聯(lián)Android SDK源碼(Windows&Mac)

Message分析

既然message充當(dāng)信使,那么其應(yīng)當(dāng)有必要的字段來存儲攜帶的數(shù)據(jù)、區(qū)分收發(fā)雙方等,實際上message不只有這些字段。上個例子里,我們通過靜態(tài)方法獲取message實例。

    private static Message sPool;
    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();
    }

sPool是靜態(tài)變量,從名字可以猜測出是message存儲池。obtain()作用是:先從存儲池里找到可用的message,如果沒有則創(chuàng)建新的message并返回,那什么時候?qū)essage放到存儲池里呢?我們只需要關(guān)注sPoolSize增長的地方即可,搜索找到:

    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

message里有個message類型的next字段,熟悉鏈表的同學(xué)應(yīng)該知道這是鏈表的基本操作,該字段用于指向鏈表里下一個節(jié)點的。現(xiàn)在要將一個message對象放入message存儲池里:

將待存儲的message next字段指向存儲池的頭部,此時該頭部指向的是第一個元素,再將存儲池的頭指向待插入的message,最后將存儲池里個數(shù)+1。這時候message已經(jīng)放入存儲池里,并且當(dāng)作存儲池的頭。

我們再回過頭來看看怎么從存儲池里取出message:

聲明message引用,該引用指向存儲池頭部,將存儲池頭部指向鏈表下一個元素,并將message next置空,并將存儲池里個數(shù)-1,這時候message已經(jīng)從存儲池里取出。

可以看出,每次將message放入鏈表頭,取的時候從鏈表頭取,這不就是我們熟悉的棧結(jié)構(gòu)嗎。也就是說,message對象存儲池是通過棧來實現(xiàn)的。

問題1

至此,我們分析了message存儲池的結(jié)構(gòu),還留了兩個問題后面分析:

1、我們通過obtain()獲取message,那么啥時候調(diào)用recycleUnchecked()放入存儲池呢?
2、可以直接構(gòu)造message嗎?

MessageQueue

從handler.sendMessage(message)分析:

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

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

sendMessageAtTime()方法輸入兩個參數(shù),一個是待發(fā)送的message對象,另一個是該message延遲執(zhí)行的時間。這里引入了一個新的類型:MessageQueue,其聲明了一個字段:mQueue。接著調(diào)用enqueueMessage,該方法里msg.target = this,target實際上指向了handler。最后調(diào)用queue.enqueueMessage(msg, uptimeMillis),我們來看看queue.enqueueMessage方法:

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                //第一點
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            //第三點
            if (needWake) {
                nativeWake(mPtr);
            }
        }

MessageQueue顧名思義:Message隊列。它是怎樣實現(xiàn)隊列的呢?里有個Message類型的mMessages字段,看到這是不是想到了前面的message存儲池(棧)呢?沒錯,MessageQueue是通過message鏈接成鏈表的,只不過該鏈表是實現(xiàn)了隊列的,接下來我們看看怎么實現(xiàn)隊列。
enqueueMessage源碼里標記了第一點、第二點、第三點三個關(guān)鍵點。

第一點

隊列為空、或者延遲時間為0、或者延遲時間小于隊列頭節(jié)點,那么將該message next字段指向隊列頭,并將隊列頭指向該message。此時該message已經(jīng)插入enqueueMessage的隊列頭。

第二點

如果第一點不符合,那么message不能插入隊列頭,于是在隊列里繼續(xù)尋找,直至找到延遲時間大于message的節(jié)點或者到達隊列尾,再將message插入隊列。

從第一點和第二點可以看出,enqueueMessage實現(xiàn)了隊列,并且是根據(jù)message延遲時間從小到大排序的,也就是說隊列頭的延遲時間是最小的,最先需要被執(zhí)行的。

第三點

問題2

喚醒MessageQueue隊列,后續(xù)分析。

什么時候取出message

至此,handler.sendMessage(message)發(fā)送message到MessageQueue流程已經(jīng)分析完畢,那么MessageQueue里的message什么時候取出來呢?
既然MessageQueue里有入隊方法enqueueMessage,試著找找是否存在出隊方法,很遺憾沒有。那么我們有理由相信可能在某個地方操作了MessageQueue mMessages字段,查找后發(fā)現(xiàn)引用的地方蠻多的,不好分析。換個思路,想想MessageQueue對象mQueue在哪里被引用了?


image.png

定位到Handler構(gòu)造函數(shù)

    public Handler(@Nullable 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());
            }
        }

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

我們發(fā)現(xiàn)mQueue指向了mLooper里的mQueue,而mLooper = Looper.myLooper()。現(xiàn)在我們將懷疑的目光瞥向了Looper,Looper是何方神圣呢,接下來繼續(xù)分析。

Looper分析

從Looper.myLooper()開始分析

##Looper.java
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
   /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

##ThreadLocal.java
   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();
    }

可以看出,Looper對象存儲在當(dāng)前線程里,我們暫時不管ThreadLocal原理,看到get方法,我們有理由相信有set方法,果不其然。

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

查找其引用,該方法引用的地方很多。既然ThreadLocal get方法在Looper.java里調(diào)用,那么ThreadLocal set方法是否也在Looper.java里調(diào)用呢?


image.png
Looper.java
    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));
    }

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

查找prepareMainLooper()方法引用


image.png

看到了我們熟悉的ActivityThread,也就是我們app啟動main方法所在的類。


image.png

已經(jīng)追溯到源頭了,我們現(xiàn)在從源頭再回溯。

    private static Looper sMainLooper;  // guarded by Looper.class
    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));
    }

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

在prepare方法里,構(gòu)造了Looper對象。此刻我們看到了主角mQueue被構(gòu)造出來了,也就是說主線程啟動的時候就已經(jīng)構(gòu)造了Looper對象,并存儲在靜態(tài)變量sThreadLocal里。同時靜態(tài)變量sMainLooper指向了新構(gòu)建的Looper對象。
接著查找mQueue的引用,發(fā)現(xiàn)


image.png

final MessageQueue queue = me.mQueue,此處引用了mQueue,接著調(diào)用了MessageQueue里的next()方法,該方法返回message對象。

MessageQueue.java
next()方法
                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) {
                        // 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 {
                        // 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.
                    nextPollTimeoutMillis = -1;
                }

Message msg = mMessages,我們之前分析了handler發(fā)送message后最終是將message掛在mMessages上,也就是我們的message隊列。而next方法后續(xù)操作是:從隊列頭取出message,并將該message從隊列里移除。此時我們已經(jīng)找到message隊列入隊時機與方式、message隊列出隊方式。那么message隊列出隊時機呢?也就是啥時候調(diào)用的。繼續(xù)回溯分析上面提到的Looper loop()方法。


image.png

loop()方法簡化一下

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

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
            msg.recycleUnchecked();
        }

明顯的看出,loop()方法里有個死循環(huán),循環(huán)退出的時機是隊列里沒有消息或者其它異常。

msg.target.dispatchMessage(msg);msg.target就是我們之前提到的handler,也就是handler.dispatchMessage(msg)

    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);(1)
        } else {
            if (mCallback != null) {
                (2)
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            (3)
            handleMessage(msg);
        }
    }

這方法里就是具體執(zhí)行收到message的方法,我們發(fā)現(xiàn)里邊還分了三個方法:

1、handleCallback()調(diào)用的是message里Runnable類型的callback字段,我們經(jīng)常用的方法view.post(runnable)來獲取view的寬高,此處runnable會賦值給callback字段。
2、handler mCallback字段,Callback是接口,接口里有handleMessage()方法,該方法的返回值決定是否繼續(xù)分發(fā)此message。mCallback可選擇是否實現(xiàn),目的是如果不想重寫handler handleMessage()方法,那么可以重寫callback方法。
3、handleMessage,構(gòu)造handler子類的時候需要重寫該方法。

可能上述1、2、3點應(yīng)用場合還造成一些疑惑,我們用例子來說明。
1、handleCallback()

                    //延時執(zhí)行某任務(wù)
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            
                        }
                    }, 100);
                    
                    //view 獲取寬高信息
                    tv.post(new Runnable() {
                        @Override
                        public void run() {
                            
                        }
                    });

此種方式,只是為了在某個時刻執(zhí)行回調(diào)方法,message本身無需攜帶附加信息
2、mCallback.handleMessage(msg)
構(gòu)造Handler匿名內(nèi)部類實例

    private Handler handler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            tv.setText(msg.arg1 + "");
            return false;
        }
    });

3、handleMessage(msg)
內(nèi)部類繼承Handler,重寫handleMessage方法

    class MyHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            tv.setText(msg.arg1 + "");
        }
    }
    
    MyHandler handler = new MyHandler();

此時,我們已經(jīng)知道MessageQueue里的message執(zhí)行時機。你可能還有疑惑,怎么就確保上述的runnable和handleMessage在主線程里執(zhí)行呢?根據(jù)調(diào)用棧:
loop()->msg.target.dispatch()->runnable/handleMessage(),而loop是在main方法里調(diào)用的,該方法是app入口,是主線程。
我們還注意到loop()里有兩條打印語句:

            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

可以看到位于dispatchMessage()前后,這給我們提供了一個便利:通過監(jiān)測語句的輸出,計算主線程執(zhí)行message耗時。

loop()里的msg.recycleUnchecked(),現(xiàn)在來回答問題1

1、message通過obtain()從存儲池里獲取實例,在loop()里message被使用后,調(diào)用msg.recycleUnchecked()放入存儲池里(棧)。
2、message對象不僅可以通過obtain()獲取,也可以直接構(gòu)造。最終都會放入存儲池里,推薦用obtain(),更好地復(fù)用message。

MessageQueue next()分析

前面我們簡單過了一遍next()方法,該方法是從MessageQueue里取出message對象。loop()通過next調(diào)用獲取message,如果message為空則loop()退出,假設(shè)這種情況出現(xiàn),那么主線程就退出了,app就沒法玩了。因此從設(shè)計上來說,next返回的message不能為空,那么它需要一直檢測message直到獲取到有效的message,所以next就有可能阻塞。實際上,next使用的一個for循環(huán),確實可能會阻塞,我們來看看細節(jié)之處:

        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            //第一點
            nativePollOnce(ptr, nextPollTimeoutMillis);

            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) {
                        //第三點
                        // 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 {
                        //第四點
                        // 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.
                    //第五點
                    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;
                }
            }
        }

源碼里標記了6個關(guān)鍵點,一一分析一下。

1、nativePollOnce(ptr, nextPollTimeoutMillis),查找native層MessageQueue是否有數(shù)據(jù)。nextPollTimeoutMillis=0 該方法立即返回;nextPollTimeoutMillis=-1;該方法一直阻塞直到被主動喚醒;nextPollTimeoutMillis>0;表示阻塞到特定時間后返回。現(xiàn)在我們來解答之前的問題2,當(dāng)有message入隊列的時候,調(diào)用如下代碼:

            if (needWake) {
                nativeWake(mPtr);
            }

而needWake=mBlocked,當(dāng)阻塞的時候mBlocked=true,執(zhí)行nativeWake(mPtr)后,nativePollOnce()被喚醒返回。這就是最初我們?yōu)槭裁凑fhandler-looper 采用等待通知方式。

2、如果隊頭節(jié)點是屏障消息,那么遍歷隊列直到找到一條異步消息,否則繼續(xù)循環(huán)直到nativePollOnce阻塞。此處的應(yīng)用場景之一是:在對view進行invalidate時候,為了保證UI繪制能夠及時,先插入一條屏障消息,屏障消息delayTime=0,因此插入隊列頭部(注意和普通消息插入不同的是:如果隊列某個節(jié)點和待插入的延遲時間一致,那么待插入的節(jié)點排在已有節(jié)點的后面,而屏障消息始終插在前面)。插入屏障消息后,后續(xù)的刷新消息設(shè)置為異步,再插入隊列,那么該條消息會被優(yōu)先執(zhí)行。
3、如果還未到執(zhí)行時間,則設(shè)置阻塞超時時間。
4、取出message節(jié)點,并調(diào)整隊列頭。
5、隊列里沒有message,那么設(shè)置阻塞
6、設(shè)置阻塞標記位mBlocked=true,并繼續(xù)循環(huán)

Handler線程切換本質(zhì)

上面經(jīng)handler發(fā)送message,到message被消費整個流程已經(jīng)剖析完畢。為了更直觀了解流程,使用流程圖表示。

message對象存儲池

入棧


image.png

出棧


image.png

MessageQueue隊列

出入隊


image.png

調(diào)用棧

發(fā)送消息調(diào)用棧

handler.sendMessage(message)
sendMessage->sendMessageDelayed(msg, 0)->sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)->enqueueMessage(queue, msg, uptimeMillis)->queue.enqueueMessage(msg, uptimeMillis)

取消息調(diào)用棧

msg = loop()->queue.next()
msg->dispatchMessage(msg)->handleCallback(msg)/handleMessage(msg)

將Handler、MessageQueue、Looper用圖表示如下:


image.png

由于MessageQueue是線程間共享對象,因此對隊列的操作(入隊、出隊)需要加鎖。

Handler和Looper聯(lián)系

怎么確保handleMessage(msg)在主線程執(zhí)行呢?換句話說收到消息后是怎么切換到主線程的?

1、從取消息調(diào)用棧可知,執(zhí)行l(wèi)oop()的線程就是執(zhí)行handleMessage(msg)的線程,而loop()在main()方法里執(zhí)行,因此這時候取出的消息就在主線程執(zhí)行
2、Handler發(fā)送的message存儲在Handler里的messagequeue,而此隊列指向的是Looper的messagequeue,loop()方法從Looper的messagequeue里取消息。因此Handler和Looper共享同一個queue,handler負責(zé)發(fā)送(入隊),Looper負責(zé)收取(出隊),通過中間橋梁messagequeue實現(xiàn)了線程切換。
3、接著第二點,Handler和Looper是如何共享queue呢?在構(gòu)建Handler的時候,會根據(jù)當(dāng)前線程獲取存儲的looper并獲取其messagequeue,而looper對象需要在同一線程里構(gòu)造,并將該looper引用設(shè)置到當(dāng)前線程。在app啟動的時候,系統(tǒng)已經(jīng)在主線程的looper并賦值給靜態(tài)變量sMainLooper,我們可以通過getMainLooper()獲取主線程的looper對象。
4、handler構(gòu)造時,可以選擇是否傳入looper對象,若不傳則默認從當(dāng)前線程獲取,若取不到則會拋出異常。這就是為什么在子線程里不能簡單用消息循環(huán)的原因,因為handler沒有和looper建立起關(guān)聯(lián)。

子線程消息循環(huán)

以上,我們分析的是子線程發(fā)送消息,主線程在輪詢執(zhí)行。那我們能夠在主線程(其它線程)發(fā)送消息,子線程輪詢執(zhí)行嗎?這種場景是有現(xiàn)實需求的:

A線程不斷從網(wǎng)絡(luò)拉取數(shù)據(jù),而數(shù)據(jù)分為好幾種類型,拿到數(shù)據(jù)后需要分類處理,處理可能比較耗時。為了不影響A線程效率,需要另起B(yǎng)線程處理,B線程一直在循環(huán)處理A拉取回來的數(shù)據(jù)。

是不是似曾相識,我們想要B線程達到類似主線程的looper功能,接下來看看該怎么做。

            Handler handlerB = null;
            Thread b = new Thread(new Runnable() {
                @Override
                public void run() {
                    //子線程里創(chuàng)建looper對象,并保存引用到當(dāng)前線程里
                    Looper.prepare();
                    
                    //Handler構(gòu)造方法默認獲取當(dāng)前線程的looper引用
                    handlerB = new Handler(new Callback() {
                        @Override
                        public boolean handleMessage(@NonNull Message msg) {
                            return false;
                        }
                    });
                    //子線程開啟loop()循環(huán)
                    Looper.loop();
                }
            });
            b.start();
            
            Thread a = new Thread(()->{
                //線程發(fā)送消息
                handlerB.sendMessage(new Message());
            });
            a.start();

源碼里已有注釋,不再綴敘。
你可能會說,每次都這么麻煩嗎?實際上Android系統(tǒng)開發(fā)者已經(jīng)考慮了這情況,封裝了HandlerThread類來實現(xiàn)子線程的消息循環(huán),實現(xiàn)方式和上述類似。重點是將Handler和Looper關(guān)聯(lián)起來。

總結(jié)

以上從源碼和應(yīng)用角度剖析了Handler、Message、Looper原理及其三者之間關(guān)系,三者簡稱消息循環(huán)。消息循環(huán)在Android開發(fā)中應(yīng)用廣泛,我們不僅要能夠運用自如,也可以從源碼中學(xué)習(xí)閃光的設(shè)計點。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容