Google大佬都用的廣播goAsync源碼分析
引言
近期在分析問題過程中,需要反編譯 Google 的一些庫,在看源碼的時候,發(fā)現(xiàn)使用廣播的場景都會手動調(diào)用 goAsync()
方法。
goAsync()
是一個冷門但是非常有用的知識點,很少有文章會去分析 goAsync()
方法,因此這個方法在實際項目中使用的人也非常的少,我之前對這個方法也只是有一點了解,帶著我的好奇心,研究了一下。
通過這篇文章你將學(xué)習(xí)到以下內(nèi)容:
goAsync()
是什么,它的作用是什么- BroadcastReceiver 如何處理靜態(tài)接受者和動態(tài)接受者
- 為什么
goAsync
方法,可以保證廣播處于活躍狀態(tài) - 在什么場景下使用
goAsync()
- 對進程的影響
goAsync 是什么
根據(jù) BroadcastReceiver 源碼中的介紹,goAsync()
方法返回 PendingResult
,可以在 BroadcastReceiver.onReceive()
中使用,如果調(diào)用了這個方法,當(dāng) onReceive()
方法執(zhí)行完返回時,并不會終止當(dāng)前的廣播,廣播依然處于活躍狀態(tài),直到調(diào)用 PendingResult.finish()
方法,才會結(jié)束掉當(dāng)前的廣播。
goAsync()
方法并不會影響廣播超時的策略,從調(diào)用 goAsync()
方法開始,一直到調(diào)用 finish()
方法結(jié)束,如果超過了源碼中設(shè)置的廣播超時時間(10s/60s),依然會產(chǎn)生 ANR。
為什么 goAsync()
方法,可以保證廣播處于活躍狀態(tài),我們需要先了解一下 BroadcastReceiver
調(diào)度流程,以 android-11.0.0_r3
源碼為例。
BroadcastReceiver 的調(diào)度流程
AMS 和應(yīng)用進程之間的通信是通過 ApplicationThread
進行的,而廣播處理的方式分為靜態(tài)處理和動態(tài)處理,在 ApplicationThread
中分別對這兩種方式做了處理。
動態(tài)處理
動態(tài)處理流程,如下所示:
首先會調(diào)用 ActivityThread#ApplicationThread
類中 scheduleRegisteredReceiver
方法,最終會調(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ā)送一個 Runnable, 我看一下 Args 中的 Runnable 實現(xiàn)。
frameworks/base/core/java/android/app/LoadedApk #ReceiverDispatcher #Args . java
public void Runnable getRunnable() { return () -> { // 這個是我們注冊的 BroadcastReceiver final BroadcastReceiver receiver = mReceiver; try { ...... // 為注冊的廣播接受者設(shè)置 PendingResult receiver.setPendingResult(this); // 執(zhí)行 BroadcastReceiver#onReceive 方法 receiver.onReceive(mContext, intent); } catch (Exception e) { } // 判斷 PendingResult 是否為空,如果為空,就不會結(jié)束掉當(dāng)前注冊的 Receiver // 應(yīng)用層可以調(diào)用 BroadcastReceiver.goAsync,將 PendingResult 設(shè)置為null,從而打斷廣播后續(xù)處理流程 if (receiver.getPendingResult() != null) { finish(); } }; }
Runnable 方法實現(xiàn)分為兩個部分:
- 執(zhí)行
BroadcastReceiver.onReceive()
方法之前會設(shè)置PendingResult
- 在
BroadcastReceiver.onReceive()
方法執(zhí)行完后,檢查PendingResult
是否為空,如果為空,就不會結(jié)束掉當(dāng)前注冊的BroadcastReceiver
靜態(tài)處理
首先會調(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)
方法往主線程拋一個 RECEIVER
消息,發(fā)送 RECEIVER
消息的同時會攜帶 ReceiverData
實例,其中 r
是 ReceiverData
實例, ReceiverData
是 BroadcastReceiver.PendingResult
的子類。
在主線程消息隊列中接受 RECEIVER
消息,最后會調(diào)用 ActivityThread
中的 handleMessage
方法。
frameworks/base/core/java/android/app/ActivityThread. java
private void handleReceiver(ReceiverData data) { BroadcastReceiver receiver; try { // 通過反射構(gòu)造一個 BroadcastReceiver 實例 receiver = packageInfo.getAppFactory() .instantiateReceiver(cl, data.info.name, data.intent); } catch (Exception e) { } ...... try { // 為注冊的廣播接受者設(shè)置 PendingResult // data 是 ReceiverData 實例, ReceiverData 是 BroadcastReceiver.PendingResult 的子類 receiver.setPendingResult(data); // 執(zhí)行 BroadcastReceiver#onReceive 方法 receiver.onReceive(context.getReceiverRestrictedContext(), data.intent); } catch (Exception e) { ...... } // 判斷 PendingResult 是否為空,如果為空,就不會結(jié)束掉當(dāng)前注冊的 Receiver // 應(yīng)用層可以調(diào)用 BroadcastReceiver.goAsync,將 PendingResult 設(shè)置為 null,從而打斷廣播后續(xù)處理流程 if (receiver.getPendingResult() != null) { data.finish(); } }
handleMessage
方法實現(xiàn)分為兩個部分:
- 通過反射構(gòu)造一個 BroadcastReceiver 實例
- 執(zhí)行
BroadcastReceiver.onReceive()
方法之前會設(shè)置PendingResult
- 在
BroadcastReceiver.onReceive()
方法執(zhí)行完后,檢查PendingResult
是否為空,如果為空,就不會結(jié)束掉當(dāng)前注冊的BroadcastReceiver
靜態(tài)處理和動態(tài)處理,最終的處理流程都是一樣的,唯一的區(qū)別靜態(tài)處理是通過反射構(gòu)造一個 BroadcastReceiver 實例。
為什么 goAsync 方法,可以保證廣播處于活躍狀態(tài)
通過上面的源碼分析,我們可以知道只需要將 PendingResult
設(shè)置為 null,不會馬上結(jié)束掉當(dāng)前的廣播,相當(dāng)于 "延長了廣播的生命周期",因此 Google 提供了 goAsync()
方法給開發(fā)者調(diào)用,當(dāng)調(diào)用 goAsync()
時,不會結(jié)束掉當(dāng)前的廣播,讓廣播依然處于活躍狀態(tài)。goAsync()
方法的實現(xiàn)很簡單。
public final PendingResult goAsync() { PendingResult res = mPendingResult; mPendingResult = null; return res; }
goAsync()
方法主要將 PendingResult
設(shè)置為 null,當(dāng) BroadcastReceiver.onReceive()
方法執(zhí)行結(jié)束,會檢查 PendingResult
是否為 null,如果為 null 不會結(jié)束掉當(dāng)前的 BroadcastReceiver
,需要開發(fā)者在合適的時機主動調(diào)用 PendingResult.finish()
方法,手動結(jié)束掉當(dāng)前 BroadcastReceiver
,否則會觸發(fā)廣播的超時機制(10s/60s) 發(fā)生 ANR。
對進程的影響
BroadcastReceiver 的狀態(tài)會影響其所在進程的狀態(tài),而進程的狀態(tài)又會影響它被系統(tǒng)回收的可能性。因為前臺進程和后臺進程,系統(tǒng)對它們的影響是不同的。
如何區(qū)分前臺進程
如果滿足以下任一條件,則進程會被認為位于前臺。
- 它正在用戶的互動屏幕上運行一個 Activity(其
onResume()
方法已被調(diào)用)。 - 它有一個 BroadcastReceiver 目前正在運行(其
BroadcastReceiver.onReceive()
方法正在執(zhí)行) - 它有一個 Service 目前正在執(zhí)行其某個回調(diào)(
Service.onCreate()
、Service.onStart()
或Service.onDestroy()
)中的代碼。
所以你不應(yīng)該在 onReceive()
中啟動一個長時間運行的子線程,當(dāng) onReceive()
方法執(zhí)行完返回時,BroadcastReceiver
就不再活躍,系統(tǒng)會將其進程視為低優(yōu)先級進程,系統(tǒng)會根據(jù)內(nèi)存情況來回收,在此過程中,也會終止進程中運行的派生線程。
所以如果你要在子線程中運行一個長時間的任務(wù),我們可以使用 goAsync()
方法,它會中斷廣播后續(xù)處理流程,讓 BroadcastReceiver
處于活躍狀態(tài),即使 onReceive()
方法執(zhí)行完,也不會結(jié)束掉當(dāng)前 BroadcastReceiver,除非主動調(diào)用 PendingResult.finish()
方法。
在什么場景下使用 goAsync
BroadcastReceiver. onReceive ()
方法運行在主線程中,如果我們在主線程做耗時任務(wù)就會出現(xiàn) ANR。
PS:關(guān)于廣播 ANR 發(fā)生的場景、解決方案、源碼分析,將會在后面穩(wěn)定性系列文章中分析
如果有耗時任務(wù),大部分同學(xué)的做法是,直接在 onReceive ()
方法中起子線程處理耗時任務(wù),當(dāng) onReceive () 方法返回時,BroadcastReceiver
不會在處于活躍狀態(tài),那么廣播所在的進程也會受到影響,如果當(dāng)前 BroadcastReceiver
所在的進程被系統(tǒng)回收了,那么子線程中的任務(wù)也會受到影響。
一般的處理方式會通過 IntentService、JobService 方式,保證任務(wù)能夠正常的執(zhí)行完,但是使用 Service 的方式會帶來很多的問題,因為 Service 是通過 AMS 進行跨進程調(diào)度,AMS 調(diào)度也會有超時機制,如果因為系統(tǒng)原因,或者未知原因,導(dǎo)致 AMS 調(diào)度延遲了,ANR 的概率會增大,而且代碼的復(fù)雜度也變高了。
Google 也注意到這一點,所以在 BroadcastReceiver
調(diào)度流程中留出來一個入口。增加了一個靜態(tài)內(nèi)部類 PendingResult,并且提供了 goAsync ()
方法給開發(fā)者調(diào)用,如果你需要運行一個長時間的任務(wù),在切換到子線程之前,需要調(diào)用 goAsync ()
方法,讓廣播處于活躍狀態(tài),在系統(tǒng)限制的時間內(nèi),處理完任務(wù)之后,主動調(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 { /** * 處理耗時任務(wù),如果任務(wù)在限定時間內(nèi)處理完所有消息,主動調(diào)用 goAsync.finish() 方法結(jié)束當(dāng)前的 Receiver **/ } finally { goAsync.finish(); } } }
以上就是Google大佬都用的廣播 goAsync源碼分析的詳細內(nèi)容,更多關(guān)于Google廣播 goAsync的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
實例講解Android多線程應(yīng)用開發(fā)中Handler的使用
這篇文章主要介紹了Android多線程應(yīng)用開發(fā)中Handler的使用,Handle主要被用來更新UI和處理消息,需要的朋友可以參考下2016-01-01Android應(yīng)用框架之應(yīng)用啟動過程詳解
這篇文章主要為大家詳細介紹了Android應(yīng)用框架,應(yīng)用啟動過程,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11- 面向?qū)ο缶幊掏ㄟ^對事物的抽象,大大的簡化了程序的開發(fā)難度。我們常用的編程語言:Java、C++、Python都屬于面向?qū)ο缶幊?。Kotlin與java類似,也是一種面向?qū)ο缶幊陶Z言。本文從面向?qū)ο笕齻€基本特征:封裝、繼承、多態(tài),來闡述一下Kotlin中的面向?qū)ο缶幊?/div> 2022-12-12
Android仿新浪微博oauth2.0授權(quán)界面實現(xiàn)代碼(2)
這篇文章主要為大家詳細介紹了Android仿新浪微博oauth2.0授權(quán)界面實現(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11Android編程實現(xiàn)多列顯示的下拉列表框Spinner功能示例
這篇文章主要介紹了Android編程實現(xiàn)多列顯示的下拉列表框Spinner功能,結(jié)合具體實例形式分析了Android多列表顯示功能的相關(guān)布局操作實現(xiàn)技巧,需要的朋友可以參考下2017-06-06最新評論