Android – Handler原理

Handler的主要作用是收發消息和切線程

功能一:收發消息

簡單流程介紹

希望你看完這篇文章後也可以把流程自己講出來,並且每個環節還可以講出很多細節

他的消息機制離不開Looper、MessageQueue

  • 其中 Looper 每個線程只能持有一個,主要負責循環查看 MessageQueue 裏面是否有 msg 需要處理,並將需要處理的消息取出,交給 Handler
  • MessageQueue 是負責存放消息的,數據結構是一個單鏈表,這樣就可以方便地插入或刪除 msg

具體流程一般是:

  1. Handler 發送一條msg => 本質是向MessageQueue里插入一條msg,插入時候的依據是msg.when => SystemClock.uptimeMillis() + delayMillis

  2. 這條msgMessageQueue.next()返回並交給Handler去處理
    next()會在有同步屏障(msg.target==null)的時候遍歷查找並返回最早的異步消息,並在移除屏障後,從頭取出並返回消息

  3. Handler.dispatchMessage(msg)會優先處理msg.callback,如果msg.callback為空,就處理Handler.mCallback,然後處理是msg本身
    msg.callback是在調用Handler.post(Runnable)時,裏面的Runnable(runOnUIThreadview.post(Runnable)也用的是Handler.post(Runnable)Runnable是一樣的)

    這是在不新增Handler的情況下,另一種調用Handler的方式(如下)

class MyHandlerCallBack: Handler.Callback {
  override fun handleMessage(msg: Message?): Boolean {
    TODO("Not yet implemented")
  }
}

可以看到他也有handleMessage這個方法

Looper是個死循環

(1)死循環的目的

目的就是讓主線程一直卡在這個死循環裏面

因為Looper的作用就是在這個死循環裏面取出消息,然後交給Handler處理

Android的生命周期,你了解的onCreate,onStop,onStart…… 等等都是由Handler來處理的,都是在這個死循環裏面運行的

所以什麼Looper死循環卡死主線程怎麼辦???

必須給我卡住!!!不卡住的話,消息就沒法整了!!!

看下Android啟動的時候的源碼
Activitythread.java >> main()

public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        ...
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        ...
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

想想寫java的時候,main最後一行執行完了,不就徹底玩完了嘛!!!

(2)死循環里幹了啥

其實想都不用想,一直在看MessageQueue裏面有沒有消息唄,太簡單了!調用的就是MessageQueue.next()

看下源碼 MessageQueue.java >> loop()

       for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ... 
            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);
                }
            }
            ...
        }

很簡單,next()返回Messagemsg.target.dispatchMessage() 處理Message

但是隊列里沒消息就會返回null,這是錯誤的!!!具體往下看

MessageQueue是個單鏈表

1.插隊

Handler發消息的時候,目的就是對msg經過一系列操作,最終也只是調用enqueueMessage插入隊列而已

看下源碼 Handler>>enqueueMessage()

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

return直接調用Message的插入隊列方法

2.出隊

出隊就是next()方法,之前已經見過了

(1)時間順序

Message是按時間排序的,也就是msg.when => SystemClock.uptimeMillis() + delayMillis

msg.whenMessage期望被處理的時間

SystemClock.uptimeMillis()是開機到現在的時間,delayMills是延遲時間,這個在sendMessageDelayed方法里直接可以直接傳參

next()就是按照時間順序處理MessageQueue裏面的消息的

但是next()里有個概念叫 同步屏障

(2)同步屏障

同步屏障,就是說,平時MessageQueue都是處理同步消息,也就是按順序來,一個個出隊

同步屏障就是阻擋同步消息的意思

就是msg.target == null 的時候,MessageQueue就會去找msg.isAsynchronous()返回truemsg

isAsynchronous,沒錯 ! 這是異步消息,就是優先級很高,需要立刻執行的消息,比如:更新View

(3)阻塞

值得注意的是,講Looper的時候,源碼next()後面官方給我們注釋了 // might block可能阻塞,也就是說可能這個next()也許會執行好久

next()會阻塞?,什麼時候阻塞?

now < msg.when也就是時間還沒到,期望時間大於現在的時間

(4)退出

另外看第一行,只有ptr == 0,才會返回null

所以上面才說next()不會因為沒消息而返回null,原來返回null的時候在這呢!

看下源碼,MessageQueue.java >> next()

 @UnsupportedAppUsage
    Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                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 {
                    ...
                        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;
                }
                ...
            }
            ...
        }
    }

代碼簡略了還是有點多,別著急,慢慢看

pre什麼時候就是0了呢?

答:quit()了之後

看下源碼,Looper.java

   public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }

可以看到只是一個傳參不同而已,下面看看這個參數是幹嘛的

看下源碼,MessageQueue.java >> quit()

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

可以看到,safe == true,就移除未來的Message
safe == false,就移除所有的Message

mQuiting變成了true,記住他我們一會兒會用到

而改變ptr的地方在這裡

next()裏面

裏面有個dispose,找不到可以ctrl+F找一下

這裡只有在mQuiting == true的時候,才會調用

這就是改mPtr的地方,然後下次next()的時候就會返回null

Handler流程

(1)post過來的msg

我們已經知道了在Looper的死循環裏面,會將next()返回的msg交給Handler,調用dispatchMessage()

dispatchMessage()裏面會先判斷msg是不是被post過來的,因為post要執行的邏輯在msg.callback裏面,callback是一個Runnable,這可能不是很好理解

你可以想想runOnUIThread(Runnable),這裡的Runnable就是上面的callback
他們都是調用了Handler.post(Runnable)

至於為啥起個名叫callback,我也納悶兒

(2)send過來的msg

這些msg是會的邏輯是你重寫的handleMessage那裡的邏輯

如果實現了Handler.Callback這個Interface,就會處理mCallbackhandleMessage
而不是Handler自己的handleMessage

這是一個優先級策略,沒什麼好奇怪的

我們看下源碼 => Handler.java >> dispatchMessage()

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

這就是Handler的消息機制了

接下來我們講講Handler的另一個功能,切線程

功能二:切線程

Handler切線程使用的是ThreadLocal

(1)ThreadLocal

ThreadLocal是線程裏面的一個數據儲存類,用法類似mapkey就是thread

但是他沒有提供,根據key來找ThreadLocalValues的方法,所以暴露的api就只能讓你去get當前線程的ThreadLocalValues對象而已,就是key——你自己沒法作為參數傳進去,只能是currentThread

如果你沒用過ThreadLocal,我給你舉個例子

fun main() {
    val booleanThreadLocal = ThreadLocal<Boolean>()
    booleanThreadLocal.set(true)

    println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}")

    thread(name = "thread#001") {
        booleanThreadLocal.set(false)
        println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}")
    }

    thread(name = "thread#002") {
        println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}")
    }
}

結果是這樣的:你可以自己運行看看

in Thread[main] booleanThreadLocal value = true
in Thread[thread#001] booleanThreadLocal value = false
in Thread[thread#002] booleanThreadLocal value = null
(2)切線程的細節

話說回來,Handler怎麼通過ThreadLocal切線程的呢?

答案是:Looper是放在ThreadLocal里的

回顧片頭的流程,Handler將消息插入MessageQueue,然後Looper取出來,再還給Handler,這種設計不止是為了讓msg可以按順序處理,還可以讓外部接口只有Handler

最關鍵的是,LooperHandler的觸發關係只有Looper觸發HandlerHandler不會觸發Looper

因此Handler把消息放在MessageQueue之後,就在等着Looper來給自己派發任務(msg

舉個例子:

線程A調用主線程Handler發一個消息

Handler將這個消息插入MessageQueue,此時其實還在線程A

只有Loopernext()調用msg.target.dispatchMessage()時,就變成了主線程

僅僅是因為Looper主線程 而已

OVER