热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android广播事件流程与广播ANR原理深入刨析

这篇文章主要介绍了Android广播事件流程与广播ANR原理,ANR应用程序未响应,当主线程被阻塞时,就会弹出如下弹窗,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可任意参考一下

序言

本想写广播流程中ANR是如何触发的,但是如果想讲清楚ANR的流程,就不得不提整个广播事件的流程,所以就把两块内容合并在一起讲了。

本文会讲内容如下:

1.动态注册广播的整个分发流程,从广播发出,一直到广播注册者接收。

2.广播类型ANR的判断流程和原理。

PS:本文基于android13的源码进行讲解。

一.基本流程和概念

动态广播的流程其实是很简单的,整套流程都在java层执行,不涉及到native流程。

我们先来看一下最基本的流程图:

首先是广播注册者,向AMS注册广播接收器,AMS交给BroadcastQueue来处理。也就是说BroadcastQueue中装载所有的广播接收器。然后广播发出者向AMS发出广播,这时候AMS仍然会交给BroadcastQueue来处理,在其中找到符合条件的接受者,然后向接受者发出广播的通知。

然后我们再来了解一些最基本类的功能

ContextImp:Context的最终实现类,activity,application中的各种功能最终都是委托给其来处理的,广播功能自然也不例外。

ActivityManagerService:负责所有应用的Activity的流程,包括广播六层呢。至于为什么广播也由其来进行处理,而不是搞一个BroadcastManagerService,估计是觉得广播功能简单不需要单独设计Service吧。

BroadCastQueue:装载所有的动态广播接收器,静态广播也是由其来负责加载的,负责具体广播的分发工作。一种有三种类型,前台,后台,离线。

BroadcastReceiver:广播注册者

二.无序广播流程

首先我们了解下什么是无需广播和有序广播。简单来说,有序广播就是需要严格按照优先级,依次的执行接收动作,高优先级的广播接受者如果没有处理完,低优先级的广播接受者是无法收到广播的。无需广播就是没有顺序,各个广播接受者之间没有相互依赖关系。

无需广播,发送是sendBroadcast方法发送广播通知。

有序广播,需要使用sendOrderedBroadcast方法发送广播通知。

无序广播大体流程图如下:

注册广播接收者流程

1.APP侧,无论activity还是application,还是service,最终都是交给Context的实现类ContentImpl进行最终的处理的。所以注册广播的时候,最终调用的是ContentImpl的registerReceiver方法。

2.registerReceiver方法中通过binder,通知到AMS的registerReceiverWithFeature方法。

3.registerReceiverWithFeature方法中,首先做一些安全校验。

4.除了action类型,还会有scheme的类型,这里就不具体介绍了。

5.最终都会注册到IntentResolver类型对象mReceiverResolver上。

广播通知流程

1.仍然交由ContextImpl中的broadcastIntentWithFeature方法来处理。

2.broadcastIntentWithFeature通知到AMS的broadcastIntentLocked方法中。

3.首先从mReceiverResolver中查询,看是否存在接受者,如果存在,加入到registeredReceivers集合中。

registeredReceivers = mReceiverResolver.queryIntent(intent,
                        resolvedType, false /*defaultOnly*/, userId);

4.如果mReceiverResolver不为空,并且是无序广播,则根据intent找到所对应的BroadcastQueue(根据前台,后台,离线的类型)。

if (!ordered && NR > 0) {
            final BroadcastQueue queue = broadcastQueueForIntent(intent);
            BroadcastRecord r = new BroadcastRecord(queue, intent, ...);
            if (!replaced) {
                queue.enqueueParallelBroadcastLocked(r);
                queue.scheduleBroadcastsLocked();
            }
            egisteredReceivers = null;
            NR = 0;
}

5.生成BroadcastRecord对象用来记录这次的action所对应的广播事件,然后加入到BroadcastQueue的mParallelBroadcasts集合中。然后通过scheduleBroadcastsLocked方法切换到主线程执行。这里要强调的是,无论是有序广播还是无序广播,都是通过这个方法来分发的。最终主线程会调用到processNextBroadcast方法。代码在上面5,6行。

6.processNextBroadcast方法中逻辑很多,我们这里先只讲无序广播的流程。上一步中,我们把BroadcastRecord加入到了mParallelBroadcasts中,所以这里发送广播的时候,直接遍历mParallelBroadcasts集合,然后通知接受者接收。具体的流程可以看流程图,这里就不细讲了。

具体代码如下:

while (mParallelBroadcasts.size() > 0) {
            r = mParallelBroadcasts.remove(0);
            r.dispatchTime = SystemClock.uptimeMillis();
            r.dispatchClockTime = System.currentTimeMillis();
            ...
            final int N = r.receivers.size();
            for (int i=0; i

三.有序广播流程

有序广播流程和上面无序广播是类似的,最终请求AMS的方法其实也是broadcastIntentWithFeature方法,两者只是参数有区别而已。

我们仍然按照注册广播接受者和发送广播两个流程来讲,首先是注册广播。

注册广播接收者流程

注册的流程和无序广播是一样的。

广播通知流程

1.前面的流程和无序广播是一样的,唯一的区别是进入到AMS的broadcastIntentLocked方法后,执行逻略微有区别。有序广播,调用的是BroadcastQueue中的

enqueueOrderedBroadcastLocked方法,把BroadcastRecord对象最终注册到BroadcastDispatcher中的mOrderedBroadcasts集合中,然后在调用scheduleBroadcastsLocked方法切换到主线程,执行processNextBroadcastLocked的主流程。

下面介绍的都是processNextBroadcastLocked方法,也是广播事件分发的的核心流程代码:

2.processNextBroadcastLocked中,首先仍然去无序队列mParallelBroadcasts中查,这时候肯定是没有值的,所以跳过这个步骤。

3.然后通过mDispatcher.getNextBroadcastLocked(now);方法去BroadcastDispatcher中查找,方法实现如下,因为刚才应添加到了mOrderedBroadcasts集合中,所以这时候可以找到并返回BroadcastRecord对象。

4.BroadcastRecord.nextReceiver用来记录一系列有序广播执行到了第几个,0代表开始,如果为0,则记录一些必要信息。另外,会更新掉record的时间receiverTime,这一点很重要,下面ANR流程中会有涉及到。

代码如下:

int recIdx = r.nextReceiver++;
r.receiverTime = SystemClock.uptimeMillis();
        if (recIdx == 0) {
            r.dispatchTime = r.receiverTime;
            r.dispatchClockTime = System.currentTimeMillis();
            if (mLogLatencyMetrics) {
                FrameworkStatsLog.write(
                        FrameworkStatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED,
                        r.dispatchClockTime - r.enqueueClockTime);
            }
            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
                    System.identityHashCode(r));
                Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
                    System.identityHashCode(r));
            }
            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast ["
                    + mQueueName + "] " + r);
        }

5.发送一个延时广播,延时时间具体看是前台广播还是后台广播定义的。如果已经发送过并且未结束,则跳过。setBroadcastTimeoutLocked中的具体逻辑,第四章ANR流程中会讲到。

 if (! mPendingBroadcastTimeoutMessage) {
            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
                    "Submitting BROADCAST_TIMEOUT_MSG ["
                    + mQueueName + "] for " + r + " at " + timeoutTime);
            setBroadcastTimeoutLocked(timeoutTime);
        }

6.如果接受者是BroadcastFilter类型,则把广播事件发送给接受者。

if (nextReceiver instanceof BroadcastFilter) {
    BroadcastFilter filter = (BroadcastFilter)nextReceiver;
    deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
 
}

至此,processNextBroadcastLocked的方法暂时介绍完成,即然用暂时,那就说明后面还会涉及到。

7.接收者是LoadedApk中ReceiverDispatcher的内部类InnerReceiver,它是binder的服务端接收者,其performReceive方法收到通知后,会交给ReceiverDispatcher中的performReceive方法处理。

if (rd != null) {
   rd.performReceive(intent, resultCode, data, extras,
                            ordered, sticky, sendingUser);
}

所以,最终来处理广播信息的方法是ReceiverDispatcher中的performReceive方法。其中部分代码如下,getRunnable就是最终通知到我们注册的广播接受者的流程。如果getRunnable中的任务注册不成功的话,会直接发送信号通知回AMS。什么情况下会注册不成功呢?主线程Looper异常执行完并退出时就会发生。

 if (intent == null || !mActivityThread.post(args.getRunnable())) {
                if (mRegistered && ordered) {
                    IActivityManager mgr = ActivityManager.getService();
                    if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                            "Finishing sync broadcast to " + mReceiver);
                    args.sendFinished(mgr);
                }
            }
        }

我们在来看一下getRunnable中返回的任务,我们仍然只贴核心代码:

                    if (receiver == null || intent == null || mForgotten) {
                        if (mRegistered && ordered) {
                            if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                                    "Finishing null broadcast to " + mReceiver);
                            sendFinished(mgr);
                        }
                        return;
                    }
                   ...
                    try {
                       ...
                        receiver.onReceive(mContext, intent);
                    } catch (Exception e) {
                        ...
                    }
                    if (receiver.getPendingResult() != null) {
                        finish();
                    }

我们可以看到,先执行onReceive的回调,这里就会最终通知到我们的BroadcastReceiver中的onReceive方法。

然后我们在看一下finish方法:这个其实核心就是发送广播完成的信号给AMS:

public final void finish() {
            if (mType == TYPE_COMPONENT) {
                final IActivityManager mgr = ActivityManager.getService();
                if (QueuedWork.hasPendingWork()) {
                   ..
                    QueuedWork.queue(new Runnable() {
                        @Override public void run() {
                            if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                                    "Finishing broadcast after work to component " + mToken);
                            sendFinished(mgr);
                        }
                    }, false);
                } else {
                    if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                            "Finishing broadcast to component " + mToken);
                    sendFinished(mgr);
                }
            } else if (mOrderedHint && mType != TYPE_UNREGISTERED) {
                if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                        "Finishing broadcast to " + mToken);
                final IActivityManager mgr = ActivityManager.getService();
                sendFinished(mgr);
            }
        }

最终,在BroadcastReceiver.PendingResult的sendFinished方法中,通过binder通知回AMS。

 am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
                                mAbortBroadcast, mFlags);

8.AMS的finishReceiver中,主要功能是先通过binder,找到所对应的BroadcastRecord,然后结束掉当前的这个事件流程,如果后面还有事件,则继续调用processNextBroadcastLocked方法,进行下一轮的广播事件分发。

r = queue.getMatchingOrderedReceiver(who);
if (r != null) {
     dOnext= r.queue.finishReceiverLocked(r, resultCode,
                        resultData, resultExtras, resultAbort, true);
}
if (doNext) {
     r.queue.processNextBroadcastLocked(/*fromMsg=*/ false, /*skipOomAdj=*/ true);
}

四.广播ANR流程

那么什么时候会导致广播ANR呢?这一套题,我估计能考倒90%的安卓开发者,单纯的无序广播,广播接受者中sleep几分钟,是不会产生广播类型ANR的,ANR只存在于有序广播并且广播接受者未及时响应。

其实了解完整个广播流程,我们ANR流程就好讲的多。我们只需关注整个流程中的几个点就好了。

触发广播监听者流程

1.首先第三章的第五小节中,我们讲到,通过setBroadcastTimeoutLocked方法设置一个延时的消息,消息的执行时间=当前时间+超时时间,此时伴随着广播通知到客户端的流程。

long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
 final void setBroadcastTimeoutLocked(long timeoutTime) {
        if (! mPendingBroadcastTimeoutMessage) {
            Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
            mHandler.sendMessageAtTime(msg, timeoutTime);
            mPendingBroadcastTimeoutMessage = true;
        }
    }

也就是说,一旦达到超时时间,那么就会发送BROADCAST_TIMEOUT_MSG类型的事件,就一定会执行broadcastTimeoutLocked方法。

2.broadcastTimeoutLocked方法中,会再一次进行判断,如果没有到执行时间,会重新触发一次setBroadcastTimeoutLocked的流程。

上面3.8中讲到,如果是有序广播,广播接收者收到消息后,会通过binder回调AMS通知事件处理完成,并重新进入processNextBroadcastLocked流程进行下一轮的分发,这时候,会更新掉上面用到的receiverTime时间。

3.processNextBroadcastLocked分发广播的时候,如果判断进入到了结束的环节,会主动取消注册BROADCAST_TIMEOUT_MSG类型的事件。

 if (r.receivers == null || r.nextReceiver >= numReceivers
                    || r.resultAbort || forceReceive) {
    ...
     cancelBroadcastTimeoutLocked();
}

一旦取消了超时类型消息的注册,自然也不会走到ANR逻辑。

这里有一点绕,所以我举个例子详细解释下,因为一开始我其实也被绕进去了。

假设我们的超时时间为10S,有序广播中有3个接收者,接收者1耗时5S,接收者2耗时6S,接收者3耗时4S,这时候,在接收者2处理中的时候,就会进入到broadcastTimeoutLocked的流程。但是此时,由于接收者1执行完其流程,所以更新了receiverTime时间,此时的超时时间变成5+10S,而当前为第10S,所以并不会超时。第14S的时候接收者3也完成流程,通知回AMS,取消了超时类型消息的注册,所以就不会再次走到broadcastTimeoutLocked的流程了。

所以,走到broadcastTimeoutLocked流程并不一定意味着就会发生ANR。我一开始就是被这个绕进去了。

五.总结

所以整个广播事件分发以及ANR触发流程,我们可以用下图来做一个总结:图片较大,建议另外开一个tab页查看:

六.扩展问题

1.有序和无序广播是怎么区分的?

答:sendOrderedBroadcast和sendBroadcast两个方法,其实最终broadcastIntentWithFeature方法中,就是一个参数不一样,倒数第三个boolean serialized。sendOrderedBroadcast为true,sendBroadcast为false。

2.发送一个广播,A进程中接收,广播接收者A中的onReceive方法中sleep100秒,是否一定会触发ANR?如果是或者不是?原因是什么?如果我们把广播改为有序广播呢?

答:

只有有序广播才会ANR,如果第一种情况如果是无序广播,自然不会ANR。

第二个答案是一般情况下是会的,因为广播接收者A中阻塞,导致AMS无法按时收到广播完成的信号,从而会引起ANR。除非进程A的主线程因为某种异常原因退出,不过这种情况下,onReceive方法自然也不会走到。

到此这篇关于Android广播事件流程与广播ANR原理深入刨析的文章就介绍到这了,更多相关Android广播事件流程内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
author-avatar
丶Le丨囧囧_832
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有