Android广播事件流程与广播ANR原理深入刨析
失落夏天 人气:0序言
本想写广播流程中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方法自然也不会走到。
加载全部内容