系列文章索引
并发系列:线程锁事
新系列:Android11系统源码解析
-
Android11源码分析:binder是如何实现跨进程的?(创作中)
-
Android11源码分析:SurfaceFlinger是如何对vsync信号进行分发的?(创作中)
经典系列:Android10系统启动流程
前言
经过前面Activity
,Service
的分析,相信大家一定发现了,四大组件最大的特性就是支持跨进程
作为四大组件之一的广播,使用起来相当方便,可以通过注册广播接收系统消息,也可以方便的跨进程传递消息
今天我们就来分析下,广播是如何实现跨进程传递消息,以及静态广播,动态广播是如何注册和收发消息的
下面,正文开始!
注: 文章分析源码基于android11.0.0-r8
,为方便阅读,部分源码有删减
广播的发送
发送广播在使用上非常简单,只需要调用sendBroadcast(intent)
即可,我们从这个函数为切入点,来看看广播是怎么发送的
/frameworks/base/core/java/android/app/ContextImpl.java
public void sendBroadcast(Intent intent) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
//...略
ActivityManager.getService().broadcastIntentWithFeature(...);
}
可以看到这里通过binder调用,执行到了AMS
的broadcastIntentWithFeature()
函数(在android11的源码中,AMS的registerReceiver()
函数已被标记为@deprecated
)
在这个函数中,通过binder获取对应的BroadcastFilterList对象(即ReceiverList
),并将BroadcastFilter
添加到ReceiverList
中,再通过broadcastQueueForIntent()
函数获取到BroadcastQueue
对象,通过queue进行并行广播分发enqueueParallelBroadcastLocked()
和串行广播分发scheduleBroadcastsLocked
,我们正常使用中发送广播都是串行的
代码如下
/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public Intent registerReceiverWithFeature(...){
//...略
ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());//通过binder获取对应的BroadcastFilterList
rl = new ReceiverList(this, callerApp, callingPid, callingUid,
userId, receiver);
BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
permission, callingUid, userId, instantApp, visibleToInstantApps);
rl.add(bf); //添加到BroadcastFilter列表中
mReceiverResolver.addFilter(bf);
BroadcastQueue queue = broadcastQueueForIntent(intent);//分发广播的队列
BroadcastRecord r = new BroadcastRecord(...);
queue.enqueueParallelBroadcastLocked(r); //并行广播分发
queue.scheduleBroadcastsLocked(); //串行广播分发
//...略
}
广播分发的核心逻辑在BroadcastQueue
的scheduleBroadcastsLocked()
中,函数中发送消息给BroadcastQueue
的hander进行处理,真正的处理函数是processNextBroadcastLocked()
,先对并行广播进行处理, 处理结束后在当广播为空时在while循环中获取下一条广播
先对动态广播BroadcastFilter
调用deliverToRegisteredReceiverLocked()
进行处理
再对静态广播ResolveInfo
进行处理,由于静态广播可以在进程未启动的时候被执行,所以当启动静态广播时,如果进程未启动或被kill,需要先调用AMS
的startProcessLocked()
启动进程,然后把广播添加到mPendingBroadcast
中供后续的逻辑处理
如果进程存在,则执行processCurBroadcastLocked()
对当前的广播进行处理
由于processNextBroadcastLocked()
代码过长,此处不再粘贴具体代码,感兴趣的可以自行查看
对于广播发送的逻辑,到这里就分析完成了
小结
对于广播的发送,涉及的主要类有ResolveInfo
(静态广播),BroadcastFilter
(动态广播),BroadcastQueue
(广播队列),其中主要的处理逻辑在BroadcastQueue
的processNextBroadcastLocked()
函数,对并行广播,动态广播,静态广播进行处理和分发。
动态广播由于需要动态注册,所以不需要考虑进程不存在的情况;
而静态广播是在清单文件中注册,且接收时可能进程还未启动,所以需要先判断进程是否存在,进程不存在时需要先拉起进程再进行处理
广播的注册
说完了广播的发送,我们再来看看广播是如何注册的
静态广播的注册
对于静态广播来说,需要在清单文件注册,而对清单文件进行解析并处理的服务是PackageManagerService
(后面简称PMS
)
作为系统服务,PMS
和AMS
一样在SystemServer
中被启动,PMS
会对安装的APK文件进行解析,拿到清单文件后,再对各个标签进行解析,解析处理类为PackageParser
,此处我们只关心receiver
标签,具体的处理逻辑在parseBaseApplication()
函数中,对标签进行解析后,会将解析后的Component对象添加到Package
的receivers
列表中,当发送广播时,会从中获取到已添加的静态广播
这也是静态广播能在未启动时接收到消息的最重要的一步,具体代码如下
/frameworks/base/core/java/android/content/pm/PackageParser.java
private boolean parseBaseApplication(Package owner...){
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
if (tagName.equals("receiver")) { //对广播标签进行解析
//此处的activity不是应用层的Activity对象,而是一个Component对象
Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
true, false);
//...略
hasReceiverOrder |= (a.order != 0);
owner.receivers.add(a); //加入receiver列表
}
}
动态广播的注册
说完了PMS
对清单文件的解析处理,我们再回到动态广播注册流程上来
注册动态广播需要调用ContextImpl
的registerReceiver()
函数,此时需要创建出ReceiverDispatcher
对广播进行分发,并调用AMS
的registerReceiverWithFeature()
函数对广播进行注册
其中ReceiverDispatcher
的binder对象也通过参数传递给AMS,便于AMS与ReceiverDispatcher之前跨进程通信
ReceiverDispatcher
的binder对象是它的内部类InnerDispatcher
,继承了IIntentReceiver.Stub
,之后分析中,在AMS中凡是使用IIntentReceiver
调用的方法,最终都通过binder调用执行到了InnerDispatcher
的函数
再回到AMS
的registerReceiverWithFeature()
函数上来,函数中通过receiver
的binder句柄作为key获取到对应的BroadcastFilterList(第一次时为null,需要创建并添加到集合中),添加完成后调用broadcastQueueForIntent()
获取到BroadcastQueue
,调用scheduleBroadcastsLocked()
执行广播分发流程,这部分我们上文已经做过分析
加餐:为什么广播onReceive耗时不能超过10s?
要解释这个问题,还要从源码上找答案,回到执行广播分发的函数processNextBroadcastLocked()
其中会调用mDispatcher
的getNextBroadcastLocked()
函数获取下一条广播
注
: 此处的Dispatcher
是AMS端分发逻辑的实现,并非应用端的ServiceDispatcher
)
如果当前广播处理超时,会调用broadcastTimeoutLocked()
函数进行处理,而超时的时间为BroadcastConstants.TIMEOUT
= 10000mills,也就是10秒
从广播的处理机制来看,消息的处理在客户端是串行处理的,所以各个广播在队列中按照先进先出
的原则,依次等待着被处理,如果广播处理时间过长,必然会影响其他广播的receiver的执行
所以,切记广播中也是不能执行过于耗时的操作的哦!
总结
分析完之后,我们再来通过一张图回顾下大体流程
广播作为一套使用binder在系统层进行消息分发的跨进程通信方案,设计上就是供跨进程使用的。但很多情况下,我们会错误的将其作为应用内的消息框架,这也就导致了开发者感觉到的广播收发效率低的问题
针对应用内通信,正确的使用方式是使用LocalBroadcastManager
发送本地广播来代替
最后
四大组件的分析目前完成了三个,而剩下的ContentProvider
是开发中基本上使用较少的一个类,所以我们就暂时不对其进行分析了
通过这几篇文章的的分析,大家可能也感觉到了,四大组件的调用流程中,提到最多的就是binder调用,接下来我们也将专门对binder进行分析讲解,另外插件化相关的文章也会同步进行
相信有了这几篇文章作为基础,接下来的文章阅读也会轻松很多
今天的文章就写到这里了,我们下期文章再见!