本文已經收錄到個人Github我的博客,歡迎大佬們光臨寒舍:java
個人GIthub博客git
Handler
的基本概念及使用Handler
?在Android
平臺上,主要用到的通訊機制有兩種:Handler
和Binder
,前者用於進程內部的通訊,後者主要用於跨進程通訊。github
在多線程的應用場景中,Handler
將工做線程中需更新UI
的操做信息 傳遞到 UI
主線程,從而實現工做線程對UI
的更新處理,最終實現異步消息的處理。面試
做爲一個Android
程序猿,知其然而必須知其因此然,理解其源碼能更好地瞭解Handler
機制的原理。下面,我就從消息機制入手,帶你們暢遊在Handler
的世界中,體會Google
工程師的智慧之光。c#
A.做用:跨線程通訊數組
B.經常使用場景:當子線程中進行耗時操做後須要更新UI
時,經過Handler
將有關UI
的操做切換到主線程中執行安全
系統不建議在子線程訪問
UI
的緣由:UI
控件非線程安全,在多線程中併發訪問可能會致使UI
控件處於不可預期的狀態數據結構而不對
UI
控件的訪問加上鎖機制的緣由有:多線程1.上鎖會讓UI控件變得複雜和低效併發
2.上鎖後會阻塞某些進程的執行
C.四要素:
Message
:須要被傳遞的消息,其中包含了消息ID
,消息處理對象以及處理的數據等,由MessageQueue
統一列隊,最終由Handler
處理MessageQueue
:用來存放Handler
發送過來的消息,內部經過單鏈表的數據結構來維護消息列表,等待Looper
的抽取。Handler
:負責Message
的發送及處理
Handler.sendMessage()
:向消息隊列發送各類消息事件Handler.handleMessage()
:處理相應的消息事件
Looper
:經過Looper.loop()
不斷地從MessageQueue
中抽取Message
,按分發機制將消息分發給目標處理者,能夠當作是消息泵
Thread
:負責調度整個消息循環,即消息循環的執行場所存在關係:
- 一個
Thread
只能有一個Looper
,能夠有多個Handler
Looper
有一個MessageQueue
,能夠處理來自多個Handler
的Message
MessageQueue
有一組待處理的Message
,這些Message
可來自不一樣的Handler
Message
中記錄了負責發送和處理消息的Handler
Handler
中有Looper
和MessageQueue
D.使用方法:
ActivityThread
主線程實例化一個全局的Handler
對象UI
操做的子線程裏實例化一個Message
並填充必要數據,調用Handler.sendMessage(Message)
方法發送出去handleMessage()
方法,對不一樣Message
執行相關操做E.整體工做流程:
這裏先整體地說明一下
Android
消息機制的工做流程,具體的ThreadLocal
,MessageQueue
,Looper
,Handler
的工做原理會在下文詳細解析
Handler.sendMessage()
發送消息時,會經過MessageQueue.enqueueMessage()
向MessageQueue
中添加一條消息Looper.loop()
開啓循環後,不斷輪詢調用MessageQueue.next()
Handler.dispatchMessage()
去傳遞消息,目標Handler
收到消息後調用Handler.handleMessage()
處理消息簡單來看,即
Handler
將Message
發送到Looper
的成員變量MessageQueue
中,以後Looper
不斷循環遍歷MessageQueue
從中讀取Message
,最終回調給Handler
處理。如圖:
ThreadLocal
瞭解
ThreadLocal
,有助於咱們後面對Looper
的探究
Q1:ThreadLocal
是什麼
首先咱們來看一下官方源碼(Android 9.0
)
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
大體意思:
ThreadLocal
是一個線程內部的數據存儲類,經過它能夠在指定的線程中存儲數據,只有在指定線程中才能獲取到存儲的數據(也就是說,每一個線程的一個變量,有本身的值)
Q2:ThreadLocal
的使用場景:
Android
中具體的使用場景:Looper
,ActivityThread
,AMS
若是不採用
ThreadLocal
的話,須要採起的措施:提供一個全局哈希表
採用
ThreadLocal
讓監聽器做爲線程中的全局對象,線程內部只有經過get
方法便可獲得監聽器若是不採用
ThreadLocal
的方案:a.將監聽器做爲參數傳遞
缺點:當調用棧很深的時候,程序設計看起來不美觀
b.將監聽器做爲靜態變量
缺點:狀態不具備可擴充性
Q3:ThreadLocal
和synchronized
的區別:
- 對於多線程資源共享的問題,
synchronized
機制採用了「以時間換空間」的方式- 而
ThreadLocal
採用了**「以空間換時間」**的方式- 前者僅提供一份變量,讓不一樣的線程排隊訪問,然後者爲每個線程都提供了一份變量,所以能夠同時訪問而互不影響,因此
ThreadLocal
和synchronized
都能保證線程安全,可是應用場景卻大不同。
Q4:原理
ThreadLocal
主要操做爲set
,get
操做,下面分別介紹流程
A1:set
的原理
A2:get
的原理
綜上所述,ThreadLocal
之因此有這麼奇妙的效果,是由於:
ThreadLocal.get()
,其內部會從各類線程中取出table
數組,而後根據當前ThreadLocal
的索引查找出對應的values
值想要了解
ThreadLocal
源碼的讀者,推薦一篇文章:ThreadLocal詳解
MessageQueue
數據結構:MessageQueue
的數據結構是單鏈表
操做:
A.enqueueMessage
主要操做是單鏈表的插入操做
B.next
是一個無限循環的方法,若是沒有消息,會一直阻塞;當有消息的時候,next
會返回消息並將其從單鏈表中移出
Looper
Q1:Looper
的做用
- 做爲消息循環的角色
- 它會不停地從
MessageQueue
中查看是否有新消息,如有新消息則當即處理,不然一直阻塞(不是ANR
)Handler
須要Looper
,不然將報錯
Q2:Looper
的使用
a1:開啓:
UI
線程會自動建立Looper
,子線程需自行建立
//子線程中須要本身建立一個Looper
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//爲子線程建立Looper
Handler handler = new Handler();
Looper.loop(); //開啓消息輪詢
}
}).start();
複製代碼
- 除了
prepare()
,還提供prepareMainLooper()
,本質也是經過prepare()
getMainLooper()
做用:獲取主線程的Looper
a2:關閉:
quit
:直接退出quitSafely
:設定退出標記,待MessageQueue
中處理完全部消息再退出退出
Looper
的話,子線程會馬上終止;所以:建議在不須要的時候終止Looper
Q3:原理:
Handler
Q1:Handler
的兩種使用方式:
注意:建立
Handler
實例以前必須先建立Looper
實例,不然會拋RuntimeException
(UI
線程自動建立Looper
)
send
方式//第一種:send方式的Handler建立
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//如UI操做
}
};
//send
mHandler.sendEmptyMessage(0);
複製代碼
post
方式最終是經過一系列
send
方法來實現
//實例化Handler
private Handler mHandler = new Handler();
//這裏調用了post方法,和sendMessage同樣達到了更新UI的目的
mHandler.post(new Runnable() {
@Override
public void run() {
mTextView.setText(new_str);
}
});
複製代碼
Q2:Handler
處理消息過程
Handler
的延伸在初學
Handler
的時候,每每會發現AS
亮起一大塊黃色,以警告可能會發生內存泄漏
發生場景:Handler
容許咱們發送延時消息,若是在延時期間用戶關閉了Activity
,那麼該Activity
會泄露
緣由:這個泄露是由於由於 Java
的特性,內部類會持有外部類, Handler
持有 Activity
的引用,Message
持有Handler
的引用,而MessageQueue
會持有Message
的引用,而MessageQueue
是屬於TLS(ThreadLocalStorage)
線程,是與Activity不一樣的生命週期。因此當Activity
的生命週期結束後,而MessageQueue
中還存在未處理的消息,那麼上面一連串的引用鏈就不容許Activity
的對象被回收,就形成了內存泄漏
解決方式:
A.Activity
銷燬時,清空Handler
中未執行或正在執行的Callback
以及Message
// 清空消息隊列,移除對外部類的引用
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
複製代碼
B.靜態內部類+弱引用
private static class AppHandler extends Handler {
//弱引用,在垃圾回收時,被回收
WeakReference<Activity> mActivityReference;
AppHandler(Activity activity){
mActivityReference=new WeakReference<Activity>(activity);
}
public void handleMessage(Message message){
switch (message.what){
HandlerActivity activity=mActivityReference.get();
super.handleMessage(message);
if(activity!=null){
//執行業務邏輯
}
}
}
}
複製代碼
Handler
裏藏着的Callback
首先看下Handler.dispatchMessage(msg)
public void dispatchMessage(Message msg) {
//這裏的 callback 是 Runnable
if (msg.callback != null) {
handleCallback(msg);
} else {
//若是 callback 處理了該 msg 而且返回 true, 就不會再回調 handleMessage
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
複製代碼
能夠看到 Handler.Callback
有優先處理消息的權利
Callback
處理並攔截(返回 true
),那麼 Handler.handleMessage(Msg)
方法就不會被調用了Callback
處理了消息,可是並無攔截,那麼就意味着一個消息能夠同時被Callback
以及 Handler
處理這個就頗有意思了,這有什麼做用呢?
咱們能夠利用 Callback
這個攔截機制來攔截 Handler
的消息!
場景:Hook
ActivityThread.mH ,筆者在進階之路 | 奇妙的四大組件之旅介紹過ActivityThread
,在 ActivityThread
中有個成員變量 mH
,它是個 Handler
,又是個極其重要的類,幾乎全部的插件化框架都使用了這個方法
限於當前知識水平,筆者還沒有研究過插件化的知識,之後有機會的話但願能給你們介紹!
Message
的最佳方式爲了節省開銷,儘可能複用
Message
,減小內存消耗
法一:Message msg=Message.obtain();
法二:Message msg=handler.obtainMessage();
Looper
機制咱們能夠利用Looper
的機制來幫助咱們作一些事情:
Runnable
post
到主線程執行Looper
判斷當前線程是不是主線程public final class MainThread {
private MainThread() {
}
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
//將 Runnable post 到主線程執行
public static void run(@NonNull Runnable runnable) {
if (isMainThread()) {
runnable.run();
}else{
HANDLER.post(runnable);
}
}
//判斷當前線程是不是主線程
public static boolean isMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
}
複製代碼
Android
中爲何主線程不會因Looper.loop()
的死循環卡死?這個是老生常談的問題了,記得當初被學長問到這個問題的時候,一臉懵逼,而後胡說一通,實屬羞愧
要弄清這個問題,咱們能夠經過幾個問題來逐層深刻剖析
Q1:什麼是線程?
線程是一段可執行的代碼,當可執行代碼執行完成後,線程生命週期便該終止了,線程退出
Q2:進入死循環是否是說明必定會阻塞?
前面也說到了線程既然是一段可執行的代碼,當可執行代碼執行完成後,線程生命週期便該終止了,線程退出。而對於主線程,咱們是毫不但願會被運行一段時間,本身就退出,那麼如何保證能一直存活呢?簡單作法就是可執行代碼是能一直執行下去的,死循環便能保證不會被退出
想到這就理解,主線程也是一個線程,它也要維持本身的週期,因此也是須要一個死循環的。因此死循環並非那麼讓人擔憂。
Q3:什麼是Looper
的阻塞?
Looper
的阻塞,前提是沒有輸入事件,此時MessageQueue
是空的,Looper
進入空閒,線程進入阻塞,釋放CPU
,等待輸入事件的喚醒Looper
阻塞的時候,主線程大多數時候都是處於休眠狀態,並不會消耗大量CPU
資源
Looper
的阻塞涉及到Linux pipe/epoll
機制,想了解的讀者可自行
Q4:聊聊ANR
ANR
和Looper的阻塞
兩者相混淆UI
耗時致使卡死,前提是要有輸入事件,此時MessageQueue
不是空的,Looper
正常輪詢,線程並無阻塞,可是該事件執行時間過長(通常5秒),並且與此期間其餘的事件(按鍵按下,屏幕點擊..也是經過Looper
處理的)都沒辦法處理(卡死),而後就ANR
異常了Q5:卡死的真正緣由:
onCreate
/onStart
/onResume
等操做時間過長恭喜你!已經看完了前面的文章,相信你對
Handler
已經有必定深度的瞭解,下面,進行一下課堂小測試,驗證一下本身的學習成果吧!PS:限於篇幅,筆者就不提供答案了,不過答案一搜就有了
Q1:如何將一個Thread
線程變成Looper
線程?Looper
線程有哪些特色
Q2:簡述下Handler
、Message
、Looper
的做用,以及他們之間的關係
Q3: 簡述消息機制的回調處理過程,怎麼保證消息處理機制的惟一性
Q4:爲何發送消息在子線程,而處理消息就變成主線程了,在哪兒跳轉的
若是文章對您有一點幫助的話,但願您能點一下贊,您的點贊,是我前進的動力
本文參考連接: