2018年8月1日以前謝絕全文轉(zhuǎn)載(已授權(quán)除外)
本文作者:@怪盜kidou
本文鏈接:http://www.lxweimin.com/p/f70ee1765a61
周末在家有點(diǎn)兒無(wú)聊,不知道該干些啥,想了想開(kāi)通博客這么長(zhǎng)時(shí)間以來(lái)好像并沒(méi)有些什么關(guān)于 Android 的東西,所以這次來(lái)寫(xiě)寫(xiě)Android 相關(guān)的博客 —— Handler。
為什么寫(xiě) Handler
確實(shí) Handler
是 Android
開(kāi)發(fā)過(guò)程中非常非常常見(jiàn)的東西,講Handler的博客也不勝枚舉為什么我還要寫(xiě)關(guān)于Handler的內(nèi)容?
起因是這樣的,公司為了擴(kuò)張業(yè)務(wù)準(zhǔn)備做一個(gè)新的產(chǎn)品線所以給移動(dòng)端這邊分配了4個(gè)招聘名額(iOS和Android各兩名),頭一個(gè)星期我因?yàn)樵诿χ鲂枨蟛](méi)有參與公司的面試,都是公司的另外兩個(gè)同事在參與面試,后一個(gè)星期我也參與到其中,但是我發(fā)現(xiàn)一個(gè)很?chē)?yán)重的問(wèn)題:在我面試的幾個(gè)人雖然工作經(jīng)驗(yàn)都集中3~6年但都沒(méi)有把 Handler
講清楚。
與其他的博客有什么不同
市面上有太多講 Handler 的博客了,那我的博客要如何做到讓人耳目一新并且切實(shí)能讓大家受益呢?
我想了一下,Handler的基本原理很簡(jiǎn)單,但細(xì)節(jié)還是蠻多的,這次發(fā)現(xiàn)問(wèn)題也是通過(guò)面試得出的,所以我決定通過(guò)模擬面試的方式告訴你關(guān)于 Handler 的那些事兒。
約定
本文的各個(gè)問(wèn)題只是我自己想出來(lái)的,并不是出自真實(shí)的面試中(除了部分我面試別人時(shí)的提問(wèn)),其他的均為我為了給大家介紹 Handler 機(jī)制時(shí)想出的問(wèn)題。
本文后面會(huì)出現(xiàn)的部分源碼,為避免小伙伴們?cè)?Android Studio 中看到代碼與我博客中的不一致,這里先統(tǒng)一一下環(huán)境:
- sdk版本:API 27
android{
compileSdkVersion 27
// ......
}
- 源碼版本:27_r03
Q:說(shuō)一下 Handler機(jī)制中涉及到那些類(lèi),各自的功能是什么
A:Handler
、Looper
、MessageQueue
、Message
,主要是這4個(gè),ThreadLocal
可以不算在里面畢竟這個(gè)是JDK本身自帶類(lèi)不是專(zhuān)門(mén)為Handler機(jī)制設(shè)計(jì)的。
Handler
的作用是將 Message
對(duì)象發(fā)送到 MessageQueue
中去,同時(shí)將自己的引用賦值給 Message#target
。
Looper
的作用是將 Message
對(duì)象從 MessageQueue
中取出來(lái),并將其交給 Handler#dispatchMessage(Message)
方法,這里需要主要的是:不是調(diào)用 Handler#handleMessage(Message)
方法,具體原因后面會(huì)講。
MessageQueue
的作用負(fù)責(zé)插入和取出 Message
Q:Handler 有哪些發(fā)送消息的方法
我主要是看其知不知道 post 相關(guān)的方法,問(wèn)了兩個(gè)人兩人都不知道有post方法
sendMessage(Message msg)
sendMessageDelayed(Message msg, long uptimeMillis)
post(Runnable r)
postDelayed(Runnable r, long uptimeMillis)
sendMessageAtTime(Message msg,long when)
下面的幾個(gè)方法在我眼中可能并不是那么重要
sendEmptyMessage(int what)
sendEmptyMessageDelayed(int what, long uptimeMillis)
sendEmptyMessageAtTime(int what, long when)
Q:MessageQueue 中的 Message 是有序的嗎?排序的依據(jù)是什么
是有序的。你可能會(huì)想這不是廢話嘛,Queue
都是有序的,Set
才是無(wú)序的,這里想問(wèn)你的是他的內(nèi)部是基于什么進(jìn)行的排序,排序的依據(jù)是 Message#when
字段,表示一個(gè)相對(duì)時(shí)間,該值是由 MessageQueue#enqueueMessage(Message, Long)
方法設(shè)置的。
// 見(jiàn) MessageQueue.java:554,566~578
boolean enqueueMessage(Message msg, long when) {
// ....
synchronized (this) {
// ....
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
// 一致循環(huán),直到找到尾巴(p == null)
// 或者這個(gè) message 的 when 小于我們當(dāng)前這個(gè) message 的 when
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
}
return true;
}
如果當(dāng)前插入的 message#when
是介于 5~8 之間,那么for 循環(huán)結(jié)束時(shí) prev
和p
指向的樣子應(yīng)該是下圖的
由于這個(gè)特性,所以當(dāng)兩個(gè) Message#when
一致時(shí)插入序按先后順序,比如兩個(gè)的 when 都是7,那么第一個(gè)進(jìn)入后的樣子如下圖(黃):
第二個(gè)進(jìn)入后的樣子(紅):
Q:Message#when 是指的是什么
Message#when
是一個(gè)時(shí)間,用于表示 Message
期望被分發(fā)的時(shí)間,該值是 SystemClock#uptimeMillis()
與 delayMillis
之和。
// Handler.java:596
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
SystemClock#uptimeMillis()
是一個(gè)表示當(dāng)前時(shí)間的一個(gè)相對(duì)時(shí)間,它代表的是 自系統(tǒng)啟動(dòng)開(kāi)始從0開(kāi)始的到調(diào)用該方法時(shí)相差的毫秒數(shù) 。
Q:Message#when 為什么不用 System.currentTimeMillis() 來(lái)表示
System.currentTimeMillis()
代表的是從 1970-01-01 00:00:00 到當(dāng)前時(shí)間的毫秒數(shù),這個(gè)值是一個(gè)強(qiáng)關(guān)聯(lián) 系統(tǒng)時(shí)間 的值,我們可以通過(guò)修改系統(tǒng)時(shí)間達(dá)到修改該值的目的,所以該值是不可靠的值。
比如手機(jī)長(zhǎng)時(shí)間沒(méi)有開(kāi)機(jī),開(kāi)機(jī)后系統(tǒng)時(shí)間重置為出廠時(shí)設(shè)置的時(shí)間,中間我們發(fā)送了一個(gè)延遲消息,過(guò)了一段時(shí)間通過(guò) NTP 同步了最新時(shí)間,那么就會(huì)導(dǎo)致 延遲消息失效
同時(shí) Message#when
只是用 時(shí)間差 來(lái)表示先后關(guān)系,所以只需要一個(gè)相對(duì)時(shí)間就可以達(dá)成目的,它可以是從系統(tǒng)啟動(dòng)開(kāi)始計(jì)時(shí)的,也可以是從APP啟動(dòng)時(shí)開(kāi)始計(jì)時(shí)的,甚至可以是定期重置的(所有消息都減去同一個(gè)值,不過(guò)這樣就復(fù)雜了沒(méi)有必要)。
Q:子線程中可以創(chuàng)建 Handler 對(duì)象嗎?
不可以在子線程中直接調(diào)用 Handler 的無(wú)參構(gòu)造方法,因?yàn)?Handler
在創(chuàng)建時(shí)必須要綁定一個(gè) Looper
對(duì)象,有兩種方法綁定
- 先調(diào)用 Looper.prepare() 在當(dāng)前線程初始化一個(gè) Looper
Looper.prepare();
Handler handler = new Handler();
// ....
// 這一步可別可少了
Looper.loop();
- 通過(guò)構(gòu)造方法傳入一個(gè) Looper
Looper looper = .....;
Handler handler = new Handler(looper);
Q:Handler 是如何與 Looper 關(guān)聯(lián)的
上個(gè)問(wèn)題應(yīng)該告知了其中一種情況:通過(guò)構(gòu)造方法傳參。
還有一種是我們直接調(diào)用無(wú)參構(gòu)造方法時(shí)會(huì)有一個(gè)自動(dòng)綁定過(guò)程
// Handler.java:192
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper(); // 就是這里
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Q:Looper 是如何與 Thread 關(guān)聯(lián)的
Looper 與 Thread 之間是通過(guò) ThreadLocal 關(guān)聯(lián)的,這個(gè)可以看 Looper#prepare()
方法
// Looper.java:93
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
Looper
中有一個(gè) ThreadLocal
類(lèi)型的 sThreadLocal
靜態(tài)字段,Looper
通過(guò)它的 get
和 set
方法來(lái)賦值和取值。
由于 ThreadLocal
是與線程綁定的,所以我們只要把 Looper
與 ThreadLocal
綁定了,那 Looper
和 Thread
也就關(guān)聯(lián)上了
ThreadLocal
的原理在問(wèn) Handler
機(jī)制的時(shí)候也是一個(gè)比較常問(wèn)的點(diǎn),但是介紹的博客很多,源碼也沒(méi)有多少,這里就不再介紹了,如果有需要的話后期會(huì)寫(xiě)新博客。
Q:Handler 有哪些構(gòu)造方法
如果你上面的問(wèn)題 子線程中可以創(chuàng)建 Handler 對(duì)象嗎 沒(méi)有答上的話,我一般會(huì)通過(guò)這個(gè)問(wèn)題引導(dǎo)一下。
問(wèn)這個(gè)問(wèn)題主要是想問(wèn)你構(gòu)造方法可以傳那些參數(shù),并不是要你完全說(shuō)出來(lái),但是當(dāng)你知道可以傳哪些參數(shù)的時(shí)候,也可以推算出來(lái)有幾個(gè)構(gòu)造方法。
先說(shuō)可以傳那些類(lèi)型(僅限開(kāi)放API,被 @hide 標(biāo)注的不算在內(nèi)),僅兩種類(lèi)型:Looper
、Handler$Callback
,那么我們就可以退算出有多少個(gè)公共構(gòu)造方法了:無(wú)參、單Looper、單Callback、Looper和Handler,共4種。
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
還有一個(gè) boolean
的 async, 不過(guò)這個(gè)不是開(kāi)放API,即使不知道個(gè)人覺(jué)得完全沒(méi)有問(wèn)題。
Q:looper為什么調(diào)用的是Handler的dispatchMessage方法
看一下dispatchMessage方法
// Handler.java:97
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
從上面的代碼不難看出有兩個(gè)原因:
- 當(dāng)
msg.callback != null
時(shí)會(huì)執(zhí)行handleCallback(msg)
,這表示這個(gè) msg 對(duì)象是通過(guò)handler#postAtTime(Runnable, long)
相關(guān)方法發(fā)送的,所以msg.what
和msg.obj
都是零值,不會(huì)交給Handler#handleMessage
方法。 - 從上一個(gè)問(wèn)題你應(yīng)該看到了
Handler
可以接受一個(gè)Callback
參數(shù),類(lèi)似于 View 里的 OnTouchListener ,會(huì)先把事件交給Callback#handleMessage(Message)
處理,如果返回 false 時(shí)該消息才會(huì)交給Handler#handleMessage(Message)
方法。
Q:在子線程中如何獲取當(dāng)前線程的 Looper
Looper.myLooper()
內(nèi)部原理就是同過(guò)上面提到的 sThreadLocal#get()
來(lái)獲取 Looper
// Looper.java:203
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
Q:如果在任意線程獲取主線程的 Looper
Looper.getMainLooper()
這個(gè)在我們開(kāi)發(fā) library 時(shí)特別有用,畢竟你不知道別人在調(diào)用使用你的庫(kù)時(shí)會(huì)在哪個(gè)線程初始化,所以我們?cè)趧?chuàng)建 Handler
時(shí)每次都通過(guò)指定主線程的 Looper
的方式保證庫(kù)的正常運(yùn)行。
Q:如何判斷當(dāng)前線程是不是主線程
知道了上面兩個(gè)問(wèn)題,這個(gè)問(wèn)題就好回答了
方法一:
Looper.myLooper() == Looper.getMainLooper()
方法二:
Looper.getMainLooper().getThread() == Thread.currentThread()
方法三: 方法二的簡(jiǎn)化版
Looper.getMainLooper().isCurrentThread()
Q:Looper.loop() 會(huì)退出嗎?
不會(huì)自動(dòng)退出,但是我們可以通過(guò) Looper#quit()
或者 Looper#quitSafely()
讓它退出。
兩個(gè)方法都是調(diào)用了 MessageQueue#quit(boolean)
方法,當(dāng) MessageQueue#next()
方法發(fā)現(xiàn)已經(jīng)調(diào)用過(guò) MessageQueue#quit(boolean)
時(shí)會(huì) return null
結(jié)束當(dāng)前調(diào)用,否則的話即使 MessageQueue
已經(jīng)是空的了也會(huì)阻塞等待。
Q:MessageQueue#next 在沒(méi)有消息的時(shí)候會(huì)阻塞,如何恢復(fù)?
當(dāng)其他線程調(diào)用 MessageQueue#enqueueMessage
時(shí)會(huì)喚醒 MessageQueue
,這個(gè)方法會(huì)被 Handler#sendMessage
、Handler#post
等一系列發(fā)送消息的方法調(diào)用。
boolean enqueueMessage(Message msg, long when) {
// 略
synchronized (this) {
// 略
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 {
// 略
}
if (needWake) {
nativeWake(mPtr); // 喚醒
}
}
return true;
}
Q:Looper.loop() 方法是一個(gè)死循環(huán)為什么不會(huì)阻塞APP
我認(rèn)為更好的回答:
這是一個(gè)假象,舉個(gè)例子
public static void main(String[] args){
while(true){
// do work in while
}
doSomeThingOutWhile();
}
對(duì)于從整個(gè)main方法來(lái)看,while(true)
確實(shí)阻塞了 doSomeThingOutWhile()
這個(gè)方法的執(zhí)行,對(duì)于這樣看,好像確實(shí)是卡住了,因?yàn)槲覀冊(cè)?doSomeThingOutWhile
方法中想要做的事沒(méi)法做了,但是如果我們把我們要做的事情通過(guò)隊(duì)列放到 while
里面去做,那么是不是你就不會(huì)覺(jué)得卡了,你想要做的事情都完成了,雖然有個(gè)死循環(huán)但并不影響你想要做什么,而Android中 Looper.loop()
就是這樣的原理,因?yàn)樗凶屛覀儠?huì)覺(jué)得卡住的都被放到 MessageQueue
里,然后通過(guò)Looper
取出并交給 Handler
執(zhí)行了。
PS:不僅僅是Android,幾乎所有和UI操作的都有一個(gè)類(lèi)似Android Handler機(jī)制的事件循環(huán)處理機(jī)制
-----分割線-------
下面是原始回答,會(huì)讓人覺(jué)得卡是因?yàn)樗姥h(huán)之后的代碼無(wú)法執(zhí)行,如果沒(méi)有理解到其實(shí)我們的代碼都是執(zhí)行在死循環(huán)里面的話,還是沒(méi)有辦法理解為什么不會(huì)卡。
如果說(shuō)操作系統(tǒng)是由中斷驅(qū)動(dòng)的,那么Android的應(yīng)用在宏觀上可以說(shuō)是 Handler 機(jī)制驅(qū)動(dòng)的,所以主線程中的 Looper 不會(huì)一直阻塞的,原因如下(以下是我瞎JB猜的,歡迎補(bǔ)充、指正):
- 當(dāng)隊(duì)列中只有延遲消息的時(shí)候,阻塞的時(shí)間等于頭結(jié)點(diǎn)的 when 減去 當(dāng)前時(shí)間,時(shí)間到了以后會(huì)自動(dòng)喚醒。
- 在Android中 一個(gè)進(jìn)程中不會(huì)只有一個(gè)線程,由于 Handler 的機(jī)制,導(dǎo)致我們?nèi)绻僮?View 等都要通過(guò) Handler 將事件發(fā)送到主線程中去,所以會(huì)喚醒阻塞。
- 傳感器的事件,如:觸摸事件、鍵盤(pán)輸入等。
- 繪制事件:我們知道要想顯示流暢那么屏幕必須保持 60fps的刷新率,那繪制事件在入隊(duì)列時(shí)也會(huì)喚醒。
- 總是有
Message
源源不斷的被加入到MessageQueue
中去,事件是一環(huán)扣一環(huán)的,舉個(gè)Fragment
的栗子:
getSupportFragmentManager()
.beginTransaction()
.replace(android.R.id.content,new MyFragment())
.commit();
這個(gè)時(shí)候并不是立馬把 MyFragment
顯示出來(lái)了,而是經(jīng)過(guò)層層的調(diào)用來(lái)到了 FragmentManager#scheduleCommit()
方法,在這里又有入隊(duì)列操作,
// FragmentManager.java:2103
private void scheduleCommit() {
synchronized (this) {
boolean postponeReady =
mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
if (postponeReady || pendingReady) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit); // 這里有入隊(duì)列操作
}
}
}
提交后是不是緊接著又是一系列的生命周期的事件分發(fā)?所以。。。
你還有什么關(guān)于Handler的問(wèn)題,評(píng)論告訴我吧
如果你還有什么在面試中遇到的和 Handler
相關(guān)的問(wèn)題,該博客中沒(méi)有體現(xiàn)出來(lái)的趕緊評(píng)論告訴我吧,我會(huì)持續(xù)補(bǔ)充到這篇博客當(dāng)中。
我最近剛剛開(kāi)通了微信公眾號(hào)(怪盜kidou),歡迎關(guān)注