不同角度看Handler——另類三問
- 2020 年 11 月 20 日
- 筆記
之前有一章節介紹了Handler的常見面試題,今天就來說說另類的,可能你沒關注的其他問題,一起看看吧。
系統為什麼提供Handler
- 這點大家應該都知道一些,就是為了切換執行緒,主要就是為了解決在子執行緒無法訪問UI的問題。
那麼為什麼系統不允許在子執行緒中訪問UI呢?
- 因為
Android的UI控制項不是執行緒安全的,所以採用單執行緒模型來處理UI操作,通過Handler切換UI訪問的執行緒即可。
那麼為什麼不給UI控制項加鎖呢?
- 因為加鎖會讓
UI訪問的邏輯變得複雜,而且會降低UI訪問的效率,阻塞執行緒執行。
Handler是怎麼獲取到當前執行緒的Looper的
- 大家應該都知道
Looper是綁定到執行緒上的,他的作用域就是執行緒,而且不同執行緒具有不同的Looper,也就是要從不同的執行緒取出執行緒中的Looper對象,這裡用到的就是ThreadLocal。
假設我們不知道有這個類,如果要完成這樣一個需求,從不同的執行緒獲取執行緒中的Looper,是不是可以採用一個全局對象,比如hashmap,用來存儲執行緒和對應的Looper?所以需要一個管理Looper的類,但是,執行緒中並不止這一個要存儲和獲取的數據,還有可能有其他的需求,也是跟執行緒所綁定的。所以,我們的系統就設計出了ThreadLocal這種工具類。
ThreadLocal的工作流程是這樣的:我們從不同的執行緒可以訪問同一個ThreadLocal的get方法,然後ThreadLocal會從各自的執行緒中取出一個數組,然後再數組中通過ThreadLocal的索引找出對應的value值。具體邏輯呢,我們還是看看程式碼,分別是ThreadLocal的get方法和set方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
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();
}
首先看看set方法,獲取到當前執行緒,然後取出執行緒中的threadLocals變數,是一個ThreadLocalMap類,然後將當前的ThreadLocal作為key,要設置的值作為value存到這個map中。
get方法就同理了,還是獲取到當前執行緒,然後取出執行緒中的ThreadLocalMap實例,然後從中取到當前ThreadLocal對應的值。
其實可以看到,操作的對象都是執行緒中的ThreadLocalMap實例,也就是讀寫操作都只限制在執行緒內部,這也就是ThreadLocal故意設計的精妙之處了,他可以在不同的執行緒進行讀寫數據而且執行緒之間互不干擾。
畫個圖方便理解記憶:

當MessageQueue 沒有消息的時候,在幹什麼,會佔用CPU資源嗎。
MessageQueue沒有消息時,便阻塞在 loop 的queue.next()方法這裡。具體就是會調用到nativePollOnce方法里,最終調用到epoll_wait()進行阻塞等待。
這時,主執行緒會進行休眠狀態,也就不會消耗CPU資源。當下個消息到達的時候,就會通過pipe管道寫入數據然後喚醒主執行緒進行工作。
這裡涉及到阻塞和喚醒的機制叫做 epoll 機制。
先說說文件描述符和I/O多路復用:
在Linux作業系統中,可以將一切都看作是文件,而文件描述符簡稱fd,當程式打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符,可以理解為一個索引值。
I/O多路復用是一種機制,讓單個進程可以監視多個文件描述符,一旦某個描述符就緒(一般是讀就緒或寫就緒),能夠通知程式進行相應的讀寫操作
所以I/O多路復用其實就是一種監聽讀寫的通知機制,而Linux提供的三種 IO 復用方式分別是:select、poll 和 epoll 。而這其中epoll是性能最好的多路I/O就緒通知方法。
所以,這裡用到的epoll其實就是一種I/O多路復用方式,用來監控多個文件描述符的I/O事件。通過epoll_wait方法等待I/O事件,如果當前沒有可用的事件則阻塞調用執行緒。
拜拜
今天就說這麼多了,感興趣的朋友也可以繼續深究下去,比如epoll為什麼是性能最好的I/O多路復用方法?Handler在App啟動流程中涉及到了哪些功能?等等。有機會再和大家聊聊~
有一起學習的小夥伴可以關注下❤️我的公眾號——碼上積木,每天剖析一個知識點,我們一起積累知識。

