網(wǎng)站首頁 編程語言 正文
引言
近期在分析問題過程中,需要反編譯 Google 的一些庫,在看源碼的時(shí)候,發(fā)現(xiàn)使用廣播的場景都會(huì)手動(dòng)調(diào)用 goAsync()
方法。
goAsync()
是一個(gè)冷門但是非常有用的知識(shí)點(diǎn),很少有文章會(huì)去分析 goAsync()
方法,因此這個(gè)方法在實(shí)際項(xiàng)目中使用的人也非常的少,我之前對這個(gè)方法也只是有一點(diǎn)了解,帶著我的好奇心,研究了一下。
通過這篇文章你將學(xué)習(xí)到以下內(nèi)容:
-
goAsync()
是什么,它的作用是什么 - BroadcastReceiver 如何處理靜態(tài)接受者和動(dòng)態(tài)接受者
- 為什么
goAsync
方法,可以保證廣播處于活躍狀態(tài) - 在什么場景下使用
goAsync()
- 對進(jìn)程的影響
goAsync 是什么
根據(jù) BroadcastReceiver 源碼中的介紹,goAsync()
方法返回 PendingResult
,可以在 BroadcastReceiver.onReceive()
中使用,如果調(diào)用了這個(gè)方法,當(dāng) onReceive()
方法執(zhí)行完返回時(shí),并不會(huì)終止當(dāng)前的廣播,廣播依然處于活躍狀態(tài),直到調(diào)用 PendingResult.finish()
方法,才會(huì)結(jié)束掉當(dāng)前的廣播。
goAsync()
方法并不會(huì)影響廣播超時(shí)的策略,從調(diào)用 goAsync()
方法開始,一直到調(diào)用 finish()
方法結(jié)束,如果超過了源碼中設(shè)置的廣播超時(shí)時(shí)間(10s/60s),依然會(huì)產(chǎn)生 ANR。
為什么 goAsync()
方法,可以保證廣播處于活躍狀態(tài),我們需要先了解一下 BroadcastReceiver
調(diào)度流程,以 android-11.0.0_r3
源碼為例。
BroadcastReceiver 的調(diào)度流程
AMS 和應(yīng)用進(jìn)程之間的通信是通過 ApplicationThread
進(jìn)行的,而廣播處理的方式分為靜態(tài)處理和動(dòng)態(tài)處理,在 ApplicationThread
中分別對這兩種方式做了處理。
動(dòng)態(tài)處理
動(dòng)態(tài)處理流程,如下所示:
首先會(huì)調(diào)用 ActivityThread#ApplicationThread
類中 scheduleRegisteredReceiver
方法,最終會(huì)調(diào)用 LoadedApk#ReceiverDispatcher
類中的 performReceive
方法。
frameworks/base/core/java/android/app/LoadedApk #ReceiverDispatcher . java
public void performReceive(Intent intent, ...) { final Args args = new Args(intent, resultCode, ...); if (intent == null || !mActivityThread.post(args.getRunnable())) { .... } }
通過 mActivityThread. post ()
發(fā)送一個(gè) Runnable, 我看一下 Args 中的 Runnable 實(shí)現(xiàn)。
frameworks/base/core/java/android/app/LoadedApk #ReceiverDispatcher #Args . java
public void Runnable getRunnable() { return () -> { // 這個(gè)是我們注冊的 BroadcastReceiver final BroadcastReceiver receiver = mReceiver; try { ...... // 為注冊的廣播接受者設(shè)置 PendingResult receiver.setPendingResult(this); // 執(zhí)行 BroadcastReceiver#onReceive 方法 receiver.onReceive(mContext, intent); } catch (Exception e) { } // 判斷 PendingResult 是否為空,如果為空,就不會(huì)結(jié)束掉當(dāng)前注冊的 Receiver // 應(yīng)用層可以調(diào)用 BroadcastReceiver.goAsync,將 PendingResult 設(shè)置為null,從而打斷廣播后續(xù)處理流程 if (receiver.getPendingResult() != null) { finish(); } }; }
Runnable 方法實(shí)現(xiàn)分為兩個(gè)部分:
- 執(zhí)行
BroadcastReceiver.onReceive()
方法之前會(huì)設(shè)置PendingResult
- 在
BroadcastReceiver.onReceive()
方法執(zhí)行完后,檢查PendingResult
是否為空,如果為空,就不會(huì)結(jié)束掉當(dāng)前注冊的BroadcastReceiver
靜態(tài)處理
首先會(huì)調(diào)用 ActivityThread#ApplicationThread
類中 scheduleReceiver
方法。
frameworks/base/core/java/android/app/ActivityThread #ApplicationThread . java
public final void scheduleReceiver(Intent intent, ...) { sendMessage(H.RECEIVER, r); }
通過 sendMessage(H.RECEIVER, r)
方法往主線程拋一個(gè) RECEIVER
消息,發(fā)送 RECEIVER
消息的同時(shí)會(huì)攜帶 ReceiverData
實(shí)例,其中 r
是 ReceiverData
實(shí)例, ReceiverData
是 BroadcastReceiver.PendingResult
的子類。
在主線程消息隊(duì)列中接受 RECEIVER
消息,最后會(huì)調(diào)用 ActivityThread
中的 handleMessage
方法。
frameworks/base/core/java/android/app/ActivityThread. java
private void handleReceiver(ReceiverData data) { BroadcastReceiver receiver; try { // 通過反射構(gòu)造一個(gè) BroadcastReceiver 實(shí)例 receiver = packageInfo.getAppFactory() .instantiateReceiver(cl, data.info.name, data.intent); } catch (Exception e) { } ...... try { // 為注冊的廣播接受者設(shè)置 PendingResult // data 是 ReceiverData 實(shí)例, ReceiverData 是 BroadcastReceiver.PendingResult 的子類 receiver.setPendingResult(data); // 執(zhí)行 BroadcastReceiver#onReceive 方法 receiver.onReceive(context.getReceiverRestrictedContext(), data.intent); } catch (Exception e) { ...... } // 判斷 PendingResult 是否為空,如果為空,就不會(huì)結(jié)束掉當(dāng)前注冊的 Receiver // 應(yīng)用層可以調(diào)用 BroadcastReceiver.goAsync,將 PendingResult 設(shè)置為 null,從而打斷廣播后續(xù)處理流程 if (receiver.getPendingResult() != null) { data.finish(); } }
handleMessage
方法實(shí)現(xiàn)分為兩個(gè)部分:
- 通過反射構(gòu)造一個(gè) BroadcastReceiver 實(shí)例
- 執(zhí)行
BroadcastReceiver.onReceive()
方法之前會(huì)設(shè)置PendingResult
- 在
BroadcastReceiver.onReceive()
方法執(zhí)行完后,檢查PendingResult
是否為空,如果為空,就不會(huì)結(jié)束掉當(dāng)前注冊的BroadcastReceiver
靜態(tài)處理和動(dòng)態(tài)處理,最終的處理流程都是一樣的,唯一的區(qū)別靜態(tài)處理是通過反射構(gòu)造一個(gè) BroadcastReceiver 實(shí)例。
為什么 goAsync 方法,可以保證廣播處于活躍狀態(tài)
通過上面的源碼分析,我們可以知道只需要將 PendingResult
設(shè)置為 null,不會(huì)馬上結(jié)束掉當(dāng)前的廣播,相當(dāng)于 "延長了廣播的生命周期",因此 Google 提供了 goAsync()
方法給開發(fā)者調(diào)用,當(dāng)調(diào)用 goAsync()
時(shí),不會(huì)結(jié)束掉當(dāng)前的廣播,讓廣播依然處于活躍狀態(tài)。goAsync()
方法的實(shí)現(xiàn)很簡單。
public final PendingResult goAsync() { PendingResult res = mPendingResult; mPendingResult = null; return res; }
goAsync()
方法主要將 PendingResult
設(shè)置為 null,當(dāng) BroadcastReceiver.onReceive()
方法執(zhí)行結(jié)束,會(huì)檢查 PendingResult
是否為 null,如果為 null 不會(huì)結(jié)束掉當(dāng)前的 BroadcastReceiver
,需要開發(fā)者在合適的時(shí)機(jī)主動(dòng)調(diào)用 PendingResult.finish()
方法,手動(dòng)結(jié)束掉當(dāng)前 BroadcastReceiver
,否則會(huì)觸發(fā)廣播的超時(shí)機(jī)制(10s/60s) 發(fā)生 ANR。
對進(jìn)程的影響
BroadcastReceiver 的狀態(tài)會(huì)影響其所在進(jìn)程的狀態(tài),而進(jìn)程的狀態(tài)又會(huì)影響它被系統(tǒng)回收的可能性。因?yàn)榍芭_(tái)進(jìn)程和后臺(tái)進(jìn)程,系統(tǒng)對它們的影響是不同的。
如何區(qū)分前臺(tái)進(jìn)程
如果滿足以下任一條件,則進(jìn)程會(huì)被認(rèn)為位于前臺(tái)。
- 它正在用戶的互動(dòng)屏幕上運(yùn)行一個(gè) Activity(其
onResume()
方法已被調(diào)用)。 - 它有一個(gè) BroadcastReceiver 目前正在運(yùn)行(其
BroadcastReceiver.onReceive()
方法正在執(zhí)行) - 它有一個(gè) Service 目前正在執(zhí)行其某個(gè)回調(diào)(
Service.onCreate()
、Service.onStart()
或Service.onDestroy()
)中的代碼。
所以你不應(yīng)該在 onReceive()
中啟動(dòng)一個(gè)長時(shí)間運(yùn)行的子線程,當(dāng) onReceive()
方法執(zhí)行完返回時(shí),BroadcastReceiver
就不再活躍,系統(tǒng)會(huì)將其進(jìn)程視為低優(yōu)先級進(jìn)程,系統(tǒng)會(huì)根據(jù)內(nèi)存情況來回收,在此過程中,也會(huì)終止進(jìn)程中運(yùn)行的派生線程。
所以如果你要在子線程中運(yùn)行一個(gè)長時(shí)間的任務(wù),我們可以使用 goAsync()
方法,它會(huì)中斷廣播后續(xù)處理流程,讓 BroadcastReceiver
處于活躍狀態(tài),即使 onReceive()
方法執(zhí)行完,也不會(huì)結(jié)束掉當(dāng)前 BroadcastReceiver,除非主動(dòng)調(diào)用 PendingResult.finish()
方法。
在什么場景下使用 goAsync
BroadcastReceiver. onReceive ()
方法運(yùn)行在主線程中,如果我們在主線程做耗時(shí)任務(wù)就會(huì)出現(xiàn) ANR。
PS:關(guān)于廣播 ANR 發(fā)生的場景、解決方案、源碼分析,將會(huì)在后面穩(wěn)定性系列文章中分析
如果有耗時(shí)任務(wù),大部分同學(xué)的做法是,直接在 onReceive ()
方法中起子線程處理耗時(shí)任務(wù),當(dāng) onReceive () 方法返回時(shí),BroadcastReceiver
不會(huì)在處于活躍狀態(tài),那么廣播所在的進(jìn)程也會(huì)受到影響,如果當(dāng)前 BroadcastReceiver
所在的進(jìn)程被系統(tǒng)回收了,那么子線程中的任務(wù)也會(huì)受到影響。
一般的處理方式會(huì)通過 IntentService、JobService 方式,保證任務(wù)能夠正常的執(zhí)行完,但是使用 Service 的方式會(huì)帶來很多的問題,因?yàn)?Service 是通過 AMS 進(jìn)行跨進(jìn)程調(diào)度,AMS 調(diào)度也會(huì)有超時(shí)機(jī)制,如果因?yàn)橄到y(tǒng)原因,或者未知原因,導(dǎo)致 AMS 調(diào)度延遲了,ANR 的概率會(huì)增大,而且代碼的復(fù)雜度也變高了。
Google 也注意到這一點(diǎn),所以在 BroadcastReceiver
調(diào)度流程中留出來一個(gè)入口。增加了一個(gè)靜態(tài)內(nèi)部類 PendingResult,并且提供了 goAsync ()
方法給開發(fā)者調(diào)用,如果你需要運(yùn)行一個(gè)長時(shí)間的任務(wù),在切換到子線程之前,需要調(diào)用 goAsync ()
方法,讓廣播處于活躍狀態(tài),在系統(tǒng)限制的時(shí)間內(nèi),處理完任務(wù)之后,主動(dòng)調(diào)用 PendingResult. finish ()
方法,結(jié)束掉當(dāng)前的廣播。
如何使用 goAsync
這里我以 Google play services cloud messaging 中的源碼為例。
public abstract class CloudMessagingReceiver extends BroadcastReceiver { public final void onReceive(final Context context, final Intent intent) { // 調(diào)用 goAsync() 返回新的 PendingResult,并將原 PendingResult 設(shè)置為 null final BroadcastReceiver.PendingResult goAsync = goAsync(); // 開啟線程處理接受的消息,并將 goAsync 傳遞到子線程 getBroadcastExecutor().execute(new Runnable() { @Override public final void run() { parseIntent(intent, , goAsync); } }); } public final void parseIntent(Intent intent, BroadcastReceiver.PendingResult goAsync) { try { /** * 處理耗時(shí)任務(wù),如果任務(wù)在限定時(shí)間內(nèi)處理完所有消息,主動(dòng)調(diào)用 goAsync.finish() 方法結(jié)束當(dāng)前的 Receiver **/ } finally { goAsync.finish(); } } }
原文鏈接:https://juejin.cn/post/7179800784904093755
相關(guān)推薦
- 2021-12-06 C/C++?Qt?數(shù)據(jù)庫與ComBox實(shí)現(xiàn)多級聯(lián)動(dòng)示例代碼_C 語言
- 2022-05-22 查看Docker容器的信息的方法實(shí)現(xiàn)_docker
- 2022-05-09 Redis擊穿穿透雪崩產(chǎn)生原因分析及解決思路面試_Redis
- 2022-06-04 Android自定義ScrollView實(shí)現(xiàn)阻尼回彈_Android
- 2023-03-20 C#?獲取XML文件內(nèi)容的多種方式總結(jié)_C#教程
- 2022-06-02 Android?實(shí)例代碼帶你掌握FrameLayout_Android
- 2023-04-02 vscode?ssh遠(yuǎn)程連接服務(wù)器的思考淺析_相關(guān)技巧
- 2023-03-23 C語言中的字符型數(shù)據(jù)與ASCII碼表_C 語言
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支