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

近期在剖析问题过程中,需求反编译 Google 的一些库,在看源码的时候,发现运用播送的场景都会手动调用 goAsync() 办法。

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

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 大佬都在使用它

首先会调用 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 实例,其中 rReceiverData 实例, ReceiverDataBroadcastReceiver.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

最新文章

  • 国外大厂面试题, 7 个 Android Lifecycle 重要的知识点
  • Android 13这些权限抛弃,你的运用受影响了吗?
  • Android 12 已来,你的 App 溃散了吗?
  • Android 利器,我开发了云同步编译东西
  • Twitter 上风趣的代码
  • 谁动了我的内存,揭秘 OOM 溃散下降 90% 的秘密
  • 反射技巧让你的功能提高 N 倍
  • 90%人不明白的泛型局限性,泛型擦除,星投影
  • 揭秘反射真的很耗时吗,射 10 万次耗时多久
  • Google 宣告抛弃 LiveData.observe 办法
  • 影响功能的 Kotlin 代码(一)
  • 揭秘 Kotlin 中的 == 和 ===

开源新项目

  • 云同步编译东西(SyncKit),本地写代码,长途编译,欢迎前去检查 SyncKit

  • KtKit 小巧而实用,用 Kotlin 语言编写的东西库,欢迎前去检查 KtKit

  • 最全、最新的 AndroidX Jetpack 相关组件的实战项目以及相关组件原理剖析文章,正在逐渐添加 Jetpack 新成员,库房继续更新,欢迎前去检查 AndroidX-Jetpack-Practice

  • LeetCode / 剑指 offer,包括多种解题思路、时刻复杂度、空间复杂度剖析,在线阅览