目前,系统提供两种方式来声明一个广播接收者。
· 在AndroidManifest.xml中声明标签。在应用程序运行时,系统会利用Java反射机制构造一个广播接收者实例。本书将这种广播接收者称为静态注册者或静态接收者。
· 在应用程序运行过程中,可调用Context提供的registerReceiver函数注册一个广播接收者实例。本书将这种广播接收者称为动态注册者或动态接收者。与之相对应,当应用程序不再需要监听广播时(例如当应用程序退到后台时),则要调用unregisterReceiver函数撤销之前注册的BroadcastReceiver实例。
Android定义了三种不同类型的广播发送方式,它们分别是:
· 普通广播发送方式,由sendBroadcast及相关函数发送。以工作中的场景为例,当程序员们正埋头工作之时,如果有人大喊一声“吃午饭去”,前刻还在专心编码的人即作鸟兽散。这种方式即为普通广播发送方式,所有对“吃午饭”感兴趣的接收者都会响应。
· 串行广播发送方式,即ordered广播,由sendOrdedBroadcast及相关函数发送。在该类型方式下,按接收者的优先级将广播一个个地派发给接收者。只有等这一个接收者处理完毕,系统才将该广播派发给下一个接收者。其中,任意一个接收者都可以中止后续的派发流程。还是以工作中的场景为例:经常有项目经理(PM)深夜组织一帮人跟踪bug的状态。PM看见一个bug,问某程序员,“这个bug你能改吗?”如果得到的答案是“暂时不会”或“暂时没时间”,他会将目光转向下一个神情轻松者,直到找到一个担当者为止。这种方式即为ordered广播发送方式,很明显,它的特点是“一个一个来”。
· Sticky广播发送方式,由sendStickyBroadcast及相关函数发送。Sticky的意思是“粘”,其背后有一个很重要的考虑。我们举个例子:假设某广播发送者每5秒发送一次携带自己状态信息的广播,此时某个应用进程注册了一个动态接收者来监听该广播,那么该接收者的OnReceive函数何时被调用呢?在正常情况下需要等这一轮的5秒周期结束后才调用(因为发送者在本周期结束后会主动再发一个广播)。而在Sticky模式下,系统将马上派发该广播给刚注册的接收者。注意,这个广播是系统发送的,其中存储的是上一次广播发送者的状态信息。也就是说,在Sticky模式下,广播接收者能立即得到广播发送者的信息,而不用等到这一轮周期结束。其实就是系统会保存Sticky的广播[⑤],当有新广播接收者来注册时,系统就把Sticky广播发给它。
1. registerReceiver流程分析
registerReceiver函数用于注册一个动态广播接收者,该函数在Context.java中声明。其功能最终将通过ContextImpl类的registerReceiver函数来完成,可直接去看ContextImpl是如何实现此函数的。
1.1 ContextImpl registerReceiver
IIntentReceiver是一个Interface,和它相关的成员图谱:
· BroadcastReceiver内部有一个PendingResult类。该类是Android 2.3以后新增的,用于异步处理广播消息。例如,当BroadcastReceiver收到一个广播时,其onReceive函数将被调用。一般都是在该函数中直接处理该广播。不过,当该广播处理比较耗时,还可采用异步的方式进行处理,即先调用BroadcastReceiver的goAsync函数得到一个PendingResult对象,然后将该对象放到工作线程中去处理,这样onReceive就可以立即返回而不至于耽误太长时间(这一点对于onReceive函数被主线程调用的情况尤为有用)。在工作线程处理完这条广播后,需调用PendingResult的finish函数来完成整个广播的处理流程。
· 广播由AMS发出,而接收及处理工作却在另外一个进程中进行,整个过程一定涉及进程间通信。在图6-17中,虽然在BroadcastReceiver中定义了一个广播接收者,但是它与Binder没有有任何关系,故其并不直接参与进程间通信。与之相反,IIntentReceiver接口则和Binder有密切关系,故可推测广播的接收是由IIntentReceiver接口来完成的。确实,在整个流程中,首先接收到来自AMS的广播的将是该接口的Bn端,即LoadedApk.ReceiverDispather的内部类InnerReceiver。
1.2 AMS的registerReceiver分析
函数比较简单,但是由于其中将出现一些新的变量类型和成员,因此接下来按分两部分进行分析。
1.2.1 registerReceiver分析之一
各数据类型和成员变量的作用及关系的解释如下:
· 在AMS中,BroadcastReceiver的过滤条件由BroadcastFilter表示,该类从IntentFilter派生。由于一个BroadcastReceiver可设置多个过滤条件(即多次为同一个BroadcastReceiver对象调用registerReceiver函数以设置不同的过滤条件),故AMS使用ReceiverList(从ArrayList派生)这种数据类型来表达这种一对多的关系。
· ReceiverList除了能存储多个BroadcastFilter外,还应该有成员指向某一个具体BroadcastReceiver,否则如何知道到底是哪个BroadcastReceiver设置的过滤条件呢?前面说过,BroadcastReceiver接收广播是通过IIntentReceiver接口进行的,故ReceiverList中有receiver成员变量指向IIntentReceiver。
· AMS提供mRegisterReceivers用于保存IIntentReceiver和对应ReceiverList的关系。此外,AMS还提供mReceiverResolver变量用于存储所有动态注册的BroadcastReceiver所设置的过滤条件。
1.2.2 registerReceiver分析之二
这一阶段的工作用一句话就能说清楚:为每一个满足IntentFilter的Sticky的intent创建一个BroadcastRecord对象,并将其保存到mParllelBroadcasts数组中,最后,根据情况调度AMS发送广播。
在代码中,registerReceiver将调用scheduleBroadcastsLocked函数,通知AMS立即着手开展广播发送工作,在其内部就发送BROADCAST_INTENT_MSG消息给AMS。相关的处理工作将放到本节最后再来讨论。
2. sendBroadcast流程分析
在SDK中同样定义了好几个函数用于发送广播。不过,根据之前的经验,最终和AMS交互的函数可能通过一个接口就能完成。来看最简单的广播发送函数sendBroadcast. AMS的broadcastIntent函数的主要工作将交由AMS的broadcastIntentLocked来完成,故此处直接分析broadcastIntentLocked。
2.1 broadcastIntentLocked分析
2.1.1 broadcastIntentLocked分析之一
broadcastIntentLocked第一阶段的工作主要是处理一些特殊的广播消息。
2.1.2 broadcastIntentLocked分析之二
broadcastIntentLocked第二阶段的工作有两项:
· 查询满足条件的动态广播接收者及静态广播接收者。
· 当本次广播不为ordered时,需要尽快发送该广播。另外,非ordered的广播都被AMS保存在mParallelBroadcasts中。
2.1.3 broadcastIntentLocked分析之三
AMS将动态注册者和静态注册者都合并到receivers中去了。注意,如果本次广播不是ordered,那么表明动态注册者就已经在第二阶段工作中被处理了。因此在合并时,将不会有动态注册者被加到receivers中。最终所创建的广播记录存储在mOrderedBroadcasts中,也就是说,不管是否串行化发送,静态接收者对应的广播记录都将保存在mOrderedBroadcasts中。为什么不将它们保存在mParallelBroadcasts中呢?
结合代码会发现,保存在mParallelBroadcasts中的BroadcastRecord所包含的都是动态注册的广播接收者信息,这是因为动态接收者所在的进程是已经存在的(如果该进程不存在,如何去注册动态接收者呢?),而静态广播接收者就不能保证它已经和一个进程绑定在一起了(静态广播接收者此时可能还仅仅是在AndroidManifest.xml中声明的一个信息,相应的进程并未创建和启动)。根据前一节所分析的Activity启动流程,AMS还需要进行创建应用进程,初始化Android运行环境等一系列复杂的操作。读到后面内容时会发现,AMS将在一个循环中逐条处理mParallelBroadcasts中的成员(即派发给接收者)。如果将静态广播接收者也保存到mParallelBroadcasts中,会有什么后果呢?假设这些静态接收者所对应的进程全部未创建和启动,那么AMS将在那个循环中创建多个进程!结果让人震惊,系统压力一下就上去了。所以,对于静态注册者,它们对应的广播记录都被放到mOrderedBroadcasts中保存。AMS在处理这类广播信息时,一个进程一个进程地处理,只有处理完一个接收者,才继续下一个接收者。这种做法的好处是,避免了惊群效应的出现,坏处则是延时较长。
3. BROADCAST_INTENT_MSG消息处理函数
BROADCAST_INTENT_MSG消息将触发processNextBroadcast函数,下面分阶段来分析它。
3.1 processNextBroadcast分析之一
对于动态注册者而言,在大部分情况下会执行if分支,所以应用进程ApplicationThread的scheduleRegisteredReceiver函数将被调用。稍后再分析应用进程的广播处理流程。
3.2 processNextBroadcast分析之二
至此,processNextBroadcast已经在一个while循环中处理完mParallelBroadcasts的所有成员了,实际上,这种处理方式也会造成惊群效应,但影响相对较少。这是因为对于动态注册者来说,它们所在的应用进程已经创建并初始化成功。此处的广播发送只是调用应用进程的一个函数而已。相比于创建进程,再初始化Android运行环境所需的工作量,调用scheduleRegisteredReceiver的工作就比较轻松了。
至此,processNextBroadcast已经在一个while循环中处理完mParallelBroadcasts的所有成员了,实际上,这种处理方式也会造成惊群效应,但影响相对较少。这是因为对于动态注册者来说,它们所在的应用进程已经创建并初始化成功。此处的广播发送只是调用应用进程的一个函数而已。相比于创建进程,再初始化Android运行环境所需的工作量,调用scheduleRegisteredReceiver的工作就比较轻松了。
3.3 processNextBroadcast分析之三
对processNextBroadcast第三阶段的工作总结如下:
· 如果广播接收者为动态注册对象,则直接调用deliverToRegisteredReceiverLocked处理它。
· 如果广播接收者为静态注册对象,并且该对象对应的进程已经存在,则调用processCurBroadcastLocked处理它。
· 如果广播接收者为静态注册对象,并且该对象对应的进程还不存在,则需要创建该进程。这是最糟糕的情况。
此处,不再讨论新进程创建及Android运行环境初始化相关的逻辑,读者可返回阅读“attachApplicationLocked分析之三”,其中有处理mPendingBroadcast的内容。
4. 应用进程处理广播分析
下面来分析当应用进程收到广播后的处理流程,以动态接收者为例。
4.1 ApplicationThreadscheduleRegisteredReceiver函数分析
如前所述,AMS将通过scheduleRegisteredReceiver函数将广播交给应用进程
receiver对象的真实类型为LoadedApk.ReceiverDispatcher,来看它的performReceive函数
scheduleRegisteredReceiver最终向主线程的Handler投递了一个Args对象,这个对象的run函数将在主线程中被调用。
4.2 Args.run分析
4.3 AMS的finishReceiver函数分析
不论ordered还是非orded广播,AMS的finishReceiver函数都会被调用