Handler後傳篇二: 該如何理解Handler的"異步"?

提到異步, 你們首先想到的必定是多線程, 多線程當中的併發, 指的是經過CPU調度計算, 讓用戶看上去是同時執行的, 實際上從CPU操做層面上並非真正的同時。CPU經過調度計算, 在同一時間, 只會有一條線程在執行任務, 只不過調度計算速度很快, 隨機選擇執行的線程, 因此咱們看起來像是同時在執行, 下面咱們來舉個例子: android

private static class ThreadOne extends Thread {

    private static final String ThreadName = "ThreadOne";

    @Override
    public void run() {
        super.run();

        for (int i = 0; i < 100; i++) {
            Log.i(TAG, "run: " + ThreadName + "執行了任務" + i);
        }
    }
}複製代碼
private static class ThreadTwo extends Thread {

    private static final String ThreadName = "ThreadTwo";

    @Override
    public void run() {
        super.run();

        for (int i = 0; i < 100; i++) {
            Log.i(TAG, "run: " + ThreadName + "執行了任務" + i);
        }
    }
}複製代碼
private void threadTest() {

    final ThreadOne threadOne = new ThreadOne();
    final ThreadTwo threadTwo = new ThreadTwo();

    threadOne.start();
    threadTwo.start();
}複製代碼

例子很簡單, 建立兩個線程, 調用 start()方法啓動線程bash

上面的例子運行結果以下: (部分結果截圖)多線程



不一樣的手機運行結果可能不一樣, 可是咱們仍是可以得出結論: 併發

多線程的異步, 是多條路徑都在執行, 互不干擾, 因此他們執行的順序是未知的, 這得看CPU的調度計算,正常狀況下, 你沒法控制哪條線程先執行哪條線程後執行, 或者哪條線程先結束哪條線程後結束。app

平時咱們提到 Handler, 咱們有的時候也會說是異步分發, 可是這裏的"異步"跟多線程的異步是同樣的嗎?? 咱們看一下下面的小例子: 異步

private void test() {

    Log.i(TAG, "onCreate: 事件分發前");
    sHandler.sendEmptyMessage(0);
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Log.i(TAG, "test: 事件分發後");
}複製代碼
private static Handler sHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.i(TAG, "handleMessage: 事件處理回調");
    }
};複製代碼

咱們在onCreate()方法中執行了 test() 方法, 經過 hander 分發事件之後, 讓主線程 slee 2秒, ide

運行上面的程序之後, 打印結果以下: post



發現問題了嗎?? 多線程的狀況下, 每一條線程中的代碼是交替執行的, 可是上面的這個Handler 例子, 並無出現交替執行的狀況, 而是等到了咱們 test()方法執行完成了之後, 纔去執行了 Handler 的處理回調, 這看上去並非一個異步操做, 而是一個同步操做, 只不過Handler 回調的代碼放到了最後執行, 這是爲何呢? 下面咱們來分析一下: 學習

首先, 咱們得了解 Handler 的運行原理, 咱們的主線程會對應配備一個消息隊列MessageQueue, 而且只有一個消息隊列, Handler 是消息的發送接收者和處理這, Message是消息的載體, 大體的一個運行狀況以下: ui



這裏面, 消息的發送和處理並非連續發生的, 並非我發送了消息之後當即就去執行處理消息, 而是現將消息發送到線程中對應的消息隊列的尾部, 當消息隊列中前面的消息執行完了之後, 纔會輪到他來執行.

再者, 在android中, 全部的事件的運行都是基於事件驅動的, activity 中的聲明週期也不例外, 在 app 程序的入口類 ThreadActivity 中定義了一個內部類 H, 這個H就是一個Handler, activity 的聲明週期也是經過 handler 進行分發的, 感興趣的能夠去看看源碼, 這裏不是咱們的重點, 

這裏, 我經過幾張圖來分析一下咱們上面的Handler 例子:

如今假設咱們的 test() 方法是消息A, 在 test()方法中經過Handler 發送的消息是消息B, 爲了更直觀一點, 咱們假設消息A是帶血槽的(原諒我中了王者的毒):


前面咱們說過, onCreate()方法也是經過Handler 分發出來的, 也就是程序經過Handler 將咱們的消息發送到主線程的消息隊列 MessageQueue 中, test()方法在 onCreate 中執行的, 咱們只看test()方法, 也就是消息A在將要被執行是的狀態是這樣的: 


這時消息A開始執行, 當第一個血槽執完之後, 變成下面這樣: 


這時候, 血槽二開始執行, 在這是經過 handler 分發事件B, 也就是 test()方法中的sHandler.sendEmptyMessage(0);

如今MessageQueue 中的狀態有變成了下面這樣: 


由於咱們的Handler 是在主線程中建立的,也就是說Handler 跟主線程綁定的, 因此經過Handler 發送出來的消息, 會先存到消息隊列MessageQueue 中, 這是消息A沒有執行完, 等到消息A所有執行完了, 咱們的消息B才能獲得執行, 這也是爲何咱們上面的那個例子打印順序的緣由.

經過上面的分析, 咱們能夠知道, Handler 中所謂的"異步", 其實並非真正的異步, 只不過是消息的發送和處理不止前後執行的, 並且咱們的程序也不用阻塞着等到 handle 回調執行完成之後再繼續執行, 而是繼續向下執行, 等當前消息執行完成了, 再從消息隊列中取出下一個消息執行.最終的執行也並非異步的,而是同步的, 只不過消息B放到了事件A的後面, 當時間A執行完了才能輪到事件B執行.

並且, 在這裏咱們須要注意一個問題, Handler 最終的回調也是在主線程的, 咱們不能再他的回調方法中執行耗時操做, 不僅僅是 handler.sendMessage()方法, 咱們調用 handler.post()方法的時候他也是這樣的.

說到這裏, 讓我想起來以前在學習 view 的繪製過程當中遇到的一個問題.

咱們知道, view 的測量繪製操做是在 ViewRootImpl 這個類裏面的, 這個類是在Activity 的onResume()方法後才建立的, 因此咱們在 onCreat()方法中去獲取 view 的寬高, 獲得的都是0. 若是想在 onCreate()方法中獲取寬高, 其中的一個方法就是經過 view.post()方法去獲取,咱們能夠看下這個方法的源碼: 

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().post(action);
    return true;
}複製代碼

AttachInfo 是View 的一個內部類, 在 onCreate()方法中 view 尚未被建立, 因此這裏是AttachInfo 是 null 的, 這是他會調用下面這個方法:

getRunQueue().post(action);
複製代碼

在調用這個方法以後, 他會把傳入的Runnable 對象放入到運行隊列 RunQueue 中, 這裏只是放入運行隊列, 而並無去執行, 真正的執行是在哪裏呢?在ViewRootImpl的 performTraversals()這個方法裏面, 這裏爲了便於分析, 我簡化一下這個方法: 

performTraversals() {
    // 省略.....
    getRunQueue().executeActions(mAttachInfo.mHandler);
    
    performMeasure();
    
    performLayout();

    performDraw();

}複製代碼

這裏面會真正的去執行運行隊列中的消息, 可是問題來了, 咱們在這個方法裏面, 是先調用的運行隊列, 再調用的 performMeasure()方法, 按道理來講, 咱們的 view.post() 裏面也拿不到 view 的寬高, 可是咱們是能夠拿到的, 其實這個緣由就是咱們上面分析的那樣, 當執行performTraversals()這個方法時,它是阻塞在消息隊列頭部的, 而後調用 getRunnQueue().executeAction()方法會把運行隊列裏面的消息發送到消息隊列裏面, 可是它是排在 performTraversals()這個方法以後的, 只有當performTraversals()方法執行完了, 才能輪到運行隊列裏面的消息執行.

相關文章
相關標籤/搜索