【總結】Android消息機制

Android消息機制主要是指Handler的運行機制,以及MessageQueue和Looper的工作過程。

概述

從開發的角度來說,Handler是Android消息機制的上層接口,開發中只需要關心Handler的交互即可。

很多人認為Handler的作用是更新UI,這個認為沒有錯但是比較片面,它只是Handler的一種使用場景。
Handler的主要作用是將一個任務切換到某一個指定的線程中去執行。

Handler的運行需要底層的MessageQueue和Looper的支撐。
MessageQueue叫做消息隊列,是用鏈表來實現的一個存儲消息的列表。
Looper是混喚起,會以無限循環的形式查找消息。線程默認沒有Looper,需要手動為其創建,但是主線程即UI線程,會在創建的時候自動初始化一個Looper。
ThreadLocal是Looper中的一個特殊概念,它不是線程而是一種數據載體,它可以互不干擾的為每一個線程存儲/提供數據。

由于Android的UI控件是線程不安全的,多線程并發訪問會導致控件處于不可預計的狀態,但若加線程鎖,又會導致其訪問邏輯變得復雜并且低效,得不償失。所以Android在設計上,規定訪問UI線程只能在主線程中進行,并且在ViewRootImpl接口中對UI操作做了驗證。

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(...);
        }
    }

并且提供了Handler機制,解決在子線程中訪問UI線程的問題。
Handler的使用此處不表,這次的重點是Handler以及其底層類的運作原理。

MessageQueue

MessageQueue,即消息隊列,內部通過一個單鏈表的數據結構來維護消息列表,使用單鏈表而非隊列是因為單鏈表在插入以及刪除操作上比較有優勢。
消息隊列主要有兩個操作,插入和刪除(讀取)。
enqueueMessage是插入方法,向隊列中插入一條消息
next是刪除方法,從消息隊列中取出一條消息,并將其從隊列中刪除。并且next方法是一個無限循環的方法,如果消息隊列中沒有消息,那么next方法會被阻塞,直到有新消息出現。

ThreadLocal

介紹Looper之前必須先介紹ThreadLocal這個線程內部的數據存儲類。

ThreadLocal也叫線程本地變量,它的特性是,它可以互不干擾的為每一個線程存儲/提供數據,使用的時候會對線程自身的內存進行操作,并且提供了get/set方法來訪問。簡單來說ThreadLocal可以為不同的線程提供不同的數據副本。

白話有點抽象,舉個例子:


    private ThreadLocal<String> stringThreadLocal;
    stringThreadLocal = new ThreadLocal<>();
    stringThreadLocal.set("main");
    Log.e(TAG, "run: " + stringThreadLocal.get());

    new Thread(new Runnable() {
        @Override
        public void run() {
            stringThreadLocal.set("Thread1");
            Log.e(TAG, "run: " + stringThreadLocal.get());
        }
    }).start();

    new Thread(new Runnable() {
        @Override
        public void run() {
            Log.e(TAG, "run: " + stringThreadLocal.get());
        }
    }).start();
12-26 16:25:57.208 19506-19506/com.zx.studyapp E/WindowActivity: run: main
12-26 16:25:57.209 19506-19800/com.zx.studyapp E/WindowActivity: run: Thread1
12-26 16:25:57.210 19506-19801/com.zx.studyapp E/WindowActivity: run: null

ThreadLocal底層也是用Map實現,可以近似的理解為,此Map的key是線程標識,value就是當前線程所對應的值。

ThreadLocal的這個特性,正好滿足Handler中Looper的需求:Looper的作用域是當前線程,并且不同的線程擁有不同的Looper。

Looper

Looper 就是循環器,它會以無限循環的形式,向MessageQueue中查找新消息,有新消息就立即處理,否則就阻塞。
Handler工作的時候需要Looper循環器的參與,但是除了主線程,其他線程不會默認初始化一個Looper供我們使用,這時候就需要手動創建一個Looper。

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();//創建一個Looper
        Handler handler = new Handler();
        Looper.loop();//開啟消息循環
    }
}).start();

Looper的loop方法中有一個死循環,用來讀取MessageQueue中的新消息。
Looper除了prepare方法之外,還提供了prepareMainLooper方法為主線程創建Looper使用,本質上還是prepare方法。
此外,Looper還提供了兩個方法來退出消息循環,分別是quit和quitSafely,他們的區別是:
quit會直接退出Looper;
quitSafely會設定一個標記,當隊列中已有的消息全部處理完成后,才會退出。
Looper退出后,Handler發送的消息就會失敗,即send方法返回false。在子線程中,應該在所有任務處理完成后立即調用quit方法來退出消息循環,否則這個子線程就會持續處于等待狀態,無法終止。

Handler

Handler主要工作是發送和接受消息。
Handler有一個特殊的構造方法,是通過特定的Looper來構造,這個方法會在默認構造方法中被調用,所以如果當前線程沒有Looper的話,就會拋出無法創建Handler的異常。
Handler發送主要通過post或者send方法來實現(最終都是通過send實現)。Handler發送消息的過程,實質上就是向消息隊列中插入一條數據。
Handler.send在子線程發送了一條消息,之后Looper的loop方法不再阻塞,通過MessageQueue.next獲取了新消息,接著通過msg.targer.dispatchMessage方法,將其傳遞給Looper所在線程的Handlder,完成了一次消息傳遞。
如下圖:


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

推薦閱讀更多精彩內容