[TOC]
## 什么是 Handler?
Handler 是 Android 的一種消息處理機制,與 Looper,MessageQueue 綁定,可以用來進行線程的切換。常用于接收子線程發送的數據并在主線程中更新 UI
## Handler線程通信的原理
> 你剛說 Handler 可以切換線程,它是怎么實現的?
“切換線程”其實是“線程通信”的一種。為了保證主線程不被阻塞,我們常常需要在子線程執行一些耗時任務,執行完畢后通知主線程作出相應的反應,這個過程就是線程間通信。
Linux 里有一種進程間通信的方式叫消息隊列,簡單來說當兩個進程想要通信時,一個進程將消息放入隊列中,另一個進程從這個隊列中讀取消息,從而實現兩個進程的通信。

Handler 就是基于這一設計而實現的。在 Android 的多線程中,每個線程都有一個自己的消息隊列,線程可以開啟一個死循環不斷地從隊列中讀取消息。
當 B 線程要和 A 線程通信時,只需要往 A 的消息隊列中發送消息,A 的事件循環就會讀取這一消息從而實現線程間通信

### 事件循環和消息隊列的實現(Looper MessageQueue)
Android 的事件循環和消息隊列是通過 Looper 類來實現的
Looper.prepare() 是一個靜態方法。它會構建出一個 Looper,同時創建一個 MessageQueue 作為 Looper 的成員變量。MessageQueue 是存放消息的隊列
當調用 Looper.loop() 方法時,會在線程內部開啟一個死循環,不斷地從 MessageQueue 中讀取消息,這就是事件循環
每個 Handler 都與一個 Looper 綁定,Looper 包含 MessageQueue

### Looper 被存放在ThreadLocal
Looper 是存放在線程中的。但如何把 Looper 存放在線程中就引入了 Android 消息機制的另一個重點 --- **ThreadLocal**
前面我們提到。Looper.prepare() 方法會創建出一個 Looper,它其實還做了一件事,就是將 Looper 放入線程的局部變量 ThreadLocal 中。
~~~
// Looper.java#private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// sThreadLocal是一個靜態對象,類型是ThreadLocal<Looper>
sThreadLocal.set(new Looper(quitAllowed));
}
~~~
### 什么是 ThreadLocal
ThreadLocal 又稱線程的局部變量。它最大的神奇之處在于,**一個 ThreadLocal 實例在不同的線程中調用 get 方法可以取出不同的值。** 用一個例子來表示這種用法:
~~~
public void set(T value) {
// ① 獲取當前線程對象
Thread t = Thread.currentThread();
// ② 獲取線程的成員屬性map
ThreadLocalMap map = getMap(t);
// ③ 將value放入map中,如果map為空則創建map
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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;
~~~
**ThreadLocal.set 可以將一個實例變成線程的成員變量**
因為 Looper 要放在線程中的,每個線程只需要一個事件循環,只需要一個 Looper。事件循環是個死循環,多余的事件循環毫無意義。ThreadLocal.set 可以將 Looper 設置為線程的成員變量
同時為了方便在不同線程中獲取到 Looper,Android 提供了一個靜態對象 Looper.sThreadLocal。這樣在線程內部調用 sThreadLocal.get 就可以獲取線程對應的 Looper 對象
綜上所述,使用 ThreadLocal 作為 Looper 的設置和獲取工具是十分方便合理噠
## Looper 的這個死循環會一直“空轉”
當然不會!如果事件循環中沒有消息要處理但仍然執行循環,相當于無意義的浪費 CPU 資源!Android 是不允許這樣的
為了解決這個問題,在 MessageQueue 中,有兩個 native 方法,`nativePollOnce` 和 `nativeWake`。
nativePollOnce 表示進行一次輪詢,來查找是否有可以處理的消息,如果沒有就阻塞線程,讓出 CPU 資源
nativeWake 表示喚醒線程
所以這兩個方法的調用時機也就顯而易見了
~~~
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
···
if (needWake) {
nativeWake(mPtr);
}
···
}
~~~
在 MessageQueue 類中,`enqueueMessage` 方法用來將消息入隊,如果此時線程是阻塞的,調用 `nativeWake` 喚醒線程
~~~
// MessageQueue.java
Message next() {
···
nativePollOnce(ptr, nextPollTimeoutMillis);
···
}
復制代碼
~~~
`next()` 方法用來取出消息。取之前調用 `nativePollOnce()` 查詢是否有可以處理的消息,如果沒有則阻塞線程。等待消息入隊時喚醒。
### nativePollOnce 與 nativeWake
在linux新的內核中使用了epoll來替換它,相比于select,epoll最大的好處在于它不會隨著監聽文件描述符數目的增長而效率降低,select機制是采用輪詢來處理的,輪詢的fd數目越多,效率也就越低。epoll的接口非常簡單就只有三個函數:
int epoll_create(int size);創建一個epoll句柄,當這個句柄創建完成之后,在/proc/進程id/fd中可以看到這個fd。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);注冊事件函數。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);等待事件的發生,參數timeout是超時時間毫秒值,0會立即返回,-1將不確定,也就是說有可能永久阻塞。該函數返回需要處理的事件數目,如返回0表示已超時。
### Looper 是個死循環,為什么不會導致 ANR
首先要明確一下概念。**ANR 是應用在特定時間內無法響應一個事件時拋出的異常。**
典型例子的是在主線程中執行耗時任務。當一個觸摸事件來臨時,主線程忙于處理耗時任務而無法在 5s 內響應觸摸事件,此時就會拋出 ANR。
但 Looper 死循環是事件循環的基石,本身就是 Android 用來處理一個個事件的。正常情況下,觸摸事件會加入到這個循環中被處理。但如果前一個事件太過耗時,下一個事件等待時間太長超出特定時間,這時才會產生 ANR。所以 Looper 死循環并不是產生 ANR 的原因。
## 消息隊列中的消息是如何進行排序
這個就要看 MessageQueue 的 enqueueMessage 方法了
enqueueMessage 是消息的入隊方法。Handler 在進行線程間通信時,會調用 sendMessage 將消息發送到接收消息的線程的消息隊列中,消息隊列調用 enqueueMessage 將消息入隊。
~~~
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
// ① when是消息入隊的時間
msg.when = when;
// ② mMessages是鏈表的頭指針,p是哨兵指針
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
// ③ 遍歷鏈表,比較when找到插入位置
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// ④ 將msg插入到鏈表中
msg.next = p;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
~~~
消息入隊分為 3 步:
① 將入隊的時間綁定在 when 屬性上
② 遍歷鏈表,通過比較 when 找到插入位置
③ 將 msg 插入到鏈表中
這就是消息的排序方式
## 異步消息和同步屏障
在 Android 的消息機制中,消息分為**同步消息**、**異步消息**和**同步屏障**三種。(沒錯,同步屏障是 target 屬性為 null 的特殊消息)。通常我們調用 sendMessage 方法發送的是同步消息。異步消息需要和同步屏障配合使用,來提升消息的優先級。
同步屏障理解起來其實很簡單。剛才說同步屏障是一種特殊的消息,當事件循環檢測到同步屏障時,之后的行為不再像之前那樣根據 when 的值一個個取消息,而是遍歷整個消息隊列,查找到異步消息取出并執行。
這個特殊的消息在消息隊列中像一個標志,事件循環探測到它時就改變原來的行為,轉而去查找異步消息。表現上看起來像一個屏障一樣攔住了同步消息。所以形象地稱為同步屏障。
源碼實現非常非常簡單:
~~~
//MessageQueue.java
Message next() {
···
// ① target為null表明是同步屏障
if (msg != null && msg.target == null) {
// ② 取出異步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
···
}
~~~
### 同步屏障,不移除,會發生什么事呢?
同步屏障是用來“攔住”同步消息,處理異步消息的。如果同步屏障不移除,消息隊列里的異步消息會一個一個被取出處理,直到異步消息被取完。如果此時隊列中沒有異步消息了,則線程會阻塞,隊列中的同步消息永遠不會執行。所以同步屏障要及時移除。
### 那你知道同步屏障的應用場景
同步屏障的核心作用是提高消息優先級,保證 Message 被優先處理。Android 為了避免卡頓,應用在了 view 繪制中。具體可以看之前關于 view 繪制的總結~
## Handler相關的內存泄漏問題
內存泄漏歸根到底其實是生命周期“錯位”導致的:**一個對象本來應該在一個短的生命周期中被回收,結果被一個長生命周期的對象引用,導致無法回收。** Handler 的內存泄漏其實是內部類持有外部類引用導致的。 形成方式有兩種:
(1)匿名內部類持有外部類引用
~~~
class Activity {
var a = 10
fun postRunnable() {
Handler(Looper.getMainLooper()).post(object : Runnable {
override fun run() {
this@Activity.a = 20
}
})
}
}
~~~
Handler 在發送消息時,message.target 屬性就是 handler 本身。message 被發送到消息隊列中,被線程持有,線程是一個無比“長”生命周期的對象,導致 activity 無法被及時回收從而引起內存泄漏。
解決辦法是在 activity destory 時及時移除 runnable
(2)非靜態內部類持有外部類引用
~~~
//非靜態內部類
protected class AppHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
}
}
}
~~~
解決方案是用靜態內部類,并將外部引用改為弱引用
~~~
private static class AppHandler extends Handler {
//弱引用,在垃圾回收時,被回收
WeakReference<Activity> activity;
AppHandler(Activity activity){
this.activity = new WeakReference<Activity>(activity);
}
public void handleMessage(Message message){
switch (message.what){
}
}
}
~~~
## HandlerThread
HandlerThread 顧名思義就是 Handler+Thread 的結合體,它本質上是一個 Thread。
我們知道,子線程是需要我們通過 Looper.prepare()和 Looper.loop()手動開啟事件循環的。HandlerThread 其實就幫我們做了這件事,它是一個實現了事件循環的線程。我們可以在這個線程中做一些 IO 耗時操作。
## IdleHandler
IdleHandler 雖然叫 Handler,其實和同步屏障一樣是一種特殊的”消息"。不同于 Message,它是一個接口
~~~
public static interface IdleHandler{
boolean queueIdle();
}
復制代碼
~~~
Idle 是空閑的意思。與同步屏障不同,同步屏障是提高異步消息的優先級使其優先執行,IdleHandler 是事件循環出現空閑的時候來執行。
這里的“空閑”主要指兩種情況
(1)消息隊列為空
(2)消息隊列不為空但全部是延時消息,也就是 msg.when > now
利用這一特性,我們可以將一些不重要的初始化操作放在 IdleHandler 中執行,以此加快 app 啟動速度;由于 View 的繪制是事件驅動的,我們也可以在主線程的事件循環中添加一個 IdleHandler 來作為 View 繪制完成的回調,等等。 但應該注意的是,如果主線程中一直有任務執行,IdleHandler 被執行的時機會無限延后,使用的時候要注意哦~
## 參考資料
[【面試官爸爸】繼續給我講講Handler?](https://juejin.cn/post/6995341995015143432)
- Android
- 四大組件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介紹
- MessageQueue詳細
- 啟動流程
- 系統啟動流程
- 應用啟動流程
- Activity啟動流程
- View
- view繪制
- view事件傳遞
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大數據
- Binder小結
- Android組件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 遷移與修復
- Sqlite內核
- Sqlite優化v2
- sqlite索引
- sqlite之wal
- sqlite之鎖機制
- 網絡
- 基礎
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP進化圖
- HTTP小結
- 實踐
- 網絡優化
- Json
- ProtoBuffer
- 斷點續傳
- 性能
- 卡頓
- 卡頓監控
- ANR
- ANR監控
- 內存
- 內存問題與優化
- 圖片內存優化
- 線下內存監控
- 線上內存監控
- 啟動優化
- 死鎖監控
- 崩潰監控
- 包體積優化
- UI渲染優化
- UI常規優化
- I/O監控
- 電量監控
- 第三方框架
- 網絡框架
- Volley
- Okhttp
- 網絡框架n問
- OkHttp原理N問
- 設計模式
- EventBus
- Rxjava
- 圖片
- ImageWoker
- Gilde的優化
- APT
- 依賴注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 協程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 運行期Java-hook技術
- 編譯期hook
- ASM
- Transform增量編譯
- 運行期Native-hook技術
- 熱修復
- 插件化
- AAB
- Shadow
- 虛擬機
- 其他
- UI自動化
- JavaParser
- Android Line
- 編譯
- 疑難雜癥
- Android11滑動異常
- 方案
- 工業化
- 模塊化
- 隱私合規
- 動態化
- 項目管理
- 業務啟動優化
- 業務架構設計
- 性能優化case
- 性能優化-排查思路
- 性能優化-現有方案
- 登錄
- 搜索
- C++
- NDK入門
- 跨平臺
- H5
- Flutter
- Flutter 性能優化
- 數據跨平臺