序言

本想写广播流程中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<N; i  ) {
                Object target = r.receivers.get(i);
                deliverToRegisteredReceiverLocked(r,
                        (BroadcastFilter) target, false, i);//分发给接受者
            }
            addBroadcastToHistoryLocked(r);
        }

三.有序广播流程

有序广播流程和上面无序广播是类似的,最终请求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广播事件流程内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Android广播事件流程与广播ANR原理深入刨析的更多相关文章

  1. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  4. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  5. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  6. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  7. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  8. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  9. 使用 Swift 语言编写 Android 应用入门

    Swift标准库可以编译安卓armv7的内核,这使得可以在安卓移动设备上执行Swift语句代码。做梦,虽然Swift编译器可以胜任在安卓设备上编译Swift代码并运行。这需要的不仅仅是用Swift标准库编写一个APP,更多的是你需要一些框架来搭建你的应用用户界面,以上这些Swift标准库不能提供。简单来说,构建在安卓设备上使用的Swiftstdlib需要libiconv和libicu。通过命令行执行以下命令:gitclonegit@github.com:SwiftAndroid/libiconv-libi

  10. Android – 调用GONE然后VISIBLE使视图显示在错误的位置

    我有两个视图,A和B,视图A在视图B上方.当我以编程方式将视图A设置为GONE时,它将消失,并且它正下方的视图将转到视图A的位置.但是,当我再次将相同的视图设置为VISIBLE时,它会在视图B上显示.我不希望这样.我希望视图B回到原来的位置,这是我认为会发生的事情.我怎样才能做到这一点?编辑–代码}这里是XML:解决方法您可以尝试将两个视图放在RelativeLayout中并相对于彼此设置它们的位置.

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部