Android – Handler原理
Handler的主要作用是收發消息和切線程
功能一:收發消息
簡單流程介紹
希望你看完這篇文章後也可以把流程自己講出來,並且每個環節還可以講出很多細節
他的消息機制離不開Looper、MessageQueue
- 其中
Looper
每個線程只能持有一個,主要負責循環查看MessageQueue
裏面是否有msg
需要處理,並將需要處理的消息取出,交給Handler
MessageQueue
是負責存放消息的,數據結構是一個單鏈表,這樣就可以方便地插入或刪除msg
具體流程一般是:
-
Handler
發送一條msg
=> 本質是向MessageQueue
里插入一條msg
,插入時候的依據是msg.when
=>SystemClock.uptimeMillis() + delayMillis
-
這條
msg
被MessageQueue.next()
返回並交給Handler
去處理
next()
會在有同步屏障(msg.target==null
)的時候遍歷查找並返回最早的異步消息,並在移除屏障後,從頭取出並返回消息 -
Handler.dispatchMessage(msg)
會優先處理msg.callback
,如果msg.callback
為空,就處理Handler.mCallback
,然後處理是msg
本身
msg.callback
是在調用Handler.post(Runnable)
時,裏面的Runnable
(runOnUIThread
,view.post(Runnable)
也用的是Handler.post(Runnable)
,Runnable
是一樣的)這是在不新增
Handler
的情況下,另一種調用Handler
的方式(如下)
class MyHandlerCallBack: Handler.Callback {
override fun handleMessage(msg: Message?): Boolean {
TODO("Not yet implemented")
}
}
可以看到他也有handleMessage
這個方法
Looper是個死循環
(1)死循環的目的
目的就是讓主線程一直卡在這個死循環裏面
因為Looper的作用就是在這個死循環裏面取出消息,然後交給Handler
處理
Android的生命周期,你了解的onCreate,onStop,onStart…… 等等都是由Handler
來處理的,都是在這個死循環裏面運行的
所以什麼Looper
死循環卡死主線程怎麼辦???
必須給我卡住!!!不卡住的話,消息就沒法整了!!!
看下Android啟動的時候的源碼
Activitythread.java >> main()
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
想想寫java的時候,main最後一行執行完了,不就徹底玩完了嘛!!!
(2)死循環里幹了啥
其實想都不用想,一直在看MessageQueue
裏面有沒有消息唄,太簡單了!調用的就是MessageQueue.next()
看下源碼 MessageQueue.java >> loop()
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
}
很簡單,next()
返回Message
,msg.target.dispatchMessage()
處理Message
但是隊列里沒消息就會返回null,這是錯誤的!!!具體往下看
MessageQueue是個單鏈表
1.插隊
Handler
發消息的時候,目的就是對msg
經過一系列操作,最終也只是調用enqueueMessage
插入隊列而已
看下源碼 Handler>>enqueueMessage()
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
return
直接調用Message的插入隊列方法
2.出隊
出隊就是next()
方法,之前已經見過了
(1)時間順序
Message
是按時間排序的,也就是msg.when
=> SystemClock.uptimeMillis() + delayMillis
msg.when
是Message
期望被處理的時間
SystemClock.uptimeMillis()
是開機到現在的時間,delayMills
是延遲時間,這個在sendMessageDelayed
方法里直接可以直接傳參
next()
就是按照時間順序處理MessageQueue
裏面的消息的
但是next()
里有個概念叫 同步屏障
(2)同步屏障
同步屏障,就是說,平時MessageQueue
都是處理同步消息,也就是按順序來,一個個出隊
同步屏障就是阻擋同步消息的意思
就是msg.target == null
的時候,MessageQueue
就會去找msg.isAsynchronous()
返回true
的msg
isAsynchronous,沒錯 ! 這是異步消息,就是優先級很高,需要立刻執行的消息,比如:更新View
(3)阻塞
值得注意的是,講Looper的時候,源碼next()
後面官方給我們注釋了 // might block
可能阻塞,也就是說可能這個next()
也許會執行好久
next()
會阻塞?,什麼時候阻塞?
now < msg.when
也就是時間還沒到,期望時間大於現在的時間
(4)退出
另外看第一行,只有ptr == 0
,才會返回null
所以上面才說next()
不會因為沒消息而返回null
,原來返回null
的時候在這呢!
看下源碼,MessageQueue.java >> next()
@UnsupportedAppUsage
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
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) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
...
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
...
}
...
}
}
代碼簡略了還是有點多,別著急,慢慢看
那pre
什麼時候就是0
了呢?
答:quit()
了之後
看下源碼,Looper.java
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
可以看到只是一個傳參不同而已,下面看看這個參數是幹嘛的
看下源碼,MessageQueue.java >> quit()
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
可以看到,safe == true
,就移除未來的Message
safe == false
,就移除所有的Message
mQuiting
變成了true
,記住他我們一會兒會用到
而改變ptr的地方在這裡
在next()
裏面
裏面有個dispose
,找不到可以ctrl+F
找一下
這裡只有在mQuiting == true
的時候,才會調用
這就是改mPtr
的地方,然後下次next()
的時候就會返回null
了
Handler流程
(1)post過來的msg
我們已經知道了在Looper
的死循環裏面,會將next()
返回的msg
交給Handler
,調用dispatchMessage()
dispatchMessage()
裏面會先判斷msg
是不是被post
過來的,因為post
要執行的邏輯在msg.callback
裏面,callback
是一個Runnable
,這可能不是很好理解
你可以想想runOnUIThread(Runnable)
,這裡的Runnable
就是上面的callback
,
他們都是調用了Handler.post(Runnable)
至於為啥起個名叫callback
,我也納悶兒
(2)send過來的msg
這些msg
是會的邏輯是你重寫的handleMessage
那裡的邏輯
如果實現了Handler.Callback
這個Interface
,就會處理mCallback
的handleMessage
而不是Handler
自己的handleMessage
這是一個優先級策略,沒什麼好奇怪的
我們看下源碼 => Handler.java >> dispatchMessage()
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
這就是Handler的消息機制了
接下來我們講講Handler
的另一個功能,切線程
功能二:切線程
Handler切線程使用的是ThreadLocal
(1)ThreadLocal
ThreadLocal
是線程裏面的一個數據儲存類,用法類似map
,key
就是thread
但是他沒有提供,根據key
來找ThreadLocal
的Values
的方法,所以暴露的api
就只能讓你去get
當前線程的ThreadLocal
的Values
對象而已,就是key
——你自己沒法作為參數傳進去,只能是currentThread
如果你沒用過ThreadLocal
,我給你舉個例子
fun main() {
val booleanThreadLocal = ThreadLocal<Boolean>()
booleanThreadLocal.set(true)
println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}")
thread(name = "thread#001") {
booleanThreadLocal.set(false)
println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}")
}
thread(name = "thread#002") {
println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}")
}
}
結果是這樣的:你可以自己運行看看
in Thread[main] booleanThreadLocal value = true
in Thread[thread#001] booleanThreadLocal value = false
in Thread[thread#002] booleanThreadLocal value = null
(2)切線程的細節
話說回來,Handler怎麼通過ThreadLocal切線程的呢?
答案是:Looper
是放在ThreadLocal
里的
回顧片頭的流程,Handler
將消息插入MessageQueue
,然後Looper
取出來,再還給Handler
,這種設計不止是為了讓msg
可以按順序處理,還可以讓外部接口只有Handler
最關鍵的是,Looper
跟Handler
的觸發關係只有Looper
觸發Handler
,Handler
不會觸發Looper
因此Handler
把消息放在MessageQueue
之後,就在等着Looper
來給自己派發任務(msg
)
舉個例子:
線程A調用主線程的Handler
發一個消息
Handler
將這個消息插入MessageQueue
,此時其實還在線程A里
只有Looper
在next()
調用msg.target.dispatchMessage()
時,就變成了主線程了
僅僅是因為Looper
在 主線程 而已