Handler 相信每個作 Android 開發的小夥伴都很是熟悉了,最經常使用的場景就是在子線程中進行數據操做而後經過 Handler 消息機制通知到 UI 線程來更新 UI ,地球人都知道在子線程中更新 UI 通常狀況下都會報錯。是吧!咱用的賊溜,各類姿式發送 Handler 消息都熟練掌握,可是若是這時候出去面試被問到「 Handler 原理」,「消息是怎麼從子線程發送到主線程的」等等 Handler 底層的實現,就懵逼了。java
雖然網上關於分析 Handler
的博客文章很是多,已經有不少大佬分析的很是透徹清楚了,但我這裏仍是想在看過大佬們的文章後本身再寫一篇,一方面是讓本身加深理解,另外一方面就是想分享所學知識(分享會讓知識變的更多)。android
看了不少大佬去大廠面試的面經,Handler幾乎是必問的,因此咱們更加必須知其因此然了。git
簡單來講Handler是結合線程消息隊列來發送和處理 Message
, Runnable
對象來實現線程間通訊的工具。面試
先來一塊兒看一個關於 Handler 使用的典型實例(實例來源 Gityuan 博客)c#
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
//TODO 定義消息處理邏輯.
}
};
Looper.loop();
}
}
複製代碼
在線程中聲明一個Handler必須按如下順序三步走:app
以上三步曲我稱之爲:Looper 肉夾饃
(手動狗頭)異步
這裏產生一個問題了:async
Question 1: 建立 Handler
以前爲啥必定要先調用 Looper.prepare()
呢?ide
咱們帶着這個問題繼續往下看。工具
提及 Handler 那就必需要了解 Android 的消息機制了, Android 中有大量的交互場景是經過消息機制來驅動的,如咱們最熟悉不過的 Activity
的生命週期等等。而 Handler 就是 Android 消息機制的重要組成部分。
咱們先來看一張圖:
圖中描述的是消息是怎樣從子線程流到主線程從而完成線程間通訊的大概流程。Android 整個消息機制主要由Handler
,Looper
,MessageQueue
,Message
四個部分組成。這裏先簡單介紹一下這四位具體職責:
Handler : 負責發送消息和接收消息並進行處理。
MessageQueue : 消息隊列,負責消息存儲與管理。
Looper : 負責關聯 Handler
當前所處線程,進行消息分發,經過不斷循環實現將消息從消息隊列取出,而後分發給對應的消息處理者。
Message : 消息載體。
看完上圖腦海中應該對 Android 的消息機制已經有了一個大概的概念了,下面咱們開始從源碼來看 Android 的消息機制具體實現。這裏就以上圖中的主線程和子線程通訊爲思路。
先來看上圖對應的代碼實例
public class MainActivity extends AppCompatActivity {
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//todo UI線程接收消息並處理
Log.d("MainActivity","handleMessage....");
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new Thread(){
@Override
public void run() {
super.run();
//子線程發送消息
handler.sendMessage(new Message());
}
}.start();
}
}
複製代碼
主( UI )線程中實例化一個 Handle
對象。咱們跟到源碼中看一下 Handle()
作了什麼事情。
Handle
默認構造方法public Handler() {
this(null, false);
}
複製代碼
Handler(Callback callback, boolean async)
public Handler(Callback callback, boolean async) {
......
//獲取當前Looper對象
mLooper = Looper.myLooper();
//檢查當前線程Looper是否存在
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
......
}
複製代碼
源碼解析:
在該構造方法中,每次實例化 Handler
都會檢查當前線程是否已綁定 Looper
,若是沒有則會拋出異常提示你沒有調用 Looper.Prepare()
。看到這裏【1.2】的 Question 1`是否是就有答案了。
Question 1 正解:
在建立
Handler
對象以前必需要先建立Looper
而且將Looper
對象綁定到當前線程,不然在實例化Handler
時會報異常。
那麼 Looper.Prepare()
方法裏又是怎麼建立 Looper
對象並綁定到當前線程的呢?跟進源碼看一下。
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//檢查當前線程是否已有 Looper,每一個線程只容許執行一次該方法,第二次執行時線程的TLS已有數據,則會拋出異常。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//建立Looper對象,並保存到當前線程的TLS區域
sThreadLocal.set(new Looper(quitAllowed));
}
複製代碼
源碼解析:
prepare()
方法默認會調用 prepare(true)
。
prepare(boolean quitAllowed)
方法參數quitAllowed
表示是否當前Looper
是否可退出,傳true
表示可退出,false
爲不可退出。
prepare(boolean quitAllowed)
方法中會先檢查當前線程是否已經有 Looper
,若是有則會拋出異常,也就意味着每一個線程中 Looper.Prepare()
方法只能夠調用一次!這裏咱們就知道了,
Looper
和當前線程是一一對應的關係,一個Looper
只能關聯一個線程。
Looper
後 sThreadLocal.set(new Looper(quitAllowed))
就開始建立一個 Looper
對象並保存到當前線程的TLS(關於 ThreadLocal 請自行查閱資料)區域,這樣就完成了 Looper
對象的建立和關聯到當前線程。Looper.Prepare()
調用後 Looper
建立好了,咱們再回到 Handler
構造方法中:
Looper.myLooper()
方法
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
複製代碼
源碼解析: 這個方法很簡單就是去當前線程的中的 TLS 區域 Get Looper
對象,沒錯就是 Looper.Prepare()
方法中sThreadLocal.set(new Looper(quitAllowed))
Set 進去的。
最後 mQueue = mLooper.mQueue
會獲取 Looper
對象的消息隊列,若是你剛纔從 prepare(boolean quitAllowed)
方法中繼續跟進 new Looper(quitAllowed)
方法中你就會發現:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
複製代碼
建立 Looper
對象時會爲當前 Looper
對象建立一個消息隊列 new MessageQueue(quitAllowed)
。
不知道細心的你發現了沒,咱們的圖一的代碼實例中是否是少了點什麼。
沒錯,少了肉夾饃!
吶!Question 2來啦:
不是反覆強調建立 Handler
以前要先調用 Looper.Prepare()
建立 Looper
嗎?那圖一的代碼實例中主線程並無調用啊,直接就建立了 Handler
,你這代碼確定會拋這個異常:
RuntimeException( "Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
複製代碼
悄咪咪告訴你,若是你在圖一的代碼實例中建立Handler
以前調用 Looper.Prepare()
又會拋出如下異常哦:
RuntimeException("Only one Looper may be created per thread");
複製代碼
答案在 ActivityThread.main()
中。
ActivityThread
的main()
方法就是整個APP的入口,也就是咱們一般所講的主線程
,UI線程
。 但他實際上並非一個線程,ActivityThread
並無繼承Thread
類,咱們能夠把他理解爲主線程的管理者,負責管理主線程的執行,調度等操做,能夠看下這個類源碼的註釋。
/** * This manages the execution of the main thread in an * application process, scheduling and executing activities, * broadcasts, and other operations on it as the activity * manager requests. * * {@hide} */
public final class ActivityThread extends ClientTransactionHandler {
......
}
複製代碼
好了,咱們來看下 ActivityThread.main()
一探究竟(這裏只貼關鍵代碼了)。
public static void main(String[] args) {
.......
Looper.prepareMainLooper();
.......
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
.......
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
複製代碼
源碼解析:
Looper.prepareMainLooper();
Looper
,內部實現調用了 prepare(false)
方法。傳 false
表示當前 Looper
不可退出。/** * Initialize the current thread as a looper, marking it as an * application's main looper. The main looper for your application * is created by the Android environment, so you should never need * to call this function yourself. See also: {@link #prepare()} */
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
複製代碼
以上,就是Question 2的正解所在,Android 程序入口 ActivityThread.main()
中調用 Looper.prepareMainLooper();
爲主線程建立了 Looper
,因此咱們的主線程能夠直接建立 Handler
。
Looper.loop();
Looper 肉夾饃
寫法,這個方法幹啥用的,後面再說。OK,到這裏,Handler
,Looper
,MessageQueue
都已經準備就緒,圖一的流程差很少能夠跑起來了。咱們總結一下重點:
Handler
會檢測當前線程 Looper
是否存在,沒有則會拋出異常。Handler
以前必須先調用 Looper.Prepare()
建立 Looper
。Looper
能夠理解爲當前線程的消息調度者,負責消息分發。和當前線程的關聯綁定經過 sThreadLocal.set(new Looper(quitAllowed));
實現。Looper
和當前線程是一對一的關係, Looper.Prepare()
方法中有進行校驗,重複建立 Looper
綁定會拋出異常。Looper
和 MessagQueue
也是一對一的關係,構造 Looper
對象時會爲其建立一個對應的 MessagQueue
對象。Handler
消息發送實際上是一個消息入隊的過程,調用 Handler
相應的發送消息方法將當前消息添加到 MessageQueue
消息隊列,最終由 Looper
負責進行消息分發。
Handler
爲咱們提供了一系列的消息發送方法:
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessage(int what){
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageAtTime(msg, uptimeMillis);
}
複製代碼
發送消息的方法還有不少就不一一列出來了,全部發送消息的方法最終都是調用到 enqueueMessage
方法中:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼
該方法實現最終調用的是queue.enqueueMessage(msg,uptimeMillis)
方法即 MessageQueue
添加消息到消息隊列中。
前面提到 Handler
全部發送的消息最終都經過 enqueueMessage
加入到 MessageQueue
消息隊列中,因此消息的存儲與管理由 MessageQueue
來負責。
消息入隊由 enqueueMessage(msg,uptimeMillis)
方法負責:
boolean enqueueMessage(Message msg, long when) {
//驗證Message 是否有target,這裏的target就是對應的handler
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//將消息按時間順序插入到MessageQueue。通常地,不須要喚醒事件隊列,除非
//消息隊頭存在barrier,而且同時Message是隊列中最先的異步消息。
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
複製代碼
消息入隊順序是按照 Message
觸發時間 long when
入隊的,有新消息加入時,會循環遍歷當前消息隊列,對比消息觸發時間,直到找到當前消息合適的插入位置,以此來保證全部消息的觸發時間順序。
MessageQueue
的 next
方法負責從消息隊列中取出一條消息。
Message next() {
.......
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 嘗試檢索下一條消息。若是找到,則返回。
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
////當消息Handler爲空時,查詢MessageQueue中的下一條異步消息msg,則退出循環。
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//當異步消息觸發時間大於當前時間,則設置下一次輪詢的超時時長
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 獲取一條消息,並返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg; //成功地獲取MessageQueue中的下一條即將要執行的消息
}
} else {
//沒有消息
nextPollTimeoutMillis = -1;
}
//消息正在退出,返回null
if (mQuitting) {
dispose();
return null;
}
}
//IdleHandler相關內容
.......
}
}
複製代碼
有人( Handler
)在子線程把消息發到我(MessageQueue
)這裏來了,我負責把消息存起來,而且告訴你們存儲(enqueueMessage
)消息和提取(next
)消息的方法 ,那麼誰來負責把我這裏的消息分發出去而且告訴主線程的人(Handler
)呢?
幹這個活的就是咱們的 Looper
了!
還記得大明湖畔的的 Looper 肉夾饃
麼!
如今咱們就來看一下 Looper 肉夾饃
之 Looper.loop()
:
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
......
//開啓死循環
for (;;) {
//循環遍歷,不斷從MessagQueue中獲取Messag
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
......
try {
//分發消息到對應的msg.target
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
msg.recycleUnchecked();
}
}
複製代碼
源碼解析: 調用 loop()
方法後,方法內部會開啓一個死循環,經過調用 MessageQueue
消息出棧方法 next()
獲取 Message
,而後調用 msg.target.dispatchMessage(msg);
將消息分發到 Message
對應的 target
。
msg.target
就是發送該消息的Handler
對象。
loop()
方法中調用 msg.target.dispatchMessage(msg);
將消息分發出去,咱們來看一下分發出去的消息是怎麼被接收的。
/** * Handle system messages here. */
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//回調到 Handler 的 handleMessage 方法
handleMessage(msg);
}
}
複製代碼
源碼解析:
dispatchMessage(msg)
方法內部調用了 handleMessage(msg)
方法,這個方法用過 Handler
的應該都很是熟悉了,它是 Handler
的一個空實現方法,通常在建立 Handler
的線程重寫此方法,就能夠回調到子線程發出的消息了。
/** * Subclasses must implement this to receive messages. */
public void handleMessage(Message msg) {
}
複製代碼
Looper 肉夾饃
職責:
Looper
對象,並關聯當前線程。Handler
對象。MessageQueue
消息隊列中取消息,取到後分發給 Message
對應的 target
。整個消息從子線程流轉到主線程流程:
Looper肉夾饃
。Handler
相應發送消息方法,最終經過 enqueueMessage
將消息加入消息隊列 MessageQueue
。Looper
循環從 MessageQueue
獲取到消息,調用當前消息對應的 target
的 dispatchMessage(msg)
方法。dispatchMessage(msg)
回調 handleMessage(msg)
方法,消息即從子線程流轉到重寫 handleMessage(msg)
方法所在線程。Android 主線程默認會使用 Looper 肉夾饃
(詳見 ActivityThread.main()
),所以 Android 主線程中只需建立 Handler
對象便可。
主線程 Looper
不能夠退出,所以 ActivityThread.main()
方法中調用的 Looper.prepareMainLooper();
方法中 prepare(false);
傳參是 false
。 若是強行退出主線程 Looper
會拋出如下異常:
Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
at android.os.MessageQueue.quit(MessageQueue.java:415)
at android.os.Looper.quit(Looper.java:240)
複製代碼
緣由是:Android App 主進程依賴消息機制驅動,主線程消息循環退出了,那麼 App 也會退出。
Looper
中開啓了一個死循環來從 MessageQueue
中取消息,並且沒有單獨開線程,爲何不會形成 Android 主線程卡死 ANR?