Android消息機制

Android消息機制

引言

從開發的角度說,Handler是Android消息機制的上層接口,使得在開發的過程中只需要和Handler交互即可。
  Handler的使用過程很簡單,通過它可以將一個任務切換到Handler所在的線程中進行.Handler一般被用于更新UI。因為Android不允許在子線程中更新UI,所以子線程需要通過Handler發送信息到主線程更新UI。
  Handler不僅可以用于在其他的線程處理的過程中更新UI,還可以處理其他的一些線程間的通信。

概述

簡介

Android的消息機制實際上指的是Handler的消息機制,而Handler的運行需要通過底層的MessageQueue和Looper的支撐。MessageQueue即消息隊列,實際上由單鏈表而不是隊列儲存消息列表。Looper即循環,也可以被理解為消息循環,它不能去處理消息,而Looper就填補了這個功能,Looper通過運行一個死循環,從MessageQueue中獲取信息,如果有則處理信息,否則就一直阻塞等待。Looper中還有一個特殊的概念,ThreadLocal,ThreadLocal用于在每個線程中儲存數據。Handler創建的時候會默認使用當前線程的Looper對象來構造消息循環系統,而Android通過ThreadLocal可以在不同的線程中互不干擾的獲取數據的特點,通過ThreadLocal輕松地獲取每個線程的Looper。
  需要注意的是,線程默認是沒有Looper的,如果需要使用Handler則必須為線程創建Looper。而Android中的UI線程,即ActivityThread,在被創建的時候會初始化Looper,所以不需要再創建Looper就可以使用Handler。

使用Handler的原因

Android的消息機制中,Handler和MessageQueue、Looper實際上是一個整體,而在開發的過程中,開發者只是比較多的接觸Handler而已。而Handler的主要作用是將一個任務切換到它Handler所在的線程執行,Android提供這樣的一個功能是它規定UI訪問只能在主線程中執行,如果在子線程中訪問UI,那么程序就會拋出異常,究其原因,是因為ViewRootImpl通過checkThread方法對UI操作做了驗證。
  因為Android的這個限制,所以必須在主線程中訪問UI,但是Android又不建議在主線程中進行耗時的操作,否則會導致程序出現ANR。假如我們需要進行耗時工作,如從網絡中獲取數據并更新UI,這個時候必須新建一個子線程來完成網絡請求操作,獲取完畢后需要在子線程中更新UI,但子線程中無法直接更新UI,所以這個時候必須通過Handler來將UI的工作切換到主線程中,解決了子線程中無法訪問UI的矛盾。這就是為什么Android提供Handler。

不允許在子線程中更新UI的原因

Android系統不允許在子線程中更新UI的原因是,Android中的UI并不是線程安全的,而如果在多線程中并發訪問可能會導致UI控件處于不可預期的狀態。Android系統也不對UI控件的訪問提供鎖機制,原因有二,首先添加鎖機制會讓UI訪問的邏輯變得復雜,其次鎖機制會因為其會阻塞某些線程的執行而降低UI訪問的效率。而鑒于這兩個問題,最簡單高效的方法就是通過單線程模型來處理UI操作,對開發者來說不不至于太復雜,只需通過Handler來切換UI訪問的線程即可。

Looper的構造

Handler創建的時候會采用當前線程的Looper來構造內部的消息循環系統,如果當前的線程沒有Looper,那么就會拋出RuntimeException異常。
  解決這個問題只需要為當前的線程創建Looper即可,或者在一個有Looper的線程中創建Handler。
  而當Handler創建完畢后,這個時候其內部的Looper以及MessageQueue就可以和Handler一起協同工作了,然后通過Handler的post方法將一個Runnbale投遞到Handler內部的Looper進行處理,而其實post方法的內部也是通過send方法完成的。
  以下是send方法的工作流程。
  當Handler的send方法被調用時,它會調用MessageQueue的enqueueMessage方法將這個消息放入隊列中,然后Looper發現有新消息到來時,就會處理這個消息。最終消息中的Runnable或者Handler的方法就會被調用。注意Handler默認創建的Looper是運行在創建Handler的線程中的,這樣一來Handler中的業務邏輯就被切換到所在的線程中處理了。
  下圖是Handler的工作過程圖。
  

Handler的工作過程

Android的消息機制分析

ThreadLocal

ThreadLocal是一個線程內部的數據儲存類,通過它可以在線程的內部存儲信息。并在指定的線程中可以獲取到儲存的數據,對于其他線程來說則無法獲取到。
  對于Hanlder來說,它需要獲取當前線程的Looper對象,很顯然Looper的作用域就是線程并且不同的線程具有不同的Looper,這個時候通過ThreadLocal就可以輕松地實現Looper在線程中的獲取。

消息隊列的工作原理

消息隊列即Android中的MessageQueue,MessageQueue主要包含兩個操作,插入和讀取,而讀取操作本身會伴隨著刪除操作,插入和獨缺對應的方法分別為enqueueMessage和next,其中enqueueMessage的作用是往消息隊列里面插入一條信息,而next的作用是從消息隊列中插入一條信息,而next的作用是從消息隊列中取出一條信息并且將其從消息隊列中移除。而MessageQueue的內部實現是通過一個單鏈表的數據結構來維護消息列表,單鏈表在插入和刪除上復雜度為O(1)。
  enqueueMessage的內部實現實際上就是單鏈表的插入操作,將Message插入到隊列中去,其優先級和delay字段有關。
  next的內部實現包含著一個無限循環的方法,如果內部的消息隊列中沒有信息或者存在信息但是還在delay中,那么將一直阻塞,而當有新消息到來時,next將返回這條信息,并從單鏈表中移除。

Looper的工作原理

Looper在Android的消息機制中扮演著消息循環的角色,具體來說就是它不停地從MessageQueue中查看是否有新消息,如果有新消息則立刻進行處理,說否則就一直阻塞。Looper的構造方法如下,在構造方法中它會創建一個MeesageQueue消息隊列,然后將當前線程的對象保存起來。

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

而Handler的工作需要Looper,在沒有Looper的線程創建Handler就會拋出異常,而為一個沒有Looper的線程創建Looper只需要通過調用Looper.prepare()即可,接著通過Looper.loop()開啟消息循環,代碼如下。

new Thread("Thread#2") {

    @Override
    public void run() {
        Looper.prepare();
        Hanlder handler = new Handler() {
            // some code
        }
        Looper.loop():
    }
}

Looper除了prepare方法外,還提供了prepareMainLooper方法,這個方法主要是給主線程也就是ActivityThread創建Looper使用的,其本質也是通過prepare方法來實現的。由于主線程的Looper方法比較特殊,所以Looper提供了一個getMainLooper方法,通過它可以在任何地方獲取到主線程的Looper。
  Looper也是可以退出的,Looper提供了quit和quitSafely來退出一個Looper,兩者的在于,quit會直接退出Looper,而quitSafely只是設定一個退出標記,然后把消息隊列的已有印象處理完畢才完全地退出。Looper退出后,通過Handler發送的消息會失敗,這個時候Handler的send方法會返回false。
  在子線程中,如果手動為其創建了Looper,那么在所有事情都完成以后應該調用quit來終止消息循環,否則這個子線程則一直處于等待的狀態。而當子線程推出Looper后,這個線程會立刻停止,因此建議當子線程不需要再運行的時候終止Looper。

Loop.loop()

Looper最重要的是loop方法,只有調用了loop方法,消息循環系統才真正的開始。 Looper的loop方法的工作過程也比較好理解,loop是一個死循環,唯一跳出循環的方式是MessageQueue的next方法返回了null。而當Loooper的quit方法被調用時,Looper就會調用MessageQueue的quit或者quitSafely通知消息隊列退出,當消息隊列被標記為退出狀態時,它的next方法就會返回null。也就是說,Looper必須退出,否則Looper方法將無限循環下去,loop方法會MessageQueue的next方法來獲取新消息,而next是一個阻塞的操作,當沒有消息的時候,next方法會一直阻塞,這同時也導致loop方法一直阻塞。如果MessageQueue的next方法返回了新消息,Looper就會處理這條消息"msg.target.dispatchmessage(msg),這里的msg.target是發送這條消息的Handler對象,這樣handler發送的消息最終又交給了它的dispatchMessage方法處理。但是這里不同的是,Handler的dispatchMessage方法是在創建Hanlder所使用的Looper中執行的,這就成功地將代碼邏輯切換到指定的線程中執行了。

Handler的工作原理

Handler的工作主要包含信息的發送和接收過程。消息的發送可以通過post的一系列方法以及send的一系列方法來實現,post的一系列方法最終是通過send的一系列方法來實現的。
  Handler內部源碼中發送消息的過程僅僅是向消息隊列里面插入了一條信息,MessageQueue的next方法就會返回這條消息給Looper,Looper收到消息后就開始處理了,最終消息由Looper交由Handler處理,即Handler的dispatchMessage方法會被調用,這時Handler就進入了處理消息的階段。

Handler處理消息的過程

首先,先檢查Message的callback是否為null,不為null則通過handleCallback來處理消息。Message的callback是一個Runnable對象,實際上就是通過Handler的post方法所傳遞的Runnable參數。handleCallback的邏輯也是很簡單的,代碼如下

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

privare static void handlerCallback(Message msg) {
    msg.callback.run();
}

其次,檢查mCallback是否為null,不為null就調用mCallback的handlerMessage方法來處理消息。Callback是個接口,代碼定義如下。

public interface Callback {
        public boolean handleMessage(Message msg);
    }

而通過Callback可以采用如下方式創建Handler對象:Handler handler = new Hanler(callback)。使用Callback的意義在于不用創建一個Hanlder的實例而不用派生Handler的子類,并且可以在Callback的handleMessage通過返回false時,通過Handler的handleMessage方法來處理。
  日常開發中,也可以通過派生Handler子類的方式并重寫handleMessage方法來處理具體的消息,而Callback通過另一種方式也可以實現。
  而最后,通過調用Handler的handlerMessage方法來處理消息。
  Handler處理消息的過程如圖下。
  

Handler處理消息流程圖

指定Looper

Handler還有一個特殊的構造方法,可以指定一個Looper來構造Handler,代碼如下。

public Hanlder(Looper looper) {
    this(looper. null, false)
}

而Handler的默認構造方法是public Handler(),這個構造方法會調用以下代碼的構造方法。

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

    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;
    mAsynchronous = async;
}

可以看出假如當前線程沒有Looper的話,就會拋出異常。

主線程的消息循環

Android的主線程就是Activity,主線程的入口方法是main,在main方法中系統會通過Looper.prepareMainLooper()來創建主線程的Looper以及MessageQueue,并通過Looper.loop()來開啟消息循環。
  主線程的消息循環開始了以后,AcitivyThread還需要一個Handler來和消息隊列進行交互,這個Hanlder就是ActivityThread.H,它內部定義了一組消息類型,主要包含四大組件的啟動和停止等過程。
  AcitivityThread通過ApplicationThread和AMS進行進程間通信,AMS以進程間通信方式完成AcitivityThread的請求后回調ApplicationThread中的Binder方法,然后ApplicationThrad會向H發送消息,H收到消息后會將ApplicationThread中的邏輯切換到ActivityThread中執行,即切換到主線程中去執行,這個過程就是主線程的消息循環過程。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容