Android Broadcast 分析——注册
更新日期:
背景
我们的平板增加了一个关机闹钟的功能。就是设备在关机的情况也能唤醒然后响闹钟。实现方法是:在 kernel 中增加一个 RTC 时钟(关机情况下,板载的 RTC 时钟还在运行的,所以你关机一段时间发现电池会变少),然后提前一段时间开机(我们设定的是1分钟,得给点时间给开机,我们的平板开机要40多秒咧),然后闹钟模块接收开机广播(ACTION_BOOT_COMPLETED
),然后把 android 应用层的闹钟设置下去,然后就能实现关机闹钟功能了。
但是最近遇到一个问题:说关机闹钟,到了时间设备能唤醒,但是闹钟不响。于是分析一下 android 广播的处理流程(这里以开机广播为例)。然后顺带记录下分析过程,以后忘记看几眼能捡起来。
android 广播的注册方式分为2种:一种是动态注册,一种是静态注册。哦,对了,在说之前,照例把相关源码位置啰嗦一下(4.2.2):
|
|
动态注册
接口端
我们先来说说动态注册。动态注册是在程序当中调用 Context(ContextImpl)的接口来注册的:
|
|
其中一般自己要继承 BoradcastReceiver,然后实现里面的 onReceive 函数:
|
|
然后那个 IntentFilter 里面可以设置要接收的广播,可以设置好多种类型,具体的自己去翻 api 文档。一般简单的可以直接设一个 ACTION。然后因为这个是需要在程序运行中拿 Context 去注册的,所以这种注册就有一个特点:接收广播的进程已经运行起来了。
接下来我们就去看看这个注册过程是怎么样的。首先调用的是 Context(Activity、Service 里均可以调用)的 registerReceiver ,这个其实是挂 ContextImpl 里面的同名函数的马甲:
|
|
可以看出最后是调用 registerReceiverInternal 这个函数的。然后我们用的那个没指定权限(我们先不管啥权限之类的),还有 Handler。传过去的是 null:
|
|
然后我们得先说 IIntentReceiver 这个东西。这个是保存广播最基本的数据了。这个是以 II 开头的,就知道这个实现了 Binder 接口支持 IPC 访问的。这个是用 aidl 写的,接口只有一个:
|
|
然后 LoadApk 中的一个内部类的内部类(LoadedApk.ReceiverDispatcher.InnerReceiver)实现了这个接口:
|
|
这个 LoadedApk 看名字,基本上就是和本应用程序包相关的东西(manifest 里面申明的那一堆东西基本上都在这里,当然扫描、解析是 PMS 干的)。然后它提供一个 IIntentReceiver 的 Bn 实现。它会保存调用者传递过来的 Handler,这个也就是说我们可以指定广播处理的线程(哪天有空把 Handler、Looper 也写下分析),如果我们没有指定的话,默认用注册进程的主线程(所以如果要在广播接收中做一些耗时的操作,请开一个后台线程作为接收的处理线程)。然后我们回到 ContextImpl 的注册函数那,有一个处理,如果 mPackageInfo 不为空,就去调用 LoadedApk 里的函数去取:
|
|
然后后面就去 AMS 里面去注册了。其实 AMS 可以说是广播的中转站,应用程序通过 AMS 发送广播,然后 AMS 找到对应的接收器进行广播分发(咋感觉和消息分发差不多)。
服务端
来看下 AMS 中 registerReceiver :
|
|
我们先说说参数,IIntentReceiver 是支持 Binder 的,传过来的是 Bp,IntentFilter 实现了 Parcelable 接口,也能 IPC 传过来。然后我们再开始说代码,一开始先获取注册调用者的进程记录(ProcessRecord,这个东西在 Binder 篇有说过,这里不多说)。然后广播有个 sticky 的功能,我们先不管这些花哨功能先。后面出现了一个 ReceiverList 的对象,我们来看看:
|
|
这个是自己改造的一个 ArrayList,里面存放的是 BroadcastFilter,然后注意它保存了 IIntentReceiver(从 AMS 注册接口调用者那里得到)。然后上面的代码后面马上就构造出了一个 BroadcastFilter,添加到这个 ReceiverList 中去了。这个 BroadcastFilter 我们也来稍微看下它的结构吧:
|
|
继续自 IntentFilter(这个我就不多说了,注册广播的应该对这个很熟悉了)。上面代码 AMS 中还保存了已经注册过的 RecieverList(mRegisteredReceivers),会先去以前的列表里面去找,看之前是不是注册过,这个可以防止多个重复的 broadcast recevier。然后到最后了, mReceiverResolver.addFilter(bf); 这里就很关键了。我们先来看看这个 mReceiverResolver 是什么东西:
|
|
mReceiverResolver 继承自 IntentResolver 这个模板类。前面的 F 是传入类型,保存在列表(HashMap、HashSet)里面的,后面那个 R 是传出类型,后面 AMS 处理广播的时候要向 IntentResolver 查询的,那个时候返回的就是 R。然后2个模板都是 BroadcastFilter。我们先继续看它的 addFilter 里面是怎么处理的(在父类里面):
|
|
这个 addFilter 大意就是把传过来的 F(这里是 BroadcastFilter)保存到对应的列表里面。不过我们还是继续去看看 register_intent_filter 里面的处理:
|
|
处理比较简单,就是存下数据,不过注意这里 new 数据的时候函数 newArray, AMS 中的 mReceiverResolver 实现了这个函数(其实还实现了好个,这里先看这个):
|
|
也挺简单的,就是返回具体模板的对象的数组而已。到这里动态注册的服务端的流程就完了。简单来说就是把传过来的接收处理对象(IItnentRecevier)和要接收的广播(IntentFilter)保存到了 AMS 的一个叫 mReceiverResolver 里面去了。但是由于对象层层封装,容易一下子看多了忘记这个倒数是啥东西了。于是我整了一张,看一下应该就会好了:
静态注册
上面说完了动态注册,我们来说下静态注册。和动态相对的,静态注册最大的特点就是: 进程不需要运行,并且 AMS 在处理广播的时候还会帮你把接收器所在的进程启动起来。静态注册在 AndroidMainfest.xml 中声明一个 recevier 就可以了,例如像下面这样:
|
|
那静态广播是怎么注册到系统里面的去咧。其实看到在 mainfest 声明就应该猜到是 PMS 扫描,然后将扫描结果存了起来。实际就是这样的,我们来看看具体流程。首先在 PMS 的构造函数有这么一个调用(PMS 会在 SystemService 初始化的时候启动起来,具体的去看 Binder 多线程篇):
|
|
SS(PMS) 是开机启动的,也就是说在开机的时候 PMS 会去扫描一系列目录下的应用去收集一些应用的信息(稍微扯远点,注意看上面有一些文件 Observer(这个是 android 自己弄了一个监视文件变动的 Observer)设置,会在监视相应目录下的文件变动,一有变动,PMS 会扫描变动,这也就是为什么调试的时候直接 push apk 到 /system/app 或者 /data/app 下面也是能够正常安装的原因)。扫描函数是 scanDirLI:
|
|
这里把扫描单个 apk 文件的任务交给 scanPackageLI 这个函数了:
|
|
又调用到同名,不同参数的函数中去了,而且这个函数巨长无比,有将近 1000 行,我们只看关键部分:
|
|
我们先来看这个 mReceivers 是什么:
|
|
经过前面动态注册的分析,对于个 IntentResolver 模板类应该不陌生了吧。PMS 中也有一个,用来来保存静态的注册信息的(AMS 那个是保存动态的)。PMS 里面的 F 是 PackageParser.ActivityIntentInfo, R 是 ResolverInfo。我们来看下这个 F 的定义:
|
|
还是得继承自 IntentFilter,只不过又多了一层,然后多了几个变量而已。然后 scanPackageLI 这个函数是传入 PackageParser.Activity 这个对象过去的,我们看看这个东西:
|
|
绕了几下,反正是 ActivityIntentInfo 的子类。至于 ActivityIntentInfo 怎么从 PackageParser 解析出来的,前面说了我们这里不做分析。然后我们直接去看 ActivityIntentResolver 的 addActivity:
|
|
这里有个解决前面背景中关机闹钟不响问题的关键——广播 receiver 接收优先级的设置。在 mainfest 的 intent-filter 标签中可以声明一个优先级(看我前面的那个例子),这个是一个 int 类型,一般默认是 0。数值越大,优先级越高,这个优先级在哪里会用到,到后面一篇,广播处理那里就能看到了。这个数值是由 PackageParser 解析 mainfest 中的声明得到的,从这里的判断可以看出,普通应用的广播 receiver 无法声明高于 0。但是这点好像只对静态注册的广播有限制,动态的可以使用 IntentFilter.setPriority 设置,虽然 API 文档里告诉你不要让这个值高于 SYSTEM_HIGH_PRIORITY(1000)
,但是 AMS 里面没有任何限制判断,所以你要耍下流氓可以把这个值设得很高。不过动态注册的 recevier 在并行广播处理(后面那篇处理的会解释什么叫并行广播、什么叫串行广播)中和静态的 receiver 的优先级有本质的区别(后面能知道本质区别在哪),所以动态的流氓点好像关系也不大。
扯了下优先级的问题。最后还是调用父类 IntentResolver 的 addFilter 保存到列表中,然后 PMS 的 ActivityIntentResolver 的 newArray 是这样的:
|
|
这个 addFilter 就不重复说一遍了。
总结
通过上面的分析,可以看到,虽然广播接收器注册分动态和静态。但是最后的流程都是一样的,只不过一个接收器的数据保存在 AMS 中(动态的),一个接收器的数据保存在 PMS 中(静态的)。所以后面处理广播的时候查询动态的接收器直接用 AMS 自己的数据查,而查询静态的需要调用 PMS 的接口去查(不过它们2个都在一个进程里面,也算一家人啦)。