首页 > 编程语言 >广播 goAsync 源码分析,为什么 Google 大佬都在使用它

广播 goAsync 源码分析,为什么 Google 大佬都在使用它

时间:2022-12-25 11:02:06浏览次数:38  
标签:BroadcastReceiver goAsync 广播 源码 onReceive PendingResult 方法 Google

hi 大家好,我是 DHL。公众号:ByteCode ,专注有用、有趣的硬核原创内容,Kotlin、Jetpack、性能优化、系统源码、算法及数据结构、大厂面经。

近期在分析问题过程中,需要反编译 Google 的一些库,在看源码的时候,发现使用广播的场景都会手动调用 ​​goAsync()​​ 方法。

广播 goAsync 源码分析,为什么 Google 大佬都在使用它_Android

​goAsync()​​​ 是一个冷门但是非常有用的知识点,很少有文章会去分析 ​​goAsync()​​ 方法,因此这个方法在实际项目中使用的人也非常的少,我之前对这个方法也只是有一点了解,带着我的好奇心,研究了一下。

通过这篇文章你将学习到以下内容:

  • ​goAsync()​​ 是什么,它的作用是什么
  • BroadcastReceiver 如何处理静态接受者和动态接受者
  • 为什么 ​​goAsync​​ 方法,可以保证广播处于活跃状态
  • 在什么场景下使用 ​​goAsync()​
  • 对进程的影响

goAsync 是什么

根据 ​​BroadcastReceiver​​ 源码中的介绍,​​goAsync()​​​ 方法返回 ​​PendingResult​​​,可以在 ​​BroadcastReceiver.onReceive()​​​ 中使用,如果调用了这个方法,当 ​​onReceive()​​​ 方法执行完返回时,并不会终止当前的广播,广播依然处于活跃状态,直到调用 ​​PendingResult.finish()​​ 方法,才会结束掉当前的广播。

​goAsync()​​​ 方法并不会影响广播超时的策略,从调用 ​​goAsync()​​​ 方法开始,一直到调用 ​​finish()​​ 方法结束,如果超过了源码中设置的广播超时时间(10s/60s),依然会产生 ANR。

为什么 ​​goAsync()​​​ 方法,可以保证广播处于活跃状态,我们需要先了解一下 ​​BroadcastReceiver​​​ 调度流程,以 ​​android-11.0.0_r3​​ 源码为例。

BroadcastReceiver 的调度流程

AMS 和应用进程之间的通信是通过 ​​ApplicationThread​​​ 进行的,而广播处理的方式分为静态处理和动态处理,在 ​​ApplicationThread​​ 中分别对这两种方式做了处理。

动态处理

动态处理流程,如下所示:

广播 goAsync 源码分析,为什么 Google 大佬都在使用它_Async_02

首先会调用 ​​ActivityThread#ApplicationThread​​​ 类中 ​​scheduleRegisteredReceiver​​​ 方法,最终会调用 ​​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 ()​​​ 发送一个 Runnable, 我看一下 Args 中的 Runnable 实现。
frameworks/base/core/java/android/app/LoadedApk #ReceiverDispatcher #Args . java

public void Runnable getRunnable() {
return () -> {
// 这个是我们注册的 BroadcastReceiver
final BroadcastReceiver receiver = mReceiver;
try {
......
// 为注册的广播接受者设置 PendingResult
receiver.setPendingResult(this);

// 执行 BroadcastReceiver#onReceive 方法
receiver.onReceive(mContext, intent);
} catch (Exception e) {
}

// 判断 PendingResult 是否为空,如果为空,就不会结束掉当前注册的 Receiver
// 应用层可以调用 BroadcastReceiver.goAsync,将 PendingResult 设置为null,从而打断广播后续处理流程
if (receiver.getPendingResult() != null) {
finish();
}
};
}

Runnable 方法实现分为两个部分:

  • 执行 ​​BroadcastReceiver.onReceive()​​​ 方法之前会设置 ​​PendingResult​
  • 在 ​​BroadcastReceiver.onReceive()​​​ 方法执行完后,检查 ​​PendingResult​​​ 是否为空,如果为空,就不会结束掉当前注册的 ​​BroadcastReceiver​

静态处理

首先会调用 ​​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​​​ 消息,发送 ​​RECEIVER​​​ 消息的同时会携带 ​​ReceiverData​​​ 实例,其中 ​​r​​​ 是 ​​ReceiverData​​​ 实例, ​​ReceiverData​​​ 是 ​​BroadcastReceiver.PendingResult​​ 的子类。

在主线程消息队列中接受 ​​RECEIVER​​​ 消息,最后会调用 ​​ActivityThread​​​ 中的 ​​handleMessage​​​ 方法。
frameworks/base/core/java/android/app/ActivityThread. java

private void handleReceiver(ReceiverData data) {
BroadcastReceiver receiver;
try {
// 通过反射构造一个 BroadcastReceiver 实例
receiver = packageInfo.getAppFactory()
.instantiateReceiver(cl, data.info.name, data.intent);
} catch (Exception e) {

}

......

try {
// 为注册的广播接受者设置 PendingResult
// data 是 ReceiverData 实例, ReceiverData 是 BroadcastReceiver.PendingResult 的子类
receiver.setPendingResult(data);

// 执行 BroadcastReceiver#onReceive 方法
receiver.onReceive(context.getReceiverRestrictedContext(),
data.intent);
} catch (Exception e) {
......
}

// 判断 PendingResult 是否为空,如果为空,就不会结束掉当前注册的 Receiver
// 应用层可以调用 BroadcastReceiver.goAsync,将 PendingResult 设置为 null,从而打断广播后续处理流程
if (receiver.getPendingResult() != null) {
data.finish();
}
}

​handleMessage​​ 方法实现分为两个部分:

  • 通过反射构造一个 BroadcastReceiver 实例
  • 执行 ​​BroadcastReceiver.onReceive()​​​ 方法之前会设置 ​​PendingResult​
  • 在 ​​BroadcastReceiver.onReceive()​​​ 方法执行完后,检查 ​​PendingResult​​​ 是否为空,如果为空,就不会结束掉当前注册的 ​​BroadcastReceiver​

静态处理和动态处理,最终的处理流程都是一样的,唯一的区别静态处理是通过反射构造一个 BroadcastReceiver 实例。

为什么 goAsync 方法,可以保证广播处于活跃状态

通过上面的源码分析,我们可以知道只需要将 ​​PendingResult​​​ 设置为 null,不会马上结束掉当前的广播,相当于 "延长了广播的生命周期",因此 Google 提供了 ​​goAsync()​​​ 方法给开发者调用,当调用 ​​goAsync()​​​ 时,不会结束掉当前的广播,让广播依然处于活跃状态。​​goAsync()​​ 方法的实现很简单。

public final PendingResult goAsync() {
PendingResult res = mPendingResult;
mPendingResult = null;
return res;
}

​goAsync()​​​ 方法主要将 ​​PendingResult​​​ 设置为 null,当 ​​BroadcastReceiver.onReceive()​​​ 方法执行结束,会检查 ​​PendingResult​​​ 是否为 null,如果为 null 不会结束掉当前的 ​​BroadcastReceiver​​​,需要开发者在合适的时机主动调用 ​​PendingResult.finish()​​​ 方法,手动结束掉当前 ​​BroadcastReceiver​​,否则会触发广播的超时机制(10s/60s) 发生 ANR。

对进程的影响

BroadcastReceiver 的状态会影响其所在进程的状态,而进程的状态又会影响它被系统回收的可能性。因为前台进程和后台进程,系统对它们的影响是不同的。

如何区分前台进程

如果满足以下任一条件,则进程会被认为位于前台。

  • 它正在用户的互动屏幕上运行一个 Activity(其 ​​onResume()​​ 方法已被调用)。
  • 它有一个 BroadcastReceiver 目前正在运行(其 ​​BroadcastReceiver.onReceive()​​ 方法正在执行)
  • 它有一个 Service 目前正在执行其某个回调(​​Service.onCreate()​​​、​​Service.onStart()​​​ 或 ​​Service.onDestroy()​​)中的代码。

所以你不应该在 ​​onReceive()​​​ 中启动一个长时间运行的子线程,当 ​​onReceive()​​​ 方法执行完返回时,​​BroadcastReceiver​​ 就不再活跃,系统会将其进程视为低优先级进程,系统会根据内存情况来回收,在此过程中,也会终止进程中运行的派生线程。

所以如果你要在子线程中运行一个长时间的任务,我们可以使用 ​​goAsync()​​​ 方法,它会中断广播后续处理流程,让 ​​BroadcastReceiver​​​ 处于活跃状态,即使 ​​onReceive()​​​ 方法执行完,也不会结束掉当前 BroadcastReceiver,除非主动调用 ​​PendingResult.finish()​​ 方法。

在什么场景下使用 goAsync

​BroadcastReceiver. onReceive ()​​ 方法运行在主线程中,如果我们在主线程做耗时任务就会出现 ANR。

PS:关于广播 ANR 发生的场景、解决方案、源码分析,将会在后面稳定性系列文章中分析

如果有耗时任务,大部分同学的做法是,直接在 ​​onReceive ()​​​ 方法中起子线程处理耗时任务,当 onReceive () 方法返回时,​​BroadcastReceiver​​​ 不会在处于活跃状态,那么广播所在的进程也会受到影响,如果当前 ​​BroadcastReceiver​​ 所在的进程被系统回收了,那么子线程中的任务也会受到影响。

一般的处理方式会通过 IntentService、JobService 方式,保证任务能够正常的执行完,但是使用 Service 的方式会带来很多的问题,因为 Service 是通过 AMS 进行跨进程调度,AMS 调度也会有超时机制,如果因为系统原因,或者未知原因,导致 AMS 调度延迟了,ANR 的概率会增大,而且代码的复杂度也变高了。

Google 也注意到这一点,所以在 ​​BroadcastReceiver​​​ 调度流程中留出来一个入口。增加了一个静态内部类 PendingResult,并且提供了 ​​goAsync ()​​​ 方法给开发者调用,如果你需要运行一个长时间的任务,在切换到子线程之前,需要调用 ​​goAsync ()​​​ 方法,让广播处于活跃状态,在系统限制的时间内,处理完任务之后,主动调用 ​​PendingResult. finish ()​​ 方法,结束掉当前的广播。

如何使用 goAsync

这里我以 Google play services cloud messaging 中的源码为例。

public abstract class CloudMessagingReceiver extends BroadcastReceiver {
public final void onReceive(final Context context, final Intent intent) {
// 调用 goAsync() 返回新的 PendingResult,并将原 PendingResult 设置为 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 {
/**
* 处理耗时任务,如果任务在限定时间内处理完所有消息,主动调用 goAsync.finish() 方法结束当前的 Receiver
**/
} finally {
goAsync.finish();
}
}
}


全文到这里就结束了,感谢你的阅读,坚持原创不易,欢迎在看、点赞、分享给身边的小伙伴,我会持续分享原创干货!!!


我开了一个云同步编译工具(SyncKit),主要用于本地写代码,同步到远程设备,在远程设备上进行编译,最后将编译的结果同步到本地,代码已经上传到 Github,欢迎前往仓库 ​​hi-dhl/SyncKit​​ 查看。

  • ​​仓库 SyncKit:https://github.com/hi-dhl/SyncKit​​
  • ​​下载地址:https://github.com/hi-dhl/SyncKit/releases​​

真诚推荐你关注我,公众号:ByteCode ,持续分享硬核原创内容,Kotlin、Jetpack、性能优化、系统源码、算法及数据结构、动画、大厂面经。

​​公众号 :ByteCode​

​​哔哩哔哩​

​​博客​

​​Github​


开源新项目

  • 云同步编译工具(SyncKit),本地写代码,远程编译,欢迎前去查看 SyncKit
  • KtKit 小巧而实用,用 Kotlin 语言编写的工具库,欢迎前去查看 KtKit
  • 最全、最新的 AndroidX Jetpack 相关组件的实战项目以及相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,欢迎前去查看 AndroidX-Jetpack-Practice
  • LeetCode / 剑指 offer,包含多种解题思路、时间复杂度、空间复杂度分析,在线阅读


标签:BroadcastReceiver,goAsync,广播,源码,onReceive,PendingResult,方法,Google
From: https://blog.51cto.com/u_13238266/5967972

相关文章