Android 復(fù)盤——幫你徹底了解消息機制

# 1. 什么是消息機制

說到消息機制谆棱,作為一名 Android 開發(fā)者一定先想到的是 Handler。Handler 就是 Android 消息機制的上層接口逢防,我們可用通過 Handler 輕松的在不同的線程中切換任務(wù)龄广,但 Handler 的實現(xiàn)還有兩個很重要的概念 MessageQueueLooper

MessageQueue 的翻譯是消息隊列宰掉,它的內(nèi)部采用了單鏈表的結(jié)構(gòu)存儲 Handler 對象發(fā)送的消息呵哨。

Looper 的作用是不斷地查詢 MessageQueue 中是否有消息,如果 Looper 發(fā)現(xiàn) MessageQueue 中存入了新的消息轨奄,它就會去處理這條消息孟害,如果沒有新消息,Looper 就會以無限循環(huán)的方式去查詢 MessageQueue 中是否有新消息戚绕。

# 2. 為什么要有 Handler

## 2.1)官方文檔中 Handler 的主要作用

(1)安排將來某個時間點執(zhí)行的 MessageRunnables纹坐;
(2)在不同于當(dāng)前的線程上執(zhí)行的操作枝冀;

## 2.2)Handler 被用來做的最多的一件事就是更新主線程的 UI舞丛。

在 Android 開發(fā)中,默認(rèn)子線程是不可以更新 UI 的果漾,這一點可以從 View 的最高層級 ViewRootImpl 類中找到答案

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");
    }
}

ViewRootImpl 類中的 checkThread 方法會在更新 UI 前被執(zhí)行球切,如果當(dāng)前線程不是主線程,就會拋出 Only the original thread that created a view hierarchy can touch its views. 的異常

## 2.3)那么 Android 為什么要設(shè)計為只能在主線程中更新 UI 呢绒障?

  • Android 在子線程中更新 UI 是不安全的吨凑,如果多個子線程同時修改一個控件的數(shù)據(jù)世囊,后果是不可控的
  • 如果給 UI 更新機制加鎖逃沿,會降低 UI 的訪問效率,并且可能阻塞某些線程的執(zhí)行

# 3. Handler 的用法

## 3.1)在主線程中創(chuàng)建 Handler

通常呛讲,我們在主線程中創(chuàng)建 Handler 的寫法如下:

private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};

但這樣寫庐镐,系統(tǒng)會這樣提示:

This Handler class should be static or leaks might occur (anonymous android.os.Handler)
這個Handler類應(yīng)該是靜態(tài)的恩商,否則可能會發(fā)生泄漏

出現(xiàn)這個警告但原因是,Handler 在 Activity 中作為一個匿名內(nèi)部類來定義必逆,它的內(nèi)部持有來 Activity 的實例怠堪。當(dāng) Activity 被用戶關(guān)閉時,因為 Handler 持有了 Activity 的引用名眉,就造成了 Activity 無法被回收粟矿,從而導(dǎo)致了內(nèi)存泄漏。

因此损拢,在這里推薦一種更加安全的寫法:

private static class MyHandler extends Handler{
    private WeakReference<Activity> weakReference;
    public MyHandler(Activity activity){
        weakReference = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what){
                case 0:                     
                  Toast.makeText(weakReference.get(),Thread.currentThread().getName(),Toast.LENGTH_SHORT).show();
                  break;
            }
    }
}

private MyHandler handler = new MyHandler(this);

通過靜態(tài)內(nèi)部類的方式實現(xiàn)一個 Handler陌粹,此時內(nèi)部類并不持有外部類對象的應(yīng)用,需要在內(nèi)部類的構(gòu)造方法內(nèi)增加一個外部類(Activity)的弱應(yīng)用福压。這樣掏秩,即使 Activity 被關(guān)閉绘证,Activity 也能順利被回收。

onCreate() 中的代碼如下:

btn_0 = findViewById(R.id.btn_0);
btn_0.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Thread(){
            @Override
            public void run() {
                super.run();
                Message message = Message.obtain();
                message.what = 0;
                handler.sendMessage(message);
            }
        }.start();
    }
});

這時候點擊按鈕的運行效果如下:


運行效果

## 3.2)在子線程中創(chuàng)建 Handler

在官方文檔中 Handler 的主要作用是在不同于當(dāng)前線程的線程中執(zhí)行操作哗讥,那么如何用 Handler 解決兩個子線程之間的通信呢嚷那?

請看代碼:

btn_1 = findViewById(R.id.btn_1);
btn_1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Thread(){
            @Override
            public void run() {
                super.run();
                Looper.prepare();
                handler = new MyHandler(MainActivity.this);
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Looper.loop();
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                super.run();
                Message message = Message.obtain();
                message.what = 0;
                handler.sendMessage(message);
            }
        }.start();
     }
});

此時點擊按鈕:


運行效果

可見當(dāng)前的處理線程已經(jīng)變成了子線程。

# 4. Handler 工作原理

如果細(xì)心的觀察代碼杆煞,可以看到在子線程中創(chuàng)建 Handler 的時候調(diào)用了 Looper.prepare()Looper.loop() 兩個方法魏宽。這兩句代碼有什么用呢?

我們暫時可以把 Looper 理解為消息的管理者决乎,它負(fù)責(zé)從 MessageQueue 中提取出消息队询,傳遞給 Handler 進行處理,每一個 Handler 都必須要有一個 Looper构诚,在 Handler 創(chuàng)建的時候蚌斩,它會自動使用當(dāng)前線程的 Looper,而 Looper.prepare() 的作用就是為當(dāng)前線程準(zhǔn)備一個 Looper范嘱,Looper.loop() 的作用是開始查找當(dāng)前 MessageQueue 中是否有了新的消息送膳。

這就是 Handler 工作的第一步 :

## 4.1)采用當(dāng)前線程的 Looper 創(chuàng)建 Handler

因為這里主要講 Handler 的工作流程,創(chuàng)建 Looper 的具體過程放到文章的下面講解丑蛤。我們只要知道
Looper.prepare() 為當(dāng)前的線程創(chuàng)建了一個 Looper 對象即可叠聋。

但是,在主線程中創(chuàng)建 Handler 的時候受裹,我們并沒有看到 Looper.prepare() 的執(zhí)行碌补,這是因為在 UI 線程,即 ActivityThread 的創(chuàng)建過程中棉饶,Looper 已經(jīng)被創(chuàng)建好了厦章。

我們可以在 ActivityThread 的 main() 方法中看到這樣一句代碼:

Looper.prepareMainLooper();

這個方法內(nèi)部也調(diào)用了 Looper.prepare() 為 UI 線程創(chuàng)建了一個 Looper。

## 4.2)通過 Handler 的 sendMessageAtTime() 方法發(fā)送 Message

為什么是 sendMessageAtTime照藻?不是還有 sendMessage()袜啃,sendEmptyMessage()sendEmptyMessageDelayed()岩梳,sendEmptyMessageAtTime()囊骤,sendMessageDelayed() 這么多方法嗎?

通過閱讀這些方法的源碼可以發(fā)現(xiàn)冀值,這些方法最終調(diào)用的都是 sendMessageAtTime()也物。

其次還有 post()postAtTime()列疗,postDelayed() 方法最終調(diào)用的也都是 sendMessageAtTime() 方法滑蚯,只是多了一步調(diào)用 getPostMessage(Runnable r, Object token) 將 Runnable 封裝為一個 Message 對象的 callback 里。

public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}

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

那么 sendMessageAtTime() 里的具體操作是什么呢?我們?nèi)ピ创a里一探究竟

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    // 先獲取當(dāng)前 Handler 中的 MessageQueue告材,mQueue 在 Looper 的構(gòu)造方法中進行初始化坤次。
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    // queue 不為空,則執(zhí)行 Handler.java 里的另一個 enqueueMessage() 方法
    return enqueueMessage(queue, msg, uptimeMillis);
}

    
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    // 指定 msg 的 Target 對象為當(dāng)前的 Handler
    msg.target = this;
    if (mAsynchronous) {
       msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
在 enqueueMessage() 中斥赋,會最終調(diào)用 MessageQueue.java 中的 enqueueMessage() 方法缰猴。

在 sendMessageAtTime() 方法內(nèi)部 調(diào)用了 enqueueMessage() 方法,將 Message 對象傳遞到 MessageQueue 即消息隊列里中疤剑,在消息隊列里的具體處理邏輯在文章的 MessageQueue 工作原理 部分會具體解釋滑绒。

## 4.3)Looper 處理消息后調(diào)用 Handler 的 dispatchMessage() 方法

在第二步將消息插入消息隊列后,Looper 就開始遍歷消息隊列隘膘,找到新的消息疑故,再通知 Handler 去執(zhí)行這條消息,調(diào)用的就是 Handler 的 dispatchMessage() 方法弯菊。

public void dispatchMessage(Message msg) {
   // msg 的 callback 對象就是一個 Runnable
   if (msg.callback != null) {
        handleCallback(msg);
    } else {
        // 檢查 mCallback 是否為空纵势,不為空就執(zhí)行它內(nèi)部定義的 handleMessage() 方法
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 如果 mCallback 為空,就執(zhí)行在實例化 Handler 過程中我們自己定義的 handleMessage() 方法中的內(nèi)容
        handleMessage(msg);
    }
}

dispatchMessage() 方法首先會檢查 Message 的 Callback 對象是否為空管钳,callback 就是通過 post() 方法傳遞的 Runnable 對象钦铁,如果 callback 不為空,就去執(zhí)行 handleCallback() 方法蹋嵌。

handleCallback() 方法的實現(xiàn)也很簡單育瓜,它在內(nèi)部執(zhí)行了 Runnable 的 run() 方法

private static void handleCallback(Message message) {
    message.callback.run();
}

如果 callback 對象為空,就檢查 mCallback 是否為空栽烂,不為空就執(zhí)行它的定義的 handleMessage() 方法,若沒有 mCallback恋脚,最終將直接執(zhí)行我們在繼承 Handler 時自己定義的 handleMessage() 方法中的代碼腺办。

Callback 是 Handler 中定義的的一個接口,它的代碼如下:

/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 */
public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public boolean handleMessage(Message msg);
}

如果使用 Callback 接口的話糟描,我們可以直接實例化一個 Handler 而不用去實現(xiàn)一個 Handler 的子類怀喉,

private Handler mHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        return false;
    }
});

# 5. MessageQueue 工作原理

我們從上一部分的 MessageQueue.java 中的 enqueueMessage() 方法開始入手。

## 5.1)enqueueMessage()

代碼量有點多船响,要耐心看哦躬拢!

boolean enqueueMessage(Message msg, long when) {
    // 檢查當(dāng)前 msg 的 target 是否為空
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    // msg 如果正在被執(zhí)行,就拋出異常
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        // 在 quit() 方法中见间,mQuitting 會被設(shè)置為 true
        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;
        }

        // 標(biāo)記當(dāng)前的 msg 正在執(zhí)行
        msg.markInUse();
        // 設(shè)置 msg 的 when 為傳進來的 when 參數(shù)聊闯,when 是 Message 想要被執(zhí)行的時間
        msg.when = when;
        // 得到當(dāng)前消息隊列的頭部消息
        Message p = mMessages;
        boolean needWake;
        // 當(dāng)前消息隊列為空,新消息的觸發(fā)時間為 0米诉,或者新消息的觸發(fā)時間早于消息中第一條消息的觸發(fā)時間
        // 則將新消息插入到隊列的頭部菱蔬,作為當(dāng)前消息隊列的第一條消息
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            // 將當(dāng)前消息的下一條消息指向頭部消息
            msg.next = p;
            // 頭部消息修改為當(dāng)前消息
            mMessages = msg;
            // 當(dāng)阻塞時,需要喚醒
            needWake = mBlocked;
        } else {
            // 將新消息插入到當(dāng)前消息隊列當(dāng)中,(不是頭部)
            // 通常我們不必喚醒事件隊列拴泌,
            // 除非隊列頭部有消息障礙魏身,并且消息是隊列中最早的異步消息。
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            // 開始循環(huán)便利消息隊列蚪腐,比較新消息和隊列中消息的 when(觸發(fā)事件)的值箭昵,將新消息插入到適當(dāng)位置
            for (;;) {
                // 循環(huán)第一次遍歷時,將當(dāng)前隊列中的頭部消息賦值給 prev
                prev = p;
                // p 指向隊列中的第二個消息
                p = p.next;
                // 如果下一個消息為空回季,或者新消息的觸發(fā)時間早于下一個消息宙枷,找到了要插入的位置,退出循環(huán)
                if (p == null || when < p.when) {
                    break;
                }
                // needWake 為 true茧跋,并且 下一條消息是異步的慰丛,則不需要喚醒。
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            // 將新消息插入到 p 之前瘾杭,頭消息之后诅病。
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // 如果需要喚醒,調(diào)用 nativeWake 方法去喚醒
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

執(zhí)行完 enqueueMassage 方法粥烁,我們新發(fā)送的 Message 就成功的插入了消息隊列當(dāng)中贤笆。
但是除了插入新消息,我們還需要從消息隊列中讀取消息讨阻,這又要怎么做呢芥永?

## 5.2)next()

Message next() {
    // 如果消息循環(huán)已退出,并且被丟棄钝吮,則返回空埋涧。
    // 這個將在應(yīng)用重啟一個 looper 時發(fā)生
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    // 記錄空閑時處理的 IdlerHandler 數(shù)量,只在第一次迭代時為 -1
    // IdleHandler 只在隊列為空 或者 是頭部消息時執(zhí)行
    int pendingIdleHandlerCount = -1;
    //  native 層使用的變量奇瘦,設(shè)置的阻塞超時時長棘催,0 為不阻塞,-1 為阻塞
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);
  
        // 嘗試檢索下一條消息耳标。 如果找到則返回醇坝。
        synchronized (this) {
            // 獲取系統(tǒng)從開機到現(xiàn)在到時間
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            // 將隊列中到頭部消息賦值給 msg
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // msg 不為空,但是這個 msg 沒有 handler次坡,則這個 msg 為柵欄
                // 開始遍歷呼猪,指到獲取第一個異步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                // 如果當(dāng)前時間不到 msg 的觸發(fā)時間,則計算時間差砸琅,設(shè)置阻塞超時時長
                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 {
                    // 當(dāng)前時間到了 msg 的觸發(fā)時間宋距,則獲取消息并返回
                    mBlocked = false;
                    // 如果當(dāng)前的 msg 不是頭部消息,則上一條消息的 next 指向 msg 的 next
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        // 當(dāng)前 msg 為頭部消息明棍,則將下一個 msg 設(shè)置為頭部消息
                        mMessages = msg.next;
                    }
                    // msg 的下一個 Message 對象置空乡革,表示從消息隊列中取出來了這條 msg
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    // 標(biāo)記 msg 正在使用
                    msg.markInUse();
                    return msg;
                }
            } else {
                // 如果沒有消息,則設(shè)置阻塞時長為 -1,直到被喚醒
                nextPollTimeoutMillis = -1;
            }

            // 所有的消息都被處理后沸版,判斷是否退出嘁傀,并返回 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.
            // 第一次循環(huán)時视粮,消息隊列為空细办,或 當(dāng)前時間未到消息的觸發(fā)時間,獲取 IdleHandler 的數(shù)量
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
  
            // pendingIdleHandlerCount 的數(shù)量為 0 時蕾殴,線程會繼續(xù)堵塞
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            // 判斷當(dāng)前空閑時處理任務(wù)的handler是否是為空笑撞,如果為空,就實例化出新的對象
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // 運行 IdleHandler钓觉,只有第一次循環(huán)時才會運行
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            // 釋放 IdleHandler 的引用
            mPendingIdleHandlers[i] = null;

            boolean keep = false;
            try {
                // 執(zhí)行 IdleHandler 的方法
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // 重置 IdleHandler 的數(shù)量為 0茴肥,確保不會重復(fù)運行它們
        pendingIdleHandlerCount = 0;

        // 在執(zhí)行 IdleHandler 時,一個新的消息可能插入或消息隊列中的消息到了觸發(fā)時間
        // 所以將 nextPollTimeoutMillis 設(shè)為 0荡灾,表示不需要阻塞瓤狐,重新檢查消息隊列。
        nextPollTimeoutMillis = 0;
    }
}

至此批幌,MessageQueue 的兩個最重要的方法已經(jīng)分析完了础锐,下面來看 Looper 如何循環(huán)地從消息隊列中取出消息。

# 6. Looper 工作原理

在講 Looper 之前荧缘,需要先理解 ThreadLocal 的工作原理

## 6.1)ThreadLocal 的工作原理

ThreadLocal 是一個線程內(nèi)存儲數(shù)據(jù)的類皆警,當(dāng)不同的線程去訪問同一個 ThreadLocal 對象時,獲得的值都是不一樣的截粗,下面用一段代碼來證明

private ThreadLocal<String> mThreadLocal = new ThreadLocal<>();

btn_1 = findViewById(R.id.btn_1);
btn_1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Thread(){
            @Override
            public void run() {
                super.run();
                mThreadLocal.set("Thread_A");
                Log.d("ThreadLocalValue",mThreadLocal.get());
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                super.run();
                mThreadLocal.set("Thread_B");
                Log.d("ThreadLocalValue",mThreadLocal.get());
            }
        }.start();        
    }
);

我在兩個線程中分別存入在 mThreadLocal 中存入了不同的值信姓,然后在控制臺輸出它們的內(nèi)容

不同線程訪問 ThreadLocal 對象

可見不同線程訪問同一個 ThreadLocal 對象得到的值也是不一樣的。

ThreadLocal 實現(xiàn)這種特性的原因也很簡單桐愉,下面來看它內(nèi)部的 set 方法:

public void set(T value) {
    // 獲取當(dāng)前線程 t
    Thread t = Thread.currentThread();
    // 根據(jù)當(dāng)前線程 t财破,獲取當(dāng)前線程的 ThreadLocalMap 對象
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // map 不為空,調(diào)用 ThreadLocalMap 的 set() 方法从诲。
        map.set(this, value);
    else
        // map 為空,則為當(dāng)前線程創(chuàng)建一個新的 ThreadLocalMap 對象
        createMap(t, value);
}

在 set 方法中靡羡,先獲取當(dāng)前線程系洛,然后獲取當(dāng)前線程的 ThreadLocalMap 對象。getMap() 的 和 createMap() 的實現(xiàn)如下:

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

那么 ThreadLocalMap 又是什么呢略步,這里是它的一部分源碼:

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    // 初始的 table 容量
    private static final int INITIAL_CAPACITY = 16;
  
    // Entry 數(shù)組用于存儲數(shù)據(jù)
    private Entry[] table;

    // table 的大小
    private int size = 0;

    // 負(fù)載因子描扯,用于擴容
    private int threshold; // Default to 0

    // 設(shè)置負(fù)載因子為當(dāng)然容量大小的 2 / 3 
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }
  
    // 初始化 Entry 數(shù)組
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }
}

可以將 ThreadLocalMap 當(dāng)作一個哈希表,它的內(nèi)部用 Entry 存儲相應(yīng)的數(shù)據(jù)趟薄。

在 Thread 的屬性中有 ThreadLocal.ThreadLocalMap threadLocals = null;绽诚,所以每一個線程內(nèi)部,都持有一個 ThreadLocalMap 對象,系統(tǒng)才可以通過 getMap() 方法獲取當(dāng)前線程的 ThreadLocalMap 對象恩够。

在 ThreadLocal 中調(diào)用 set 方法卒落,實際上會調(diào)用 ThreadLocalMap 中的 set 方法,源碼如下:

// ThreadLocalMap 的 set 方法
private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    // 首先獲取當(dāng)前 ThreadLocal 對象的 table 屬性蜂桶,table 一個 Entry 的數(shù)組
    // Entry 相當(dāng)于一個 HashMap儡毕,存儲了當(dāng)前 ThreadLocal 對象和 Object 類型的 value 對象
    Entry[] tab = table;
    int len = tab.length;
    // 計算出存儲的位置
    int i = key.threadLocalHashCode & (len-1);

    // 遍歷 tab
    for (Entry e = tab[i];
        e != null;
        e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        // 如果 tab 中已經(jīng)存在了相同的 key 值,就覆蓋它原有的 value
        if (k == key) {
            e.value = value;
            return;
        }
        // 如果 當(dāng)前 entrt 的 key 為 null扑媚,調(diào)用 replaceStaleEntry 方法清楚所有 key 為 null 的數(shù)據(jù)
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
        // 都不滿足腰湾,就新建一個 Entry 對象
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // ThreadLocalMap 的容量到達閥值后擴容
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

ThreadLocal 中的 get() 方法和 set() 方法一樣,都是對 Thread 中對 ThreadLocalMap 進行操作

public T get() {
    // 獲取當(dāng)前線程
    Thread t = Thread.currentThread();
    // 獲取當(dāng)前線程的 ThreadLocalMap 對象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 獲取 ThreadLocalMap 中對應(yīng)當(dāng)前線程的 Entry 對象
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            // 將 Entry 對象中的 value 取出來
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

## 6.2)Looper 中的 prepare() 方法

那么 ThreadLocal 和 Looper 有什么關(guān)系呢疆股?我們知道每一個線程都有自己的 Looper费坊,Looper 的作用域就是當(dāng)前的線程,Android 系統(tǒng)中便通過 ThreadLocal 對象來存儲不同線程中的 Looper旬痹。

Looper 中 prepare() 方法為當(dāng)前線程創(chuàng)建一個 Looper 對象附井,我們看一下它的實現(xiàn):

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // 將 Looper 對象保存到當(dāng)前線程的 ThreadLocalMap 當(dāng)中
    sThreadLocal.set(new Looper(quitAllowed));
}

這里再看一下 Looper 的構(gòu)造方法

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

可以看到在一個 Looper 中創(chuàng)建了一個 MessageQueue,這里我們就可以搞清楚 Handler唱凯、Looper 和 MessageQueue 的對應(yīng)關(guān)系了:

每個線程都有一個 Looper 對象羡忘,在 Looper 對象的初始化過程中,會為當(dāng)前線程創(chuàng)建一個 MessageQueue磕昼,而一個線程中可以有多個 Handler卷雕。

## 6.3)Looper 中的 loop() 方法:

prepare() 調(diào)用后,就是調(diào)用 loop() 方法:

/**
  * Run the message queue in this thread. Be sure to call
  * {@link #quit()} to end the loop.
  */
public static void loop() {
    // 通過 Thread Local 獲取當(dāng)前線程的 Looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // 獲取當(dāng)前 Looper 對象的 MessageQueue
    final MessageQueue queue = me.mQueue;

    // 清空遠(yuǎn)程調(diào)用端進程的身份票从,確保此線程的身份是本地進程的身份漫雕,并跟蹤該身份令牌
    // 這里主要用于保證消息處理是發(fā)生在當(dāng)前 Looper 所在的線程
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        // 取出來下一條消息
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        
        // 用 logging 打印日志,默認(rèn)為 null峰鄙,可通過 setMessageLogging() 方法來指定
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        
        // 開始跟蹤浸间,并寫入跟蹤消息,用于 debug 功能
        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }        
        ...
        ...
        try {
            // // 通過 Handler 分發(fā)消息
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                // 停止跟蹤
                Trace.traceEnd(traceTag);
            }
        }
        
        if (logSlowDispatch) {
            showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
        }

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

        //  確保在分發(fā)消息的過程中線程的身份沒有改變
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }
        // 回收消息吟榴,并把消息放入消息池
        msg.recycleUnchecked();
    }
}

可以看到 loop() 方法就是不停的遍歷消息隊列中的消息魁蒜,當(dāng)發(fā)現(xiàn)有新的消息時,便調(diào)用 Handler 的 dispatchMessage() 方法吩翻。

## 6.4)getMainLooper()

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}
  /**
    * Returns the application's main looper, which lives in the main thread of the application.
    */
public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

getMainLooper() 方法用于返回當(dāng)前 UI 線程的 Looper兜看,UI 線程的 Looper 在 ActivityThread 的建立時通過調(diào)用
prepareMainLooper() 方法創(chuàng)建。

## 6.5)quit() 和 quitSafely()

在子線程中狭瞎,如果手動為其創(chuàng)建了Looper细移,那么在所有消息處理完成之后應(yīng)該調(diào)用 quit() 方法終止消息循環(huán),不然 Looper 就會一直處于等待狀態(tài)熊锭。

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

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

可以看到這兩個方法都調(diào)用了 MessageQueue 中都 quit(boolean safe) 方法弧轧,quitSafely 的參數(shù)為 true雪侥,quit 的參數(shù)為 false。

void quit(boolean safe) {
    // 主線程不退出消息循環(huán)
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    synchronized (this) {
        // 如果已經(jīng)退出了精绎,直接 return
        if (mQuitting) {
            return;
        }

        // 標(biāo)記為已經(jīng)退出
        mQuitting = true;
        // 如果 safe 的值為 true速缨,執(zhí)行完當(dāng)前的消息后退出消息循環(huán)
        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            // 直接退出消息循環(huán)
            removeAllMessagesLocked();
        }
        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

quitSafely() 會等待當(dāng)前消息執(zhí)行完畢后退出消息循環(huán),而 quit() 方法會直接退出消息循環(huán)捺典。

private void removeAllMessagesLocked() {
    // 獲取當(dāng)前 MessageQueue 的頭部消息
    Message p = mMessages;
    while (p != null) {
        // 循環(huán)遍歷所有的 Message
        Message n = p.next;
        // 回收消息鸟廓,并把消息放入消息池
        p.recycleUnchecked();
        p = n;
    }
    // 將頭部消息置為空
    mMessages = null;
}

private void removeAllFutureMessagesLocked() {
    // 獲取系統(tǒng)從開機到現(xiàn)在到時間
    final long now = SystemClock.uptimeMillis();
    // 將當(dāng)前的頭部消息賦值給 p
    Message p = mMessages;
    if (p != null) {
        if (p.when > now) {
            // 如果當(dāng)前頭部消息將要執(zhí)行的時間大于系統(tǒng)開機到現(xiàn)在的時間,則執(zhí)行 removeAllMessagesLocked() 方法
            // 清空 MessageQueue 隊列
            removeAllMessagesLocked();
        } else {
            Message n;
            // 遍歷當(dāng)前的 MessageQueue襟己,直到某個消息的執(zhí)行時間小于 now 值(即這個消息正在執(zhí)行)
            // 將這個消息的 next 賦值為 null
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;
            // 回收不會被執(zhí)行的 Message
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while (n != null);
        }
    }
}

終于講完了引谜,希望大家能通過我的文章,徹底理解 Handler 的機制擎浴,但我的能力有限员咽,如果存在錯誤的地方,還請指出贮预。

零碎的東西很多贝室,為了方便大家記憶,我把上面的內(nèi)容做成了思維導(dǎo)圖仿吞,需要的朋友可以保存下來滑频,偶爾看一下,幫助自己記憶唤冈。

Android 消息機制

歡迎關(guān)注本文作者:

掃碼關(guān)注并回復(fù)「干貨」峡迷,獲取我整理的千G Android、iOS你虹、JavaWeb绘搞、大數(shù)據(jù)、人工智能等學(xué)習(xí)資源傅物。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末夯辖,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子董饰,更是在濱河造成了極大的恐慌蒿褂,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卒暂,死亡現(xiàn)場離奇詭異贮缅,居然都是意外死亡,警方通過查閱死者的電腦和手機介却,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來块茁,“玉大人齿坷,你說我怎么就攤上這事桂肌。” “怎么了永淌?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵崎场,是天一觀的道長。 經(jīng)常有香客問我遂蛀,道長谭跨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任李滴,我火速辦了婚禮螃宙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘所坯。我一直安慰自己谆扎,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布芹助。 她就那樣靜靜地躺著堂湖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪状土。 梳的紋絲不亂的頭發(fā)上无蜂,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天,我揣著相機與錄音蒙谓,去河邊找鬼斥季。 笑死,一個胖子當(dāng)著我的面吹牛彼乌,可吹牛的內(nèi)容都是我干的泻肯。 我是一名探鬼主播,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼慰照,長吁一口氣:“原來是場噩夢啊……” “哼灶挟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起毒租,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤稚铣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后墅垮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惕医,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年算色,在試婚紗的時候發(fā)現(xiàn)自己被綠了抬伺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡灾梦,死狀恐怖峡钓,靈堂內(nèi)的尸體忽然破棺而出妓笙,到底是詐尸還是另有隱情,我是刑警寧澤能岩,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布寞宫,位于F島的核電站,受9級特大地震影響拉鹃,放射性物質(zhì)發(fā)生泄漏辈赋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一膏燕、第九天 我趴在偏房一處隱蔽的房頂上張望钥屈。 院中可真熱鬧,春花似錦煌寇、人聲如沸焕蹄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腻脏。三九已至,卻和暖如春银锻,著一層夾襖步出監(jiān)牢的瞬間永品,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工击纬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鼎姐,地道東北人。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓更振,卻偏偏與公主長得像炕桨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子肯腕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,930評論 2 361

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