學(xué)習(xí)內(nèi)容
- Android 的消息機制
- Handler 即其底層支撐
原文開篇部分:
- 從開發(fā)角度來說,Handler 是 Android 消息機制的上層接口,通過它可以將任務(wù)切換到 Handler 所在的線程中執(zhí)行。
- 更新 UI 僅僅是 Handler 的一個特殊的使用場景。本質(zhì)不是專門用來更新 UI,只是常被用來這么做而已。
- Andorid 的消息機制主要指 Handler 的運行機制,Handler 的運行底層的 MessageQueue 和 Looper 的支撐。
- MessageQueue(消息隊列):采用單鏈表的數(shù)據(jù)結(jié)構(gòu)存儲消息列表,以隊列的形式對外提供插入和刪除的工作。只負責(zé)存儲,不管處理。
- Looper 負責(zé)處理消息:以無限循環(huán)的形式查找是否有新消息,有則處理,無則等待。
- ThreadLocal 可以在把不同的線程中互不干擾的存儲并提供數(shù)據(jù),通過它來獲取每個線程中的 Looper。
- 線程默認沒有 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.使用場景
- 存儲以線程為作用于并且不同縣城具有不同的數(shù)據(jù)副本的數(shù)據(jù)
- 如:通過 ThreadLocal 存儲不同線程中的 Looper,供 Handler 獲取當(dāng)前線程的 Looper
- 復(fù)雜邏輯下的對象傳遞,比如監(jiān)聽器的傳遞。
- 復(fù)雜邏輯:比如 函數(shù)調(diào)用棧比較深以及代碼入口的多樣性。
- 采用 ThreadLoacl 讓監(jiān)聽器作為線程內(nèi)的全局對象存在,而線程內(nèi)部只要通過 get 方法就可以獲取到監(jiān)聽器。
3.原理
ThreadLocal 是一個泛型類,定義為 public class ThreadLocal<T>,核心在于 ThreadLocal 的 get 和 set 方法。
-
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 中。(和書中有所差異,并非憑借索引位置)
-
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 ,返回之。
-
小結(jié)
- 操作對象是當(dāng)前線程的 ThreadLocalMap 內(nèi)的 private Entry[] table 數(shù)組,讀寫僅限于各自線程的內(nèi)部,
- 因此多個線程可以互不干擾的存儲和修改數(shù)據(jù)。
消息隊列的工作原理
1.基本介紹
- 消息隊列在 Android 中指的是 MessageQueue,內(nèi)部通過一個單鏈表的數(shù)據(jù)結(jié)構(gòu)來維護消息列表(方便插入和刪除)
2. 原理
- enqueueMessage 方法:向消息隊列中插入一條消息。
- next 方法:無限循環(huán)的方法。從消息隊列中取出一條消息并將其從消息隊列中移除。如果消息隊列中沒有消息,那么 next 方法會一直阻塞。
Loop 的工作原理
1.基本介紹
- Looper 在 Android 的消息機制中扮演著消息循環(huán)的角色,具體來說就是它會不斷的從 MessageQueue 中查看是否有新消息,如果有新消息就會立刻處理,否則就會一直阻塞在那里。
- Handler 的工作需要 Looper,沒有 Looper 的線程會報錯
2.原理
- 構(gòu)造方法:創(chuàng)建一個 MessageQueue,然后保存當(dāng)前線程的對象。
- 通過 Looper.prepare() 為當(dāng)前線程創(chuàng)建一個 Looper,然后就可以創(chuàng)建 Handler 的對象了,接著通過 Looper.loop() 方法來開啟消息循環(huán)。
- 除 prepare 外,還有一個 prepareMainLooper 方法,主要是給主線程 ActivityThread 創(chuàng)建 Looper 使用的,本質(zhì)也是通過 prepare 方法實現(xiàn)的。對應(yīng)的,系統(tǒng)提供 getMainLooper 方法來在任意位置獲取主線程的 Looper。
- Looper 終止:
- quit 方法:直接退出 Looper
- quitSafely 方法:設(shè)定退出標(biāo)記,當(dāng)消息隊列中的已有消息處理完畢后安全地退出。
- Looper 退出后,通過 Handler 發(fā)送的消息會失敗,此時 Handler 的 send 方法會返回 false。子線程中,如果手動創(chuàng)建了 Looper,那么所有事情做完后,應(yīng)當(dāng)調(diào)用 quit 方法來終止消息循環(huán),否則這個子線程會一直處于等待的狀態(tài),而如果退出 Looper 以后,這個縣城就會立刻終止,因此 建議不需要的時候終止 Looper。
- Looper.loop 方法
- 調(diào)用了 loop 方法后,消息循環(huán)系統(tǒng)才會 真正的起作用。
- 工作過程:
- 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。
- loop 方法會調(diào)用 MessageQueue 的 next 方法來獲取新消息,當(dāng)沒有信息時,next 會阻塞,這也導(dǎo)致 loop 方法一直阻塞。
- 當(dāng) next 方法返回新消息時,Looper 就會處理這條新消息:msg.target.dispatchMessage(msg),這里的 msg.target 是發(fā)送這條消息的 Handler 對象,這樣 Handler 發(fā)送的消息最終又交給它的 dispatchMessage 方法來處理了。
- Handler 的 dispatchMessage 方法是在創(chuàng)建 Handler 時所使用的 Looper 中執(zhí)行的,這樣就將代碼邏輯切換到制定的線程中了。
Handler 的工作原理
1.原理
Handler 的工作包含消息的發(fā)送和接收過程。
-
消息的發(fā)送:
- 通過 post 的一系列方法和 send 的一系列方法來實現(xiàn),post 相關(guān)方法最終是通過 send 相關(guān)方法來實現(xiàn)的。
- Handler 發(fā)送消息只是向消息隊列中插入一條消息,MessageQueue 的 next 方法就會返回這條信息給 Looper,Looper 收到消息后就會開始處理這條消息,最終消息由 Looper 交由 Looper 處理,即 Handler 的 dispatchMessage 方法會被調(diào)用,此時進入處理消息的階段。
-
消息的處理
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }流程如下:
-
首先,檢查 Message 的 callback 是否為 null,不為null 就通過 handlerCallback 來處理消息。Message 的 callback 是一個 Runnable 對象,實際上就是 Handler 的 post 方法所傳遞的 Runnable 參數(shù)。
private static void handleCallback(Message message) { message.callback.run(); } -
其次,檢查 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 的使用方式。
-
最后,調(diào)用 Handler 的 handlerMessage 方法來處理消息。只是一個空方法,需要我們派生 Handler 子類時,重寫該方法。
public void handleMessage(Message msg) { }
-
-
補充:
-
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)一些特殊的功能”,然而因為我接觸事件尚短,不清楚具體能做些什么。。。
-
默認構(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)模型
- Android 的主線程 ActivityThread 的入口方法 main 中,會通過 Looper.prepareMainLooper 來創(chuàng)建主線程的 Looper 以及 MessageQueue,并通過 Looper.loop 方法來開啟主線程的消息循環(huán)。
- 主線程的消息循環(huán)開始之后,ActivityThread 需要一個 Handler 來和消息隊列進行交互,這個 Handler 就是 ActivityThread.H,內(nèi)部定義了一組消息模型,主要包含四大組件的啟動和停止等過程。
- ActivityThread 通過 ApplicationThread 和 AMS 進行進程間通信,AMS 以進程間通信的方式完成 ActivityThread 的請求后回調(diào) ApplicationThread 中的 Binder 方法,然后 ApplicationThread 會向 H 發(fā)送消息,H 收到消息后會將 ApplicationThread 中的邏輯切換到 ActivityThread 中去執(zhí)行,即切換到主線程中執(zhí)行。
- 以上即為主線程的消息循環(huán)模型。