努力完善中,,,o(╥﹏╥)o 如果還有遇到別的問題的同學評論區打上,我會補上的,也歡迎糾錯!
如果對Handler原始碼不夠了解可以看看這個:Handler原始碼學習記錄(java層、native層)
模仿Handler原理,使用eventfd+epoll實作Handler基礎功能的小案例 -> gayhub地址(MessageQueueDemo)
1 Handler是什么?
android提供的執行緒切換工具類,主要的作用是通過handler實作從子執行緒切換回主執行緒進行ui重繪操作,
1.1 為什么Handler能實作執行緒切換?
在創建Handler的時候需要傳入目標執行緒的Looper,(沒有傳入Looper默認拿當前執行緒的Looper,如果當前執行緒也沒有準備好Looper會拋例外)
而當sendMessage的時候,會將當前的Handler物件賦值給Message中的target變數,并將該Message存到傳入目標執行緒Looper的MessageQueue中
當Looper消費Message的時候便會拿到Message中的taeget執行dispatchMessage(msg)方法,從而實作執行緒切換,
1.2 為什么主執行緒才能重繪ui,子執行緒不可以?
‘android的ui重繪并不是執行緒安全的’ 所以必須要有一個執行緒專門來做這件事情,那就是主執行緒,
重繪ui的時候會檢查當前執行緒是否為主執行緒,如果不是會拋例外,
view在更新的時候由于可能會發生大小、位置等變化,會執行requestLayout來告訴父View自己要更新layout,
然后父View也會一層層呼叫requestLayout,最終去到ViewRootImpl#requestLayout,在其requestLayout中
會進行執行緒檢查,
//ViewRootImpl#requestLayout
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
//ViewRootImpl#checkThread
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
嚴格來說,子執行緒也不是不可以重繪ui,詳細的文章:https://blog.csdn.net/xyh269/article/details/52728861
1.3 為什么android要設計成只有在主執行緒才能重繪ui?
設計成單執行緒重繪,目的是提高穩定性以及提高性能,
①提高穩定性:由于多執行緒存在資源共享的問題一旦處理不妥當,會造成資料丟失、重復以及錯亂等問題,單執行緒能有效降低出錯風險,提高穩定性,
②從①中引申出,如果要處理好多執行緒的資源共享的問題,就需要增加同步鎖、原子類、執行緒安全的集合等,這樣必然會存在等待,而且由于ui重繪是非常
頻繁的,如果出現大量而又頻繁的等待,會增加cpu的負擔,從而導致性能下降,
以上是個人觀點,可以看看大佬更加全面的文章:https://blog.csdn.net/qq_39154578/article/details/83782287
2 一個執行緒有幾個Handler?
可以創建無數個,但是其內部的Looper只會有一個,
2.1 一個執行緒有幾個Looper?如何保證?
一個執行緒只有一個Looper;
Looper內部是通過ThreadLocal保證的執行緒唯一,在Looper.prepare方法的時候創建并set進ThreadLocal,
2.2 為什么ThreadLocal能保證Looper執行緒唯一?
因為執行緒維護了一個ThreadLocalMap的容器,該容器專門提供給ThreadLocal獲取資料,
key就是ThreadLocal,value就是需要獲取的資料,
ThreadLocal設計思想
① Thread 內部持有一個全域變數ThreadLocalMap<ThreadLocal(弱參考), T (強參考)> threadLocals
② ThreadLocal內部
get() -> 獲取當前thread物件 -> 獲取threadLocals(并且判空,為空就為該thread創建threadLocals)-> map.get(this) 獲取到value
set(T value) -> 獲取當前thread物件 -> 獲取threadLocals(并且判空,為空就為該thread創建threadLocals)-> map.set(this, value)
remove() -> 獲取當前thread物件 -> 獲取threadLocals -> map.remove(this)
3 為什么主執行緒可以new Handler?
主執行緒:
ActivityThread main方法已經幫我們準備好Looper了,
Looper.prepareMainLooper()
Looper.loop() 這個是死回圈,是主執行緒一直存活的關鍵,
所以在主執行緒可以直接new Handler,而且還可以不用穿Looper引數,(沒有傳入Looper默認拿當前執行緒的Looper,如果當前執行緒也沒有準備好Looper會拋例外)
3.1 那么子執行緒中如何使用Handler?
Looper.prepare() 準備Looper
Looper.loop() 讓Looper運行起來
Looper.myLooper() 提供給Handler獲得該執行緒的Looper
由于子執行緒的Looper創建是在prepare()中,無法保證外部的Hander立即能獲得有效的Looper,所以需要做同步鎖操作,
HandlerThread 封裝好了一切同步操作,也可以用它,
3.2 子執行緒維持的Looper,無訊息應該怎么處理?
由于Looper.loop()的存在會一直阻塞執行緒,執行緒是不會退出的,可能會導致記憶體泄漏,
需要手動退出 Looper.quit() -> MessageQueue quit()
MessageQueue quit()作業:清空所有Message,nativeWake喚醒等待,next()繼續執行,dispose()
3.3 為什么主執行緒的Looper不需要退出?
如果主執行緒Looper退出了,整個程式就會退出了,因為整個app程式都是依靠Looper來分發處理訊息,處理生命周期回呼的,
4 Handler記憶體泄漏的原因是什么?
匿名內部類默認會持有外部類的參考,
記憶體泄漏:程式在向系統申請分配記憶體空間后(new),在使用完畢后未釋放,記憶體泄漏容易造成OOM(記憶體溢位),
OOM:我想要使用一個4M的連續空間,但是找不到,系統就會拋出OOM,
MessageQueue維護著所有將要處理的Message,在enqueueMessage的時候,將Handler物件存入Message target變數中,
所有Message的生命有可能會比Handler所在的Activity生命要長,Activity銷毀了,但是Message都還沒執行的話,該Activity
就無法銷毀,導致記憶體泄漏,
5 Handler的訊息阻塞是怎么實作的?
Handler的實作原理是使用了linux的兩個系統呼叫實作的:eventfd + epoll
eventfd 負責通知
epoll 負責監聽
首先會通過eventfd系統呼叫創建一個喚醒fd并且注冊到epoll里邊,
如果當有延時訊息入隊的時候,會根據訊息的延時時長為epoll設定阻塞時長,知道超時epoll自動喚醒,然后回傳java層處理訊息
如果當有立即執行的訊息入隊的時候,會通過寫入資料到喚醒fd從而喚醒epoll,然后回傳java層處理訊息
如果沒有訊息,epoll會一直阻塞,直到被喚醒,
5.1 Looper.loop()會阻塞主執行緒但為什么不會出現ANR?
ANR:應用無回應例外
ANR產生原因:當前的事件沒有機會得到處理
例如:當前在處理一個點擊事件,但是這個點擊事件里邊的處理是耗時的,主執行緒就會等待這個耗時的處理,
與此同時另外一個點擊事件發送過來了,新的事件就會被阻塞,當事件超過某一個時間限制(觸摸事件一般是5s)
仍未被執行,就會拋ANR,
所以ANR與Looper.loop()的阻塞是不相關的,并且Looper.loop()的阻塞是為了保證主執行緒不退出而設計的,
更詳細的文章:https://blog.csdn.net/qq_32583189/article/details/52253147
6 Handler如果讓Message盡快執行執行?
發送異步訊息,
方法:使用Message#setAsynchronous設定
這樣還是不能實作盡快執行的,還需要增加同步屏障(一種特殊的訊息)
但是添加以及移除同步屏障的方法,對開發者是不公開的,所以需要使用反射去設定,
記住:使用完之后必須要移除同步屏障,不然同步屏障后面的所有同步訊息都無法執行的,
同步屏障的作用:攔截同步屏障后面所有同步訊息,只允許異步訊息用過,
使用場景介紹:ViewRoomtImpl中為了讓訊息盡快執行大量使用了異步訊息
//ViewRoomtImpl#scheduleTraversals
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //添加訊息屏障
...
}
}
//ViewRoomtImpl#unscheduleTraversals
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); //移除訊息屏障
...
}
}
7 Handler有沒有一些機制是有助于程式優化的?
IdleHandler:可以在程式空閑的時候,做一些不太耗時的操作,
使用方法:
mHandler.getLooper().getQueue().addIdleHandler
mHandler.getLooper().getQueue().removeIdleHandler
IdleHandler#queueIdle():
回傳值用于告訴MessageQueue
true:每次進入阻塞都會呼叫該IdleHandler,直到該IdleHandler被移除,
false:只呼叫一次IdleHandler,就被自動移除了,
使用場景:
1、Activity啟動優化,onCreate,onStart,onResume中耗時較短但非必要的代碼可以放到IdleHandler中執行,減少啟動時間,
2、想要一個View繪制完成之后添加其他依賴于這個View的View,當然這個View#post()也能實作,區別就是前者會在訊息佇列空閑時執行,
詳細的文章:https://www.jianshu.com/p/1dc73c8ab6a1
8 MessageQueue如何保證執行緒安全?
MessageQueue內部的很多方法為了保證執行緒安全,都增加了物件鎖synchronized (this),
9 Message怎么創建?
Message.obtain():使用復用池,減少new Message,防止頻繁GC,(頻繁GC會導致記憶體抖動(STW) -> 導致卡頓)
設計模式:享元模式(類似例子 recycleview bindView createView)
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/257488.html
標籤:其他
