Android 開發(fā)藝術(shù)探索筆記之十 -- Android 的消息機制

學(xué)習(xí)內(nèi)容

  • Android 的消息機制
  • Handler 即其底層支撐

原文開篇部分:

  1. 從開發(fā)角度來說,Handler 是 Android 消息機制的上層接口,通過它可以將任務(wù)切換到 Handler 所在的線程中執(zhí)行。
  2. 更新 UI 僅僅是 Handler 的一個特殊的使用場景。本質(zhì)不是專門用來更新 UI,只是常被用來這么做而已。
  3. Andorid 的消息機制主要指 Handler 的運行機制,Handler 的運行底層的 MessageQueueLooper 的支撐。
    1. MessageQueue(消息隊列):采用單鏈表的數(shù)據(jù)結(jié)構(gòu)存儲消息列表,以隊列的形式對外提供插入和刪除的工作。只負責(zé)存儲,不管處理。
    2. Looper 負責(zé)處理消息:以無限循環(huán)的形式查找是否有新消息,有則處理,無則等待。
    3. ThreadLocal 可以在把不同的線程中互不干擾的存儲提供數(shù)據(jù),通過它來獲取每個線程中的 Looper。
  4. 線程默認沒有 Looper,因此如果需要使用 Handler 必須先為線程創(chuàng)建 Looper。UI 線程(ActivityThread)創(chuàng)建時會初始化 Looper,因此默認可以在主線程中使用 Handler。

Android 的消息機制概述

1.Handler 的作用:

  • 將一個任務(wù)切換到某個制定的線程中執(zhí)行。

2.為什么提供上述 Handler 的功能?

  • Android 規(guī)定訪問 UI 只能在主線程中執(zhí)行,如果在子線程中訪問 UI,那么程序會拋出異常。
  • 以上限制,源碼中 ViewRootImpl 對 UI 操作進行驗證,通過 chearThread 方法實現(xiàn)。
  • 同時由于 Android 的限制,導(dǎo)致必須在主線程中訪問 UI,但是如果在主線程中進行耗時操作,可能會導(dǎo)致 ANR,因此。系統(tǒng)提供 Handler,主要原因就是為了解決在子線程中無法訪問 UI 的矛盾。(子線程進行耗時操作,通過 Handler 訪問 UI)

3. Android 為什么不允許子線程訪問 UI?

  • 核心點:Andorid 的 UI 控件并非線程安全。
  • 通常方法及其問題:
    • 對 UI 控件的訪問加入*上鎖機制
    • 問題:使 UI 訪問的邏輯變得復(fù)雜;鎖機制會降低 UI 訪問的效率,因為鎖機制會阻塞某些線程的執(zhí)行。
  • 解決方案:
    • 采用單線程模型來處理 UI 操作
    • 通過 Handler 切換 UI 訪問的執(zhí)行線程即可

4.Handler 的工作原理:

  • Handler 創(chuàng)建時會采用當(dāng)前線程的 Looper 來構(gòu)建內(nèi)部的消息循環(huán)系統(tǒng),當(dāng)前線程沒有 Looper 會報錯
  • 創(chuàng)建完畢后,通過 Handler 的 post 方法將 Runnable 投遞到 Handler 內(nèi)部的 Looper 中去處理;或者通過 Handler 的 send 方法發(fā)送消息,該消息也會在 Looper 中處理。實際 post 最后也會調(diào)用 send 方法。
  • send 方法的工作過程:調(diào)用 send 方法后,它會調(diào)用 MessageQueue 的 enqueueMessage 方法將該消息放入消息隊列中,然后 Looper 發(fā)現(xiàn)新消息后,會處理這個消息,最終消息中的 Runnable 或者 Handler 的 handleMessage 方法就會被調(diào)用。
    • 注意:Looper 運行在創(chuàng)建 Handler 所在的線程中,這樣一來 Handler 中的業(yè)務(wù)邏輯就被切換到創(chuàng)建 Handler 所在的線程中去了。

Android 的消息機制分析

ThreadLocal 的工作原理

1.基本定義

  • ThreadLocal 是一個線程內(nèi)部的數(shù)據(jù)存儲類,通過它可以在指定線程中存儲數(shù)據(jù),數(shù)據(jù)存儲后,只有在制定線程中可以獲取到存儲的數(shù)據(jù),而其他線程則無法獲取到數(shù)據(jù)。
  • why ?(為什么通過 ThreadLocal 可以在不同的線程中維護一套數(shù)據(jù)的副本并且彼此互不打擾?)
    • (簡單說明) 不同線程訪問同一個 ThreadLocal 的 get 方法,ThreadLocal 內(nèi)部會從各自的線程中取出一個數(shù)組,然后再從數(shù)組中根據(jù)當(dāng)前 ThreadLocal 的索引去查找對應(yīng)的 value 值。顯然,不同線程中的數(shù)組是不同的,這也就是原因所在。

2.使用場景

  1. 存儲以線程為作用于并且不同縣城具有不同的數(shù)據(jù)副本的數(shù)據(jù)
    1. 如:通過 ThreadLocal 存儲不同線程中的 Looper,供 Handler 獲取當(dāng)前線程的 Looper
  2. 復(fù)雜邏輯下的對象傳遞,比如監(jiān)聽器的傳遞。
    1. 復(fù)雜邏輯:比如 函數(shù)調(diào)用棧比較深以及代碼入口的多樣性。
    2. 采用 ThreadLoacl 讓監(jiān)聽器作為線程內(nèi)的全局對象存在,而線程內(nèi)部只要通過 get 方法就可以獲取到監(jiān)聽器。

3.原理

  1. ThreadLocal 是一個泛型類,定義為 public class ThreadLocal<T>,核心在于 ThreadLocal 的 get 和 set 方法。

  2. set 方法:

    ? (lz這里查看了一下現(xiàn)版本的源碼,發(fā)現(xiàn)和書中有些許差異)

    public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    

    ? 雖說和書中源碼有出入,但是大體思想是一致的:通過一個結(jié)構(gòu) ThreadLocalMap 存儲線程的 ThreadLocal 數(shù)據(jù)。

    ? ThreadLocalMap 部分代碼如下:

    static class ThreadLocalMap {
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
        
        private Entry[] table;
       
        //...
    

    ? 內(nèi)部的這個 private Entry[] table 數(shù)組即用來存儲 ThreadLocal 的值。

    ? 回到上面 ThreadLocal 的 set 方法,如果ThreadLocalMap 為空,則創(chuàng)建該線程的 ThreadLocalMap 對象,并將 ThreadLocal 的值存儲。如果非空,則直接通過 ThreadLocalMap.set 方法存儲ThreadLocal 的值,ThreadLocalMap.set 的源碼如下:

    private void set(ThreadLocal<?> key, Object value) {
    
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);
    
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    ThreadLocal<?> k = e.get();
    
                    if (k == key) {
                        e.value = value;
                        return;
                    }
    
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }
    

    ? ThreadLocal 的值直接存儲在 Entry.key 匹配的 Entry.value 中。(和書中有所差異,并非憑借索引位置)

  3. set 方法介紹完,下面介紹 get 方法:

    public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    

    ? get 方法實際就獲取 ThreadLocalMap 對象,如果非空,則調(diào)用 ThreadLocalMap.getEntry 方法,得到目標(biāo) Entry 對象,該 Entry 對象的 value 字段即為 ThreadLocal 值。如果 ThreadLocalMap 為空,那么返回初始值,該初始值可通過重寫 initialValue 方法更改。

    ? ThreadLocalMap.getEntry 方法如下:

    private Entry getEntry(ThreadLocal<?> key) {
                int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                if (e != null && e.get() == key)
                    return e;
                else
                    return getEntryAfterMiss(key, i, e);
            }
    

    ? 可以看到,邏輯很清晰,取出 table 數(shù)組,并找出 ThreadLocal 的引用對象所在的 Entry ,返回之。

  4. 小結(jié)

    1. 操作對象是當(dāng)前線程的 ThreadLocalMap 內(nèi)的 private Entry[] table 數(shù)組,讀寫僅限于各自線程的內(nèi)部,
    2. 因此多個線程可以互不干擾的存儲和修改數(shù)據(jù)。

消息隊列的工作原理

1.基本介紹

  • 消息隊列在 Android 中指的是 MessageQueue,內(nèi)部通過一個單鏈表的數(shù)據(jù)結(jié)構(gòu)來維護消息列表(方便插入和刪除)

2. 原理

  1. enqueueMessage 方法:向消息隊列中插入一條消息。
  2. next 方法:無限循環(huán)的方法。從消息隊列中取出一條消息并將其從消息隊列中移除。如果消息隊列中沒有消息,那么 next 方法會一直阻塞。

Loop 的工作原理

1.基本介紹

  • Looper 在 Android 的消息機制中扮演著消息循環(huán)的角色,具體來說就是它會不斷的從 MessageQueue 中查看是否有新消息,如果有新消息就會立刻處理,否則就會一直阻塞在那里。
  • Handler 的工作需要 Looper,沒有 Looper 的線程會報錯

2.原理

  1. 構(gòu)造方法:創(chuàng)建一個 MessageQueue,然后保存當(dāng)前線程的對象。
  2. 通過 Looper.prepare() 為當(dāng)前線程創(chuàng)建一個 Looper,然后就可以創(chuàng)建 Handler 的對象了,接著通過 Looper.loop() 方法來開啟消息循環(huán)。
  3. 除 prepare 外,還有一個 prepareMainLooper 方法,主要是給主線程 ActivityThread 創(chuàng)建 Looper 使用的,本質(zhì)也是通過 prepare 方法實現(xiàn)的。對應(yīng)的,系統(tǒng)提供 getMainLooper 方法來在任意位置獲取主線程的 Looper。
  4. Looper 終止:
    1. quit 方法:直接退出 Looper
    2. quitSafely 方法:設(shè)定退出標(biāo)記,當(dāng)消息隊列中的已有消息處理完畢后安全地退出。
    3. Looper 退出后,通過 Handler 發(fā)送的消息會失敗,此時 Handler 的 send 方法會返回 false。子線程中,如果手動創(chuàng)建了 Looper,那么所有事情做完后,應(yīng)當(dāng)調(diào)用 quit 方法來終止消息循環(huán),否則這個子線程會一直處于等待的狀態(tài),而如果退出 Looper 以后,這個縣城就會立刻終止,因此 建議不需要的時候終止 Looper。
  5. Looper.loop 方法
    1. 調(diào)用了 loop 方法后,消息循環(huán)系統(tǒng)才會 真正的起作用。
    2. 工作過程:
      1. loop 方法是一個死循環(huán),當(dāng)且僅當(dāng) MessageQueue.next 方法返回了 null 時,跳出循環(huán)。當(dāng) Looper 的 quit 方法被調(diào)用時,Looper 就會調(diào)用 MessageQueue 的 quit 或者 quitSafely 方法來通知消息隊列退出,此時,next 的方法就會返回 null。
      2. loop 方法會調(diào)用 MessageQueue 的 next 方法來獲取新消息,當(dāng)沒有信息時,next 會阻塞,這也導(dǎo)致 loop 方法一直阻塞。
      3. 當(dāng) next 方法返回新消息時,Looper 就會處理這條新消息:msg.target.dispatchMessage(msg),這里的 msg.target 是發(fā)送這條消息的 Handler 對象,這樣 Handler 發(fā)送的消息最終又交給它的 dispatchMessage 方法來處理了。
      4. Handler 的 dispatchMessage 方法是在創(chuàng)建 Handler 時所使用的 Looper 中執(zhí)行的,這樣就將代碼邏輯切換到制定的線程中了。

Handler 的工作原理

1.原理

Handler 的工作包含消息的發(fā)送和接收過程。

  1. 消息的發(fā)送:

    1. 通過 post 的一系列方法和 send 的一系列方法來實現(xiàn),post 相關(guān)方法最終是通過 send 相關(guān)方法來實現(xiàn)的。
    2. Handler 發(fā)送消息只是向消息隊列中插入一條消息,MessageQueue 的 next 方法就會返回這條信息給 Looper,Looper 收到消息后就會開始處理這條消息,最終消息由 Looper 交由 Looper 處理,即 Handler 的 dispatchMessage 方法會被調(diào)用,此時進入處理消息的階段。
  2. 消息的處理

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

    流程如下:

    1. 首先,檢查 Message 的 callback 是否為 null,不為null 就通過 handlerCallback 來處理消息。Message 的 callback 是一個 Runnable 對象,實際上就是 Handler 的 post 方法所傳遞的 Runnable 參數(shù)。

      private static void handleCallback(Message message) {
              message.callback.run();
          }
      
    2. 其次,檢查 mCallback 是否為 null,不為 null 就調(diào)用 mCallback 的 handleMessage 方法來處理消息。

      public interface Callback {
              /**
               * @param msg A {@link android.os.Message Message} object
               * @return True if no further handling is desired
               */
              public boolean handleMessage(Message msg);
          }
      

      通過 Callback 可以如下方式創(chuàng)建 Handler 對象:Handler handler = new Handler(callback)。這樣做的意義在于提供另外一種無需派生 Handler 子類并重寫其 handlerMessage 方法處理具體消息的 Handler 的使用方式。

    3. 最后,調(diào)用 Handler 的 handlerMessage 方法來處理消息。只是一個空方法,需要我們派生 Handler 子類時,重寫該方法。

      public void handleMessage(Message msg) {
          }
      
  3. 補充:

    1. Handler 還有一個特殊的構(gòu)造方法,通過一個特定的 Looper 來構(gòu)造 Handler

      public Handler(Looper looper) {
              this(looper, null, false);
          }
      
      public Handler(Looper looper, Callback callback, boolean async) {
              mLooper = looper;
              mQueue = looper.mQueue;
              mCallback = callback;
              mAsynchronous = async;
          }
      

      原文中說“通過這個構(gòu)造方法可以實現(xiàn)一些特殊的功能”,然而因為我接觸事件尚短,不清楚具體能做些什么。。。

    2. 默認構(gòu)造方法中對 Looper 對象進行判斷,如果為空的話,那么就會報這個常見的異常"Can't create handler inside thread that has not called Looper.prepare()"。

      public Handler() {
              this(null, false);
          }
      
      
      public Handler(Callback callback, boolean async) {
              
              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;
          }
      

主線程的消息循環(huán)

消息循環(huán)模型

  1. Android 的主線程 ActivityThread 的入口方法 main 中,會通過 Looper.prepareMainLooper 來創(chuàng)建主線程的 Looper 以及 MessageQueue,并通過 Looper.loop 方法來開啟主線程的消息循環(huán)。
  2. 主線程的消息循環(huán)開始之后,ActivityThread 需要一個 Handler 來和消息隊列進行交互,這個 Handler 就是 ActivityThread.H,內(nèi)部定義了一組消息模型,主要包含四大組件的啟動和停止等過程。
  3. ActivityThread 通過 ApplicationThread 和 AMS 進行進程間通信,AMS 以進程間通信的方式完成 ActivityThread 的請求后回調(diào) ApplicationThread 中的 Binder 方法,然后 ApplicationThread 會向 H 發(fā)送消息,H 收到消息后會將 ApplicationThread 中的邏輯切換到 ActivityThread 中去執(zhí)行,即切換到主線程中執(zhí)行。
  4. 以上即為主線程的消息循環(huán)模型。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容