文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2022-11-13 18:59

关注

序言

本想写广播流程中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 , 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( false,  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广播事件流程内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯