Android Handler機制使用,原始碼分析

艾陽丶發表於2017-05-02

目錄

前言

一、Looper輪詢器

二、Handler處理者

三、MessageQueue訊息佇列

四、Message訊息載體

五、小結


Thread+Handler機制實現執行緒間通訊的方式在安卓開發中的使用較為普遍。通常是將Handler是在主執行緒中建立,子執行緒拿到這個Handler向主執行緒中傳送訊息。Handler的執行依賴於MessageQueue和Looper,當然,既然是訊息機制,通常也需要用到Message,所以Handler、Looper、MessageQueue和Message的相符配合工作,原理就像是一個工廠的生產線,Looper是發動機,MessageQueue就是傳送帶,Handler是工人,Message就是待處理的產品,這麼理解會有一個關係概念,方便理解下面的內容。

 

一、Looper輪詢器

Looper是Handler機制執行工作的心臟,所以我們先搞懂Looper是怎麼回事是關鍵的第一步。我們知道應用的啟動通過main方法作為入口,這個main方法在主執行緒(ActivityThread)中,檢視這個類的main方法可以看到有兩個方法:Looper.prepareMainLooper()和Looper.loop()方法。

我們從這兩個方法去看清Looper的廬山真面目吧!go!go!

public static final void main(String[] args) {
        ...
        //主執行緒中呼叫Looper.prepareMainLooper()方法建立Looper
        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        //主執行緒中呼叫Looper.loop(),開始輪詢,取訊息
        Looper.loop();
       ...
    }
}

首先,ActivityThread首先呼叫的Looper.prepareMainLooper()方法,我們來看一下這個方法原始碼都做了些什麼?

在Looper類中
public static final void prepareMainLooper() {
     prepare();
     setMainLooper(myLooper());
     if (Process.supportsProcesses()) {
        myLooper().mQueue.mQuitAllowed = false;
     }
}

原來,該方法中首先呼叫本類中的prepare()方法建立Looper物件,並且把該物件setMainLooper()繫結到當前執行緒中。在這裡既然是主執行緒呼叫的,那麼該Looper物件自然是在主執行緒當中。繼續看裡面的prepare()這個方法:

在Looper類中
public static final void prepare() {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //把Looper繫結到當前執行緒
    sThreadLocal.set(new Looper());
}

這個方法只做了一個事情,就是保證當前執行緒只能存在一個Looper物件,如果已經存在就會丟擲異常(Android規定一個執行緒只允許關聯一個Looper)。

繼續看new Looper()這個構造方法,我們可以看到:

private Looper() {
    mQueue = new MessageQueue();
    mRun = true;
    mThread = Thread.currentThread();
}

Looper這個類的構造方法是private私有的,不允許外界直接new出來Looper物件,而最重要的是:在建立Looper的時候,同時建立了一個訊息佇列MessageQueue。所以請注意!MessageQueue是依賴Looper一起建立的

然後,看ActivityThread呼叫Looper.loop()方法,開始輪詢訊息:

   public static final void loop() {
        Looper me = myLooper();
        //通過Looper物件,獲取MessageQueue
        MessageQueue queue = me.mQueue;
       ...
        //死迴圈獲取訊息
        while (true) {
            //呼叫MessageQueue的next()方法取訊息,這個過程也是死迴圈
            Message msg = queue.next(); 

            if (msg != null) {
                ...
                //取到訊息之後,交給傳送該訊息的Handler取處理訊息
                msg.target.dispatchMessage(msg);
               ...
                //回收訊息,在Message中維護的有一個訊息池
                msg.recycle();
            }
        }
    }

從上述原始碼中簡單的註解,我們可以直觀明白主執行緒的Looper和對應的MessageQueue之間最直接的關係了。但是我們帶著一個問題如何把Message訊息從子執行緒交給Handler在主執行緒中處理的?繼續分析msg.target.dispatchMessage()原始碼:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {//注意這個方法呼叫
                return;
            }
        }
        handleMessage(msg);
    }
}

我們可以知道Looper.loop()方法裡的死迴圈是不斷地獲取MessageQueue中的Message,然後呼叫與Message繫結的Handler物件的dispatchMessage方法。最後,我們終於明白原來Handler的handleMessage就在這裡呼叫的,其實就是一個介面回撥啊。

 

二、Handler處理者

我們知道Handler的工作主要就是傳送和處理訊息。其實Handler的構造方法有多種,但都會獲取當前執行緒的Looper物件和MessageQueue物件。這個其實也沒什麼,看看就好:

//Handler的構造方法1
public Handler() {
    ...
    //獲取當前執行緒的Looper物件
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    //獲取訊息佇列物件
    mQueue = mLooper.mQueue;
    mCallback = null;
}

//Handler的構造方法2
public Handler(Callback callback) {
   ...
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
}

//Handler的構造方法3
public Handler(Looper looper) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = null;
}

//Handler的構造方法4
public Handler(Looper looper, Callback callback) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
}

傳送訊息可以通過一系列send方法,也可以通過一系列post方法,不過post方法最終還是通過send方法去實現。
用send方法,最終也會呼叫一個方法如下:

//在Handler中
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
    boolean sent = false;
    MessageQueue queue = mQueue;
    if (queue != null) {
        //呼叫msg.target的方法把Message和傳送它的Handler繫結,所以Looper能夠把正確的Message交給傳送它的Handler處理
        msg.target = this;
        //呼叫MessageQueue的enqueueMessage方法把訊息加入訊息佇列
        sent = queue.enqueueMessage(msg, uptimeMillis);
    }
    else {
        RuntimeException e = new RuntimeException(
            this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
    }
    return sent;
}

可以看到,Handler傳送訊息的過程就是向訊息佇列中插入一條訊息。前面已經講到,MessageQueue呼叫next方法去輪詢訊息,那麼當MessageQueue拿到訊息之後,把訊息傳遞給Looper,最終交給Handler去處理,即dispatchMessage方法會被呼叫。此時,Handler開始處理訊息。值得一提的是,在訊息佇列中可能有不同Handler傳送的多個訊息,通過在傳送訊息的時候把Message和傳送它的Handler繫結,Looper就會把訊息正確的交給傳送它的Handler來處理。dispatchMessage方法如下:

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

Handler處理訊息的過程:

第一步,檢視Message的callback是否為null,不為null就通過handleCallback(msg)方法處理訊息。這裡的callback實際上就是一個Runnable物件。如果以post方式去傳送訊息,最終就會呼叫handleCallback(msg)方法去處理,這個方法內容為:

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

第二步,檢查mCallBack是否為null,如果不為null就呼叫mCallBack的handleMessage(msg)方法。這個mCallBack是CallBack介面的子類物件,前面已經說過Handler的構造方法中有兩個可以用到這個CallBack。如果mCallBack為null,最終就會呼叫Handler的handleMessage(msg)方法,這個方法通常是在建立Handler時被使用者重寫的方法。


需要說明:在主執行緒當前我們建立Handler時並沒有自己建立Looper物件,這是因為主執行緒已經為我們建立好了;如果要在子執行緒當前建立Handler,一定要在之前建立Looper物件,即呼叫Looper.prepare()方法。

 

三、MessageQueue訊息佇列

前面的內容已經講了很多關於MessageQueue的東西,這裡就總結下了。MessageQueue主要包含兩個操作:插入訊息(enqueueMessage方法)和讀取訊息(next方法)。讀取的過程也伴隨著刪除操作。MessageQueue的的內部實際上是通過一個單連結串列的資料結構來維護訊息列表,這主要也是因為單連結串列在插入和刪除上比較有優勢。

   final boolean enqueueMessage(Message msg, long when) {
    ...
        Message p = mMessages;
        if (p == null || when == 0 || when < p.when) {
            // 當前傳送的message需要馬上被處理調,needWake喚醒狀態置true
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; // new head, might need to wake up
         } else {
              // 當前傳送的message被排隊到其他message的後面,needWake喚醒狀態置為false
                Message prev = null;
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
                msg.next = prev.next;
                prev.next = msg;
                needWake = false; // still waiting on head, no need to wake up
            }
    }
    // 是否喚醒主執行緒
    if (needWake) {
        nativeWake(mPtr);
    }
    return true;
}

enqueueMessage的主要操作其實就是單連結串列的插入操作,根據時間看當前傳送的Message是否需要馬上處理。這個enqueueMessage方法是Handler傳送訊息的時候呼叫。

下面來看next方法:

final Message next() {
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;

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

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            final Message msg = mMessages;
            if (msg != null) {
                final long when = msg.when;
                if (now >= when) {
                    mBlocked = false;
                    mMessages = msg.next;
                    msg.next = null;
                    if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg);
                    return msg;
                } else {
                    nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
                }
            } else {
                nextPollTimeoutMillis = -1;
            }

        ...
}

注意"return msg",是next方法返回一個Message物件。next方法中也是採用阻塞的方式去獲取訊息佇列中的訊息,一旦有訊息立即返回並且將它從單連結串列中移除。如果沒有訊息就一直阻塞。前面已經提到,這個取訊息的next方法是在Looper的loop()方法中呼叫。

 

四、Message訊息載體

Message只有一個無參構造方法,但是Message有多個obtain靜態方法來返回Message物件。

採用哪種方式建立Message物件都可以,但是建議採用obtain方法來建立。這是因為Message通過在內部構建一個連結串列來維護一個被回收的Message物件池。當使用者呼叫obtain方法時會優先從池中獲取,如果池中沒有則建立新的Message物件。同時在使用完畢之後,進入池中以便於複用。這個在Looper.loop()方法可以看到一點端倪,在使用完畢時候呼叫了Message的recycle()方法。

下面是obtain方法建立Message物件的流程:

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

 

五、小

  • 在應用啟動時,會開啟一個主執行緒建立一個Looper物件。Looper物件與當前執行緒繫結,使得不同執行緒間的Looper不能共享。
  • 一個Thread執行緒對應多個Handler,一個Thread對應一個Looper物件、對應一個MessageQueue物件。
  • Handler通過send方法或者post方法,把訊息加入訊息佇列MessageQueue中。
  • 主執行緒中呼叫Looper的loop()方法,會開啟訊息迴圈,不斷的從訊息佇列中取出訊息。
  • Looper拿到訊息之後呼叫Handler的dispatchMessage方法來處理訊息。
  • 子執行緒中建立Handler需先建立Looper物件,Thread與Handler共享一個Looper與MessageQueue。

 

 

相關文章