Google大佬都用的广播goAsync源码分析

 更新时间:2023年01月13日 15:37:37   作者:程序员DHL  
这篇文章主要为大家介绍了Google大佬都用的广播 goAsync源码分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

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

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 中分别对这两种方式做了处理。

动态处理

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

首先会调用 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();
        }
    }
}

以上就是Google大佬都用的广播 goAsync源码分析的详细内容,更多关于Google广播 goAsync的资料请关注脚本之家其它相关文章!

相关文章

  • 实例讲解Android多线程应用开发中Handler的使用

    实例讲解Android多线程应用开发中Handler的使用

    这篇文章主要介绍了Android多线程应用开发中Handler的使用,Handle主要被用来更新UI和处理消息,需要的朋友可以参考下
    2016-01-01
  • Android控件Spinner的使用方法(1)

    Android控件Spinner的使用方法(1)

    这篇文章主要为大家详细介绍了Android控件Spinner的使用方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • Android应用框架之应用启动过程详解

    Android应用框架之应用启动过程详解

    这篇文章主要为大家详细介绍了Android应用框架,应用启动过程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-11-11
  • Android编程设计模式之单例模式实例详解

    Android编程设计模式之单例模式实例详解

    这篇文章主要介绍了Android编程设计模式之单例模式,结合实例形式详细分析了Android开发设计模式中单例模式的概念、功能、实现、使用方法及相关注意事项,需要的朋友可以参考下
    2017-12-12
  • Android实用控件自定义逼真相机光圈View

    Android实用控件自定义逼真相机光圈View

    这篇文章主要为大家详细介绍了Android实用控件自定义逼真相机光圈,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-08-08
  • Kotlin面向对象知识点讲解

    Kotlin面向对象知识点讲解

    面向对象编程通过对事物的抽象,大大的简化了程序的开发难度。我们常用的编程语言:Java、C++、Python都属于面向对象编程。Kotlin与java类似,也是一种面向对象编程语言。本文从面向对象三个基本特征:封装、继承、多态,来阐述一下Kotlin中的面向对象编程
    2022-12-12
  • Android仿新浪微博oauth2.0授权界面实现代码(2)

    Android仿新浪微博oauth2.0授权界面实现代码(2)

    这篇文章主要为大家详细介绍了Android仿新浪微博oauth2.0授权界面实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-11-11
  • Android UI组件AppWidget控件入门详解

    Android UI组件AppWidget控件入门详解

    这篇文章主要介绍了Android UI组件AppWidget控件入门,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • Kotlin扩展方法超详细介绍

    Kotlin扩展方法超详细介绍

    Kotlin 可以为一个不能修改的或来自第三方库中的类编写一个新的函数。 这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用,这种机制的函数称为扩展函数
    2022-09-09
  • Android编程实现多列显示的下拉列表框Spinner功能示例

    Android编程实现多列显示的下拉列表框Spinner功能示例

    这篇文章主要介绍了Android编程实现多列显示的下拉列表框Spinner功能,结合具体实例形式分析了Android多列表显示功能的相关布局操作实现技巧,需要的朋友可以参考下
    2017-06-06

最新评论