Android 原始碼分析 --Handler 機制的實現與工作原理

DonKingLiang發表於2017-05-08

Handler機制在Android中是一個非常重要的知識點,在我們的平常開發中也是經常使用到的。在Android的面試中Handler機制更是必考的題目,而且題目也很單一:請說說Handler、Looper、MessageQueue之間的關係。這個問題無論是我去面試還是我面試別人,都會問到的一個問題。如果你遇到了這個問題,你只是簡單的說一下它們是什麼什麼關係,那是遠遠不夠的。這道題考察的無非就是你對Handler機制的實現和它的工作原理的瞭解。下面我們就通過Handler機制的工作流程圖和原始碼來詳細分析Handler實現細節和工作原理。
下面想看Handler的工作流程圖:(第一次畫圖,有點醜,湊合著看吧)

Android 原始碼分析 --Handler 機制的實現與工作原理
Handler的工作流程.png

因為Handler的主要作用就是執行緒切換,所以在圖中我把Handler執行緒變化也畫了出來。從這張圖我們能看出幾點資訊:
1、Handler負責訊息的傳送和處理:Handler傳送訊息給MessageQueue和接收Looper返回的訊息並且處理訊息。
2、Looper負責管理MessageQueue:Looper會不斷地從MessageQueue取出訊息,交給Handler處理。
3、MessageQueue是訊息佇列(實時上它是用連結串列實現的),負責存放Handler傳送過來訊息。
4、一個Looper對應一個執行緒(自己所在的執行緒,如:執行緒B)。Looper的loop()方法執行在自己所在的執行緒(執行緒B)中,當Handler線上程A傳送一條訊息存放到MessageQueue時,Looper的loop()方法線上程B把訊息取出來,並交給Handler處理,所以Handler的處理訊息的方法是執行在Looper所在的執行緒(執行緒B)的。由於多個執行緒之間共享記憶體空間,所以Handler可以線上程A把訊息存放到MessageQueue,Looper可以線上程B把訊息取出來,一存一取之間就實現了執行緒的切換。
現在我們瞭解了Handler的工作流程和執行緒切換原理。那麼它在原始碼中又是如何去實現的呢?
從使用的角度看,我們要使用Handler首先要得到一個Handler物件,那麼我們就從最簡單的new Handler()作為入口,來分析它的原始碼。

    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
        //獲取Looper物件
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //獲取Looper物件的mQueue屬性,mQueue 就是MessageQueue物件。
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }複製程式碼

在Handler的構造方法中,首先通過Looper.myLooper()方法獲取當前執行緒的Looper物件,如果Looper物件為空,就丟擲異常,說當前執行緒還沒有呼叫Looper.prepare()方法。如果Looper不為空,Handler就會持有Looper的MessageQueue物件mQueue。
我們再看Looper.myLooper()和Looper.prepare()兩個方法:

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

    //建立當前執行緒的Looper物件
    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));
    }

    //獲取當前執行緒的Looper物件
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }複製程式碼

這裡有一個很關鍵的類:ThreadLocal,它一個執行緒內部的資料儲存類,通過它儲存的資料只有在它自己的執行緒才能獲取到,其他執行緒是獲取不到的。所以sThreadLocal.get()獲取的就是當前執行緒的Looper物件。在Looper.prepare()方法中我們看到了如果當前執行緒已經有Looper物件,就會丟擲異常,說一個執行緒只能建立一個Looper物件,所以Looper物件與自己所在的執行緒是相對應的。
再看Looper的構造方法:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }複製程式碼

Looper的構造方法是私有的,外界不能直接建立Looper物件,只能通過Looper.prepare()方法建立物件並且通過Looper.myLooper()獲取物件,這就保證了一個執行緒只能有一個Looper物件。Looper.prepare()不能呼叫兩次。
在Looper的構造方法中會建立一個MessageQueue物件,這個就是負責存放訊息的訊息佇列,也就是Handler所持有的mQueue 物件。它是由Looper建立和管理的。
看完了Handler、Looper和MessageQueue物件的建立,接著看訊息的傳送:
Handler傳送訊息的方法有很多,但無論你是send一個Message還是post一個Runnable;無論你是延時傳送還是不延時傳送,最終都會呼叫Handler的enqueueMessage()方法。

   private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //把this賦值給msg的target屬性,this就是Handler物件。
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //把訊息存放到MessageQueue 
        return queue.enqueueMessage(msg, uptimeMillis);
    }複製程式碼

這裡直接把訊息存放到MessageQueue 就完事了。那麼訊息又是從哪裡被取出來的呢?
Looper裡有一個Looper.loop()方法,我們看一下它的原始碼。

public static void loop() {

        final MessageQueue queue = me.mQueue;
        //一個死迴圈
        for (;;) {
            //從MessageQueue中取出一條訊息
            Message msg = queue.next(); 
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            //把訊息交給Handler處理。
            msg.target.dispatchMessage(msg);
        }
    }複製程式碼

從上面的程式碼中我們看到loop()會開啟一個死迴圈,不斷地從MessageQueue中取出訊息並交給Handler處理。在前面的enqueueMessage()方法中我們知道了msg.target就是傳送訊息的Handler物件。
這裡有同學可能會有疑問:上面的程式碼中明明如果(msg == null),就退出方法,為什麼我還說loop()裡面是個死迴圈呢?這是因為MessageQueue的next()方法取出訊息的時候,如果沒有訊息,next()方法會阻塞執行緒,直到MessageQueue有訊息進來,然後取出訊息並返回。所以queue.next()一般不會返回null,除非呼叫Looper的quit()或者quitSafely()方法結束訊息輪詢,queue.next()才會返回null,才會結束迴圈。

    public void quit() {
        mQueue.quit(false);
    }
    public void quitSafely() {
        mQueue.quit(true);
    }複製程式碼

最後我們來看 一下訊息的處理:Handler的dispatchMessage(msg)方法。

public void dispatchMessage(Message msg) {
        //如果Message有自己的callback,就由Message的callback處理
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
             //如果Handler有自己的mCallback,就由Handler的mCallback處理
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //預設的處理訊息的方法
            handleMessage(msg);
        }
    }複製程式碼

處理訊息的方法有三個:
1、優先順序最高的是Message自己的callback,這是一個Runnable物件,我們用Handler post一個Runnable的時候,其實就是把這個Runnable賦值個一個Message物件的callback,然後把Message物件傳送給MessageQueue。
2、優先順序第二的是Handler自己的mCallback,在我們建立一個Handler物件的使用可以傳一個Handler.Callback物件給它並實現Callback裡的handleMessage(msg)方法,作為我們的訊息處理方法。
3、優先順序最低的是Handler自己的handleMessage(msg)方法,這也是我們最常用的訊息處理方法。

到這裡我們的分析就結束了,現在你對Handler機制是不是有了深刻的認識呢。

文章已同步到我的CSDN部落格blog.csdn.net/u010177022/…

相關文章