1. 程式人生 > >Android:Handler 二三事(三)訊息處理機制

Android:Handler 二三事(三)訊息處理機制

主要內容

Handler 的訊息處理機制。
主要是關於 MessageQueue、Message、Looper、Handler 之間的關係。

Android 訊息驅動機制的四要素

  • 接收訊息的訊息佇列–>MessageQueue
  • 阻塞式的從訊息佇列中接受訊息並進行處理的執行緒–>Thead+Looper
  • 可傳送的” 訊息的格式”–>Message
  • 訊息傳送函式–>Handler 的 post 和 sendMessage

MessageQueue 雖然叫訊息佇列,但是它的內部儲存結構並不是真正的佇列,而是單鏈表的資料結構。它只能儲存訊息,處理訊息就用到了 Looper。

Looper 內部有一個無限迴圈,去查詢有沒有新訊息,有就去處理,沒有就一直等待。
Looper 中有一個 ThreadLocal,他不是執行緒,它的作用是可以在每個執行緒中儲存資料。在 Handler 內部獲取當前執行緒的 Looper,就是通過 ThreadLocal 來獲取的。因為 ThreadLocal 可以在不用的執行緒中互不干擾的儲存和提供資料,通過 ThreadLocal 可以獲取每個執行緒的 Looper。
執行緒是預設沒有 Looper 的,要想使用 Handler 就必須為執行緒建立 Looper。如果是 UI 執行緒,也就是 ActivityThread,在建立時就會初始化 Looper,所以我們可以在主執行緒中直接使用 Handler。

Handler 主要實現是將一個任務切換到指定執行緒。Android 中只允許 UI 執行緒更改 UI。在 ViewRootImpl 有一個 checkThread 方法對 UI 操作進行了驗證。

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

那麼為什麼不允許子執行緒訪問 UI 呢?

Android 中的 UI 執行緒不是執行緒安全的。如果在多執行緒中訪問可能會導致 UI 控制元件處於不可預知的狀態。當然我們可以使用鎖機制來,但是鎖機制有兩個問題:1、加上鎖機制會讓 UI 訪問的邏輯變得複雜;2、鎖機制會降低 UI 訪問的效率。簡單高效的方法就是採用單執行緒模型處理 UI。
——《Android 群英傳》

使用 Handler 必須保證執行緒中有 Looper,否則就會報錯。我們可以線上程中建立 Looper,也可以在有 Looper 的執行緒中建立 Handler。

Handler 通過 post 或 send 方法傳送訊息,然後交由 Looper 處理。同時,會呼叫 MessageQueue 的 enqueueMessage 方法將訊息放到訊息佇列中,Looper 發現新訊息,就會處理訊息,最終訊息中的 Runnable 或者 Handler 的 handleMessage 方法就會被呼叫。

ThreadLocal

ThreadLocal 是什麼

是一個執行緒內部的資料儲存類,通過它可以在指定的執行緒中儲存資料,資料儲存以後,只有在指定執行緒中才可以獲取,其他執行緒無法獲取。

什麼情況下使用 ThreadLocal

1、當某些資料是以執行緒為作用域並且不同執行緒具有不同的資料副本的時候,可以使用
2、複雜邏輯下的物件傳遞,比如監聽器的時候,有的時候函式呼叫過深,可以採用 ThreadLocal,它可以讓監聽器作為執行緒內的全域性物件,線上程中內部只要通過 get 方法就可以獲取監聽器。

如果不採用 ThreadLocal,有兩種方法,一種是將監聽器以引數的形式進行傳遞,這種會讓程式看起來很糟糕;一種是將監聽器作為靜態變數供執行緒訪問,但這樣不不可擴充的,n 個執行緒就要有 n 個靜態變數。使用 ThreadLocal 每個執行緒都能獲取自己的監聽器物件。

ThreadLocal 例項

class MainActivity : AppCompatActivity() {

    private val TAG = "tag"
    private var mBooleanThreadLocal = ThreadLocal<Boolean>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mBooleanThreadLocal.set(true)
        Log.d(TAG,"UI ============" + mBooleanThreadLocal.get())

        object : Thread("Thread#1") {
            override fun run() {
                mBooleanThreadLocal.set(false)
                Log.d(TAG,"Thread#1 ================== " + mBooleanThreadLocal.get())
            }
        }.start()

        object : Thread("Thread#2") {
            override fun run() {
                Log.d(TAG,"Thread#2 ================== " + mBooleanThreadLocal.get())
            }
        }.start()

    }
}

列印結果

07-05 14:26:55.992 6979-6979/? D/tag: UI ============true
07-05 14:26:55.992 6979-6999/? D/tag: Thread#1 ================== false
07-05 14:26:55.992 6979-7000/? D/tag: Thread#2 ================== null

可見不同執行緒訪問的雖然是同一個 ThreadLocal 物件,到那時他們獲取的結果是不一樣的。不同執行緒訪問同一個 ThreadLocal 的 get 方法,它內部會從各自的執行緒中取出一個數組,然後再從資料中根據當前 ThreadLocal 的索引去查詢對應的 value 值。

ThreadLocal.set

這裡寫圖片描述

在來看一下它的儲存,也就是 put 方法。
這裡寫圖片描述

ThreadLocal.get

這裡寫圖片描述

reference 是什麼

    /** Weak reference to this thread local instance.
      * 對此執行緒本地例項的弱引用 
      */
    private final Reference<ThreadLocal<T>> reference
            = new WeakReference<ThreadLocal<T>>(this);

ThreadLocal 總結

從以上可以看出,他們操作的物件都是的當前執行緒的 localValues 物件的 table 陣列,因為在不同執行緒訪問同一個 Threadlocal 的 set 和 get 方法,它們對 ThreadLocal 所做的讀寫操作僅限於各自執行緒的內部。

注意:這是 API21 的原始碼,API25 採取了不同的實現,暫時沒明白。

Looper

Looper 是連線 Handler 訊息處理機制的關鍵,也可以說它是一個動力。Handler 將 Message 發到 MessageQueue 中,Looper 是一個死迴圈,從 MessageQueue 中不斷的取訊息,然後分發給 Handler(handleMessage)。

除了 UI 執行緒,普通的執行緒(HandlerThread 除外)是不帶 Looper 的。如果想實現 UI 執行緒和子執行緒通訊,需要在子執行緒實現一個 Looper。

  1. 判斷是否已經有 Looper,呼叫 Looper.prepare()
  2. 做一些準備工作,比如暴露 Handler
  3. 呼叫 Looper.loop(),執行緒進入阻塞態

Looper.loop()

loop()
從程式碼中可以看出,loop() 中有一個死迴圈,不斷的從 MessageQueue 中取訊息,並且通過 dispatchMessage 方法分發。當沒有訊息時,next 方法就會阻塞在那裡。

Looper.prepare()

prepare()
從中可以看出,一個執行緒只能繫結一個 Looper 物件,如果再次繫結就會報錯。
sThreadLocal.get() 返回的是 Looper 物件,由此可見 prepare() 方法只能調一次

建構函式

建構函式

Looper 的退出

兩種方法 quit 和 quitSafely
quit 直接退出,quitSafely 設定一個退出標記,把訊息佇列中的訊息處理完之後才會退出。退出之後,Handler 傳送的訊息會失敗,Handler 的 send 方法會返回 false。

Handler

從上面看一看到訊息被分發到 dispatchMessage 方法中,那麼看一下 Handler 的 dispatchMessage 方法。
Handler

Handler 也可以通過 Callback 來建立 Handler handler = new Handler(callback),當我們不想派生子類的時候就可以用這個。

post

這裡看一下往訊息佇列中傳送訊息是怎麼實現的
這裡寫圖片描述
這裡寫圖片描述
這裡可以看到無論是 post 還是 sendMessage 都是調的 sendMessageDelayed 方法
看一下這個方法

 public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

最終呼叫的是 sendMessageAtTime 方法

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

MessageQueue

MessageQueue 雖然稱為訊息佇列,但是內部是通過單鏈表的結構來維護訊息列表。
單鏈表的結構大致如下
這裡寫圖片描述

它的在插入和刪除上比較有優勢。但是因為每個節點都有指標,空間利用率低,而且不支援隨機讀取資料。

從中可以大致瞭解 Handler 的訊息機制。

參考