日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

android handle 阻塞,Android全面解析之Handler机制:常见问题汇总

發(fā)布時(shí)間:2023/12/10 Android 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android handle 阻塞,Android全面解析之Handler机制:常见问题汇总 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

主線(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)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。