android handle 阻塞,Android全面解析之Handler机制:常见问题汇总
主線(xiàn)程為什么不用初始化Looper?
答:因?yàn)閼?yīng)用在啟動(dòng)的過(guò)程中就已經(jīng)初始化主線(xiàn)程Looper了。
每個(gè)java應(yīng)用程序都是有一個(gè)main方法入口,Android是基于Java的程序也不例外。Android程序的入口在ActivityThread的main方法中:
// 初始化主線(xiàn)程Looper
Looper.prepareMainLooper();
...
// 新建一個(gè)ActivityThread對(duì)象
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
// 獲取ActivityThread的Handler,也是他的內(nèi)部類(lèi)H
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
// 如果loop方法結(jié)束則拋出異常,程序結(jié)束
throw new RuntimeException("Main thread loop unexpectedly exited");
}
main方法中先初始化主線(xiàn)程Looper,新建ActivityThread對(duì)象,然后再啟動(dòng)Looper,這樣主線(xiàn)程的Looper在程序啟動(dòng)的時(shí)候就跑起來(lái)了。我們不需要再去初始化主線(xiàn)程Looper。
為什么主線(xiàn)程的Looper是一個(gè)死循環(huán),但是卻不會(huì)ANR?
答: 因?yàn)楫?dāng)Looper處理完所有消息的時(shí)候會(huì)進(jìn)入阻塞狀態(tài),當(dāng)有新的Message進(jìn)來(lái)的時(shí)候會(huì)打破阻塞繼續(xù)執(zhí)行。
這其實(shí)沒(méi)理解好ANR這個(gè)概念。ANR,全名Application Not Responding。當(dāng)我發(fā)送一個(gè)繪制UI 的消息到主線(xiàn)程Handler之后,經(jīng)過(guò)一定的時(shí)間沒(méi)有被執(zhí)行,則拋出ANR異常。Looper的死循環(huán),是循環(huán)執(zhí)行各種事務(wù),包括UI繪制事務(wù)。Looper死循環(huán)說(shuō)明線(xiàn)程沒(méi)有死亡,如果Looper停止循環(huán),線(xiàn)程則結(jié)束退出了。Looper的死循環(huán)本身就是保證UI繪制任務(wù)可以被執(zhí)行的原因之一。同時(shí)UI繪制任務(wù)有同步屏障,可以更加快速地保證繪制更快執(zhí)行。同步屏障下面會(huì)講。
Handler如何保證MessageQueue并發(fā)訪問(wèn)安全?
答:循環(huán)加鎖,配合阻塞喚醒機(jī)制。
我們可以發(fā)現(xiàn)MessageQueue其實(shí)是“生產(chǎn)者-消費(fèi)者”模型,Handler不斷地放入消息,Looper不斷地取出,這就涉及到死鎖問(wèn)題。如果Looper拿到鎖,但是隊(duì)列中沒(méi)有消息,就會(huì)一直等待,而Handler需要把消息放進(jìn)去,鎖卻被Looper拿著無(wú)法入隊(duì),這就造成了死鎖。Handler機(jī)制的解決方法是循環(huán)加鎖。在MessageQueue的next方法中:
Message next() {
...
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
...
}
}
}
我們可以看到他的等待是在鎖外的,當(dāng)隊(duì)列中沒(méi)有消息的時(shí)候,他會(huì)先釋放鎖,再進(jìn)行等待,直到被喚醒。這樣就不會(huì)造成死鎖問(wèn)題了。
那在入隊(duì)的時(shí)候會(huì)不會(huì)因?yàn)殛?duì)列已經(jīng)滿(mǎn)了然后一邊在等待消息處理一邊拿著鎖呢?這一點(diǎn)不同的是MessageQueue的消息沒(méi)有上限,或者說(shuō)他的上限就是JVM給程序分配的內(nèi)存,如果超出內(nèi)存會(huì)拋出異常,但一般情況下是不會(huì)的。
Handler是如何切換線(xiàn)程的?
答: 使用不同線(xiàn)程的Looper處理消息。
前面我們聊到,代碼的執(zhí)行線(xiàn)程,并不是代碼本身決定,而是執(zhí)行這段代碼的邏輯是在哪個(gè)線(xiàn)程,或者說(shuō)是哪個(gè)線(xiàn)程的邏輯調(diào)用的。每個(gè)Looper都運(yùn)行在對(duì)應(yīng)的線(xiàn)程,所以不同的Looper調(diào)用的dispatchMessage方法就運(yùn)行在其所在的線(xiàn)程了。
Handler的阻塞喚醒機(jī)制是怎么回事?
答: Handler的阻塞喚醒機(jī)制是基于Linux的阻塞喚醒機(jī)制。
這個(gè)機(jī)制也是類(lèi)似于handler機(jī)制的模式。在本地創(chuàng)建一個(gè)文件描述符,然后需要等待的一方則監(jiān)聽(tīng)這個(gè)文件描述符,喚醒的一方只需要修改這個(gè)文件,那么等待的一方就會(huì)收到文件從而打破喚醒。
能不能讓一個(gè)Message加急被處理?/ 什么是Handler同步屏障?
答:可以 / 一種使得異步消息可以被更快處理的機(jī)制
如果向主線(xiàn)程發(fā)送了一個(gè)UI更新的操作Message,而此時(shí)消息隊(duì)列中的消息非常多,那么這個(gè)Message的處理就會(huì)變得緩慢,造成界面卡頓。所以通過(guò)同步屏障,可以使得UI繪制的Message更快被執(zhí)行。
什么是同步屏障?這個(gè)“屏障”其實(shí)是一個(gè)Message,插入在MessageQueue的鏈表頭,且其target==null。Message入隊(duì)的時(shí)候不是判斷了target不能為null嗎?不不不,添加同步屏障是另一個(gè)方法:
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
// 把當(dāng)前需要執(zhí)行的Message全部執(zhí)行
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
// 插入同步屏障
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
可以看到同步屏障就是一個(gè)特殊的target,哪里特殊呢?target==null,我們可以看到他并沒(méi)有給target屬性賦值。那這個(gè)target有什么用呢?看next方法:
Message next() {
...
// 阻塞時(shí)間
int nextPollTimeoutMillis = 0;
for (;;) {
...
// 阻塞對(duì)應(yīng)時(shí)間
nativePollOnce(ptr, nextPollTimeoutMillis);
// 對(duì)MessageQueue進(jìn)行加鎖,保證線(xiàn)程安全
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
/**
* 1
*/
if (msg != null && msg.target == null) {
// 同步屏障,找到下一個(gè)異步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 下一個(gè)消息還沒(méi)開(kāi)始,等待兩者的時(shí)間差
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 獲得消息且現(xiàn)在要執(zhí)行,標(biāo)記MessageQueue為非阻塞
mBlocked = false;
/**
* 2
*/
// 一般只有異步消息才會(huì)從中間拿走消息,同步消息都是從鏈表頭獲取
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 沒(méi)有消息,進(jìn)入阻塞狀態(tài)
nextPollTimeoutMillis = -1;
}
// 當(dāng)調(diào)用Looper.quitSafely()時(shí)候執(zhí)行完所有的消息后就會(huì)退出
if (mQuitting) {
dispose();
return null;
}
...
}
...
}
}
這個(gè)方法我在前面講過(guò),我們重點(diǎn)看一下關(guān)于同步屏障的部分,看注釋1的地方的代碼:
if (msg != null && msg.target == null) {
// 同步屏障,找到下一個(gè)異步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
如果遇到同步屏障,那么會(huì)循環(huán)遍歷整個(gè)鏈表找到標(biāo)記為異步消息的Message,即isAsynchronous返回true,其他的消息會(huì)直接忽視,那么這樣異步消息,就會(huì)提前被執(zhí)行了。注釋2的代碼注意一下就可以了。
注意,同步屏障不會(huì)自動(dòng)移除,使用完成之后需要手動(dòng)進(jìn)行移除,不然會(huì)造成同步消息無(wú)法被處理。從源碼中可以看到如果不移除同步屏障,那么他會(huì)一直在那里,這樣同步消息就永遠(yuǎn)無(wú)法被執(zhí)行了。
有了同步屏障,那么喚醒的判斷條件就必須再加一個(gè):MessageQueue中有同步屏障且處于阻塞中,此時(shí)插入在所有異步消息前插入新的異步消息。這個(gè)也很好理解,跟同步消息是一樣的。如果把所有的同步消息先忽視,就是插入新的鏈表頭且隊(duì)列處于阻塞狀態(tài),這個(gè)時(shí)候就需要被喚醒了。看一下源碼:
boolean enqueueMessage(Message msg, long when) {
...
// 對(duì)MessageQueue進(jìn)行加鎖
synchronized (this) {
...
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
/**
* 1
*/
// 當(dāng)線(xiàn)程被阻塞,且目前有同步屏障,且入隊(duì)的消息是異步消息
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
/**
* 2
*/
// 如果找到一個(gè)異步消息,說(shuō)明前面有延遲的異步消息需要被處理,不需要被喚醒
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
// 如果需要?jiǎng)t喚醒隊(duì)列
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
同樣,這個(gè)方法我之前講過(guò),把無(wú)關(guān)同步屏障的代碼忽視,看到注釋1處的代碼。如果插入的消息是異步消息,且有同步屏障,同時(shí)MessageQueue正處于阻塞狀態(tài),那么就需要喚醒。而如果這個(gè)異步消息的插入位置不是所有異步消息之前,那么不需要喚醒,如注釋2。
那我們?nèi)绾伟l(fā)送一個(gè)異步類(lèi)型的消息呢?有兩種辦法:
使用異步類(lèi)型的Handler發(fā)送的全部Message都是異步的
給Message標(biāo)志異步
Handler有一系列帶Boolean類(lèi)型的參數(shù)的構(gòu)造器,這個(gè)參數(shù)就是決定是否是異步Handler:
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
// 這里賦值
mAsynchronous = async;
}
在發(fā)送消息的時(shí)候就會(huì)給Message賦值:
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);
}
但是異步類(lèi)型的Handler構(gòu)造器是標(biāo)記為hide,我們無(wú)法使用,所以我們使用異步消息只有通過(guò)給Message設(shè)置異步標(biāo)志:
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
但是!!!!,其實(shí)同步屏障對(duì)于我們的日常使用的話(huà)其實(shí)是沒(méi)有多大用處。因?yàn)樵O(shè)置同步屏障和創(chuàng)建異步Handler的方法都是標(biāo)志為hide,說(shuō)明谷歌不想要我們?nèi)ナ褂盟K赃@里同步屏障也作為一個(gè)了解,可以更加全面地理解源碼中的內(nèi)容。
什么是IdleHandler?
答: 當(dāng)MessageQueue為空或者目前沒(méi)有需要執(zhí)行的Message時(shí)會(huì)回調(diào)的接口對(duì)象。
IdleHandler看起來(lái)好像是個(gè)Handler,但他其實(shí)只是一個(gè)有單方法的接口,也稱(chēng)為函數(shù)型接口:
public static interface IdleHandler {
boolean queueIdle();
}
在MessageQueue中有一個(gè)List存儲(chǔ)了IdleHandler對(duì)象,當(dāng)MessageQueue沒(méi)有需要被執(zhí)行的Message時(shí)就會(huì)遍歷回調(diào)所有的IdleHandler。所以IdleHandler主要用于在消息隊(duì)列空閑的時(shí)候處理一些輕量級(jí)的工作。
IdleHandler的調(diào)用是在next方法中:
Message next() {
// 如果looper已經(jīng)退出了,這里就返回null
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
// IdleHandler的數(shù)量
int pendingIdleHandlerCount = -1;
// 阻塞時(shí)間
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 阻塞對(duì)應(yīng)時(shí)間
nativePollOnce(ptr, nextPollTimeoutMillis);
// 對(duì)MessageQueue進(jìn)行加鎖,保證線(xiàn)程安全
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 同步屏障,找到下一個(gè)異步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 下一個(gè)消息還沒(méi)開(kāi)始,等待兩者的時(shí)間差
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 獲得消息且現(xiàn)在要執(zhí)行,標(biāo)記MessageQueue為非阻塞
mBlocked = false;
// 一般只有異步消息才會(huì)從中間拿走消息,同步消息都是從鏈表頭獲取
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 沒(méi)有消息,進(jìn)入阻塞狀態(tài)
nextPollTimeoutMillis = -1;
}
// 當(dāng)調(diào)用Looper.quitSafely()時(shí)候執(zhí)行完所有的消息后就會(huì)退出
if (mQuitting) {
dispose();
return null;
}
// 當(dāng)隊(duì)列中的消息用完了或者都在等待時(shí)間延遲執(zhí)行同時(shí)給pendingIdleHandlerCount<0
// 給pendingIdleHandlerCount賦值MessageQueue中IdleHandler的數(shù)量
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 沒(méi)有需要執(zhí)行的IdleHanlder直接continue
if (pendingIdleHandlerCount <= 0) {
// 執(zhí)行IdleHandler,標(biāo)記MessageQueue進(jìn)入阻塞狀態(tài)
mBlocked = true;
continue;
}
// 把List轉(zhuǎn)化成數(shù)組類(lèi)型
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 執(zhí)行IdleHandler
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // 釋放IdleHandler的引用
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
// 如果返回false,則把IdleHanlder移除
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// 最后設(shè)置pendingIdleHandlerCount為0,防止再執(zhí)行一次
pendingIdleHandlerCount = 0;
// 當(dāng)在執(zhí)行IdleHandler的時(shí)候,可能有新的消息已經(jīng)進(jìn)來(lái)了
// 所以這個(gè)時(shí)候不能阻塞,要回去循環(huán)一次看一下
nextPollTimeoutMillis = 0;
}
}
代碼很多,可能看著有點(diǎn)亂,我梳理一下邏輯,然后再回去看源碼就會(huì)很清晰了:
當(dāng)調(diào)用next方法的時(shí)候,會(huì)給pendingIdleHandlerCount賦值為-1
如果隊(duì)列中沒(méi)有需要處理的消息的時(shí)候,就會(huì)判斷pendingIdleHandlerCount是否為<0,如果是則把存儲(chǔ)IdleHandler的list的長(zhǎng)度賦值給pendingIdleHandlerCount
把list中的所有IdleHandler放到數(shù)組中。這一步是為了不讓在執(zhí)行IdleHandler的時(shí)候List被插入新的IdleHandler,造成邏輯混亂
然后遍歷整個(gè)數(shù)組執(zhí)行所有的IdleHandler
最后給pendingIdleHandlerCount賦值為0。然后再回去看一下這個(gè)期間有沒(méi)有新的消息插入。因?yàn)閜endingIdleHandlerCount的值為0不是-1,所以IdleHandler只會(huì)在空閑的時(shí)候執(zhí)行一次
同時(shí)注意,如果IdleHandler返回了false,那么執(zhí)行一次之后就被丟棄了。
建議讀者再回去把源碼看一遍,這樣邏輯會(huì)清晰很多。
最后
到這里,Handler系列的文章就結(jié)束了。如果有地方遺漏沒(méi)介紹到,或者有疑問(wèn),可評(píng)論區(qū)留言。 感謝閱讀。
大家如果還想了解更多Android 相關(guān)的更多知識(shí)點(diǎn)可以點(diǎn)進(jìn)我的【GitHub】項(xiàng)目中,里面記錄了許多的Android 知識(shí)點(diǎn)。
總結(jié)
以上是生活随笔為你收集整理的android handle 阻塞,Android全面解析之Handler机制:常见问题汇总的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: css --- flex:n的解析
- 下一篇: 视频教程| Egret 打包Androi