[學習筆記]Android開發藝術探索:Android的消息機制

從開發的角度來說,Handler是Android消息機制的上層接口。Handler的運行需要底層的 MessageQueue 和 Looper 的支撐。

  1. MessageQueue是一個消息隊列,內部存儲了一組消息,以隊列的形式對外提供插入和刪除的工作,內部採用單鏈表的數據結構來存儲消息列表。
  2. Lopper會以無限循環的形式去查找是否有新消息,如果有就處理消息,否則就一直等待着。 線程是默認沒有Looperd的,使用Handler就必須爲線程創建Looper。
  3. ThreadLocal可以在不同線程中互不干擾的存儲並提供數據,通過ThreadLocal可以輕鬆的獲取每個線程的Looper。

我們經常提到的主線程,也叫UI線程,它就是ActivityThread

Android消息機制概述

  1. Handler的主要作用是將某個任務切換到Handler所在的線程中去執行。爲什麼Android要提供這個功能呢?這是因爲Android規定訪問UI只能通過主線程,如果子線程訪問UI,程序會拋出異常;ViewRootImpl在checkThread方法中做了判斷。
  2. 由於Android不建議在主線程進行耗時操作,否則可能會導致ANR。那我們耗時操作在子線程執行完畢後,我們需要將一些更新UI的操作切換到主線程當中去。所以系統就提供了Handler。
  3. 系統爲什麼不允許在子線程中去訪問UI呢?因爲Android的UI控件不是線程安全的,多線程併發訪問可能會導致UI控件處於不可預期的狀態,爲什麼不加鎖?因爲加鎖機制會讓UI訪問邏輯變得複雜;其次鎖機制會降低UI訪問的效率,因爲鎖機制會阻塞某些線程的執行。所以Android採用了高效的單線程模型來處理UI操作。
  4. Handler創建時會採用當前線程的Looper來構建內部的消息循環系統,如果當前線程沒有Looper就會報錯。Handler可以通過post方法發送一個Runnable到消息隊列中,也可以通過send方法發送一個消息到消息隊列中,其實post方法最終也是通過send方法來完成。
  5. MessageQueue的enqueueMessage方法最終將這個消息放到消息隊列中,當Looper發現有新消息到來時,處理這個消息,最終消息中的Runnable或者Handler的handleMessage方法就會被調用,注意Looper是運行Handler所在的線程中的,這樣一來業務邏輯就切換到了Handler所在的線程中去執行了。

Android的消息機制分析

ThreadLocal的工作原理

ThreadLocal是一個線程內部的數據存儲類,通過它可以在指定線程存儲數據,數據存儲後,只能在指定的線程可以獲取到存儲的數據,對於其他線程則無法獲取到數據。一般來說,當數據是以線程作爲作用域並且不同線程有不同副本的時候,就可以考慮使用ThreadLocal。對於Handler來說,它需要獲取當前線程的Looper,而Looper的作用於就是線程並且不同的線程具有不同的Looper,通過ThreadLocal可以輕鬆實現線程中的存取。

ThreadLocal的另一個使用場景是複雜邏輯下的對象傳遞

ThreadLocal原理:不同線程訪問同一個ThreadLoacl的get方法,ThreadLocal的get方法會從各自的線程中取出一個數組,然後再從數組中根據當前ThreadLocal的索引去查找對應的Value值。

ThreadLocal set方法

    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);//通過values方法獲取當前線程中的ThreadLoacl數據——localValues
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

在 localValues 內部有一個數組: private Object[] table ,ThreadLocal的值就存在這 個數組中。

ThreadLocal的值在table數組中的存儲位置總是ThreadLocal的reference字段所標識的對象的下一個位置。

ThreadLocal的get方法:

  public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);//找到localValues對象
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {//找到ThreadLocal的reference對象在table數組中的位置
                return (T) table[index + 1];//reference字段所標識的對象的下一個位置就是ThreadLocal的值
            }
        } else {
            values = initializeValues(currentThread);
        }
        return (T) values.getAfterMiss(this);
    }

從ThreadLocal的set/get方法可以看出,它們所操作的對象都是當前線程的localValues對象 的table數組,因此在不同線程中訪問同一個ThreadLocal的set/get方法,它們對ThreadLocal 的讀/寫操作僅限於各自線程的內部。

消息隊列的工作原理

  1. 消息隊列指的是MessageQueue,主要包含兩個操作:插入和讀取。讀取操作本身會伴隨着刪除操作。

  2. MessageQueue內部通過一個單鏈表的數據結構來維護消息列表,這種數據結構在插入 和刪除上的性能比較有優勢。

  3. 插入和讀取對應的方法分別是: enqueueMessage 和 next 方法。

    i. enqueueMessage()的源碼實現主要操作就是單鏈表的插入操作

    ii. next()的源碼實現也是從單鏈表中取出一個元素的操作,next()方法是一個無限循環的方法,如果消息隊列中沒有消息,那麼next方法會一直阻塞在這裏。當有新消息到 來時,next()方法會返回這條消息並將其從單鏈表中移除。

Looper的工作原理

  1. Looper在Android的消息機制中扮演着消息循環的角色,具體來說就是它會不停地從 MessageQueue中查看是否有新消息,如果有新消息就會立即處理,否則就一直阻塞在那裏。
  2. 通過 Looper.prepare() 方法即可爲當前線程創建一個Looper,再通過 Looper.loop() 開啓消息循環。 prepareMainLooper() 方法主要給主線程也就是ActivityThread創建Looper使用的,本質也是通過prepare方法實現的。
  3. Looper提供quit和quitSafely來退出一個Looper,區別在於quit會直接退出Looper,而 quitSafely會把消息隊列中已有的消息處理完畢後才安全地退出。 Looper退出後,這時候 通過Handler發送的消息會失敗,Handler的send方法會返回false。
  4. 在子線程中,如果手動爲其創建了Looper,在所有事情做完後,應該調用Looper的quit方法來終止消息循環,否則這個子線程就會一直處於等待狀態;而如果退出了Looper以 後,這個線程就會立刻終止,因此建議不需要的時候終止Looper。
  5. loop()方法會調用MessageQueue的 next() 方法來獲取新消息,而next是是一個阻塞操作,沒有信息時,next方法會一直阻塞在那裏,這也導致loop方法一直阻塞在那裏。如果MessageQueue的next方法返回了新消息,Looper就會處理這條消息: msg.target.dispatchMessage(msg) ,這裏的msg.target是發送這條消息的Handler對象,這樣Handler發送的消息最終又交給Handler來處理了。

Handler的工作原理

Handler的工作主要包含消息的發送和接受過程。

發送過程通過post的一系列方法和send的一系列方法來實現。Handler發送過程僅僅是向消息隊列中插入了一條消息。MessageQueue的next方法就會返回這條消息給Looper,Looper拿到這條消息就開始處理,最終消息會交給Handler的dispatchMessage()來處理,這時Handler就進入了處理消息的階段。dispatchMessage()方法運行在Looper所在的線程上。

 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            //Message的callback是一個Runnable,       
           //也就是Handler的 post方法所傳遞的Runnable參數
            handleCallback(msg);
        } else {
            //如果給Handler設置了Callback的實現,
            //則調用Callback的handleMessage(msg)
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //調用Handler的handleMessage方法來處理消息,
            //該Handler子類需重寫handlerMessage(msg)方法
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
        message.callback.run();//直接執行Runnable中的run()方法
    }
    public interface Callback {
        //不想繼承Handler子類,可以通過Callback來實現handleMessage
        public boolean handleMessage(Message msg);
    }
    //默認空實現
    public void handleMessage(Message msg) {
    }

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

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

Handler創建需要Looper,否則會拋出異常,默認獲取當前線程的Looper。主線程也就是ActivityThread會自動創建Looper,其他線程如果需要Looper均需要手動創建。

主線程消息循環

Android的主線程就是ActivityThread,主線程的入口方法爲main,在main方法中系統會通過Looper.prepareMainLooper()來創建主線程的Looper以及MessageQueue,並通過Looper.loop()來開啓主線程的消息循環。

public static void main(String[] args) {
        ... 
        Process.setArgV0("<pre-initialized>");
 
        Looper.prepareMainLooper();//創建主線程的Looper
 
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
 
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        AsyncTask.init();

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();//開啓looper

        throw new RuntimeException("Main thread loop unexpectedly exited");
}

主線程的消息循環開始後,ActivityThread還需要一個Handler來和消息隊列進行交互,這 個Handler就是ActivityTread.H,它內部定義了一組消息類型,主要包含四大組件的啓動和停止過程。

Android主線程的消息循環模型

ActivityThread通過ApplicationThread和AMS進行進程間通信,AMS以進程間通信的方式完成 ActivityThread的請求後會回調ApplicationThread中的Binder方法,然後ApplicationThread會向H發送消息,H收到消息後會將ApplicationThread中的邏輯切換到ActivityTread中去執行, 即切換到主線程中去執行。四大組件的啓動過程基本上都是這個流程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章