6.4 Broadcast和BroadcastReceiver分析
Broadcast,中文意思为“广播”。它是Android平台中的一种通知机制。从广义角度来说,它是一种进程间通信的手段。有广播,就对应有广播接收者。Android中四大组件之一的BroadcastReceiver即代表广播接收者。目前,系统提供两种方式来声明一个广播接收者。
在AndroidManifest.xml中声明<receiver>标签。在应用程序运行时,系统会利用Java反射机制构造一个广播接收者实例。本书将这种广播接收者称为静态注册者或静态接收者。
在应用程序运行过程中,可调用Context提供的registerReceiver函数注册一个广播接收者实例。本书将这种广播接收者称为动态注册者或动态接收者。与之相对应,当应用程序不再需要监听广播时(例如当应用程序退到后台时),则要调用unregisterReceiver函数撤销之前注册的BroadcastReceiver实例。
当系统将广播派发给对应的广播接收者时,广播接收者的onReceive函数会被调用。在此函数中,可对该广播进行相应处理。
另外,Android定义了3种不同类型的广播发送方式,它们分别是:
普通广播发送方式,由sendBroadcast及相关函数发送。以工作中的场景为例,当程序员们正埋头工作之时,如果有人大喊一声“吃午饭去”,前刻还在专心编码的人会马上站起来走向餐厅。这种方式即为普通广播发送方式,所有对“吃午饭”感兴趣的接收者都会响应。
串行广播发送方式,即ordered广播,由sendOrdedBroadcast及相关函数发送。在该类型方式下,按接收者的优先级将广播一个一个地派发给接收者。只有等上一个接收者处理完毕后,系统才将该广播派发给下一个接收者。其中,任意一个接收者都可以中止后续的派发流程。还是以工作中的场景为例:经常有项目经理(PM)深夜组织一帮人跟踪bug的状态。PM看见一个bug,问某程序员,“这个bug你能改吗?”如果得到的答案是“暂时不会”或“暂时没时间”,他会将目光转向下一个神情轻松者,直到找到一个担当者为止。这种方式即为ordered广播发送方式,很明显,它的特点是“一个一个来”。
Sticky广播发送方式,由sendStickyBroadcast及相关函数发送。Sticky的意思是“粘”,其背后有一个很重要的考虑。我们举个例子:假设某广播发送者每5秒发送一次携带自己状态信息的广播,此时某个应用进程注册了一个动态接收者来监听该广播,那么该接收者的OnReceive函数何时被调用呢?在正常情况下需要等这一轮的5秒周期结束后才调用(因为发送者在本周期结束后会主动再发一个广播)。而在Sticky模式下,系统将马上派发该广播给刚注册的接收者。注意,这个广播是系统发送的,其中存储的是上一次广播发送者的状态信息。也就是说,在Sticky模式下,广播接收者能立即得到一个广播,而不用等到这一周期结束。其实就是系统会保存Sticky的广播[1],当有新广播接收者来注册时,系统就把Sticky广播发给它。
以上我们对广播及广播接收者做了一些简单介绍,读者也可参考SDK文档中的相关说明来加强理解。
下面将以动态广播接收者为例,分析Android对广播的处理流程。
6.4.1 registerReceiver流程分析
1.ContextImpl registerReceiver分析
registerReceiver函数用于注册一个动态广播接收者,该函数在Context.java中声明。根据本章前面对Context家族的介绍(参考图6-3)可知,其功能最终将通过ContextImpl类的registerReceiver函数来完成。此处,可直接去看ContextImpl是如何实现此函数的。在SDK中一共定义了两个同名的registerReceiver函数,其代码如下:
[—>ContextImpl.java:registerReceiver]
/*
在SDK中输出该函数,这也是最常用的函数。当广播到来时,BroadcastReceiver对象的onReceive
函数将在主线程中被调用
*/
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter){
return registerReceiver(receiver, filter, null, null);
}
/*
功能和前面类似,但增加了两个参数,分别是broadcastPermission和scheduler,作用有
两个:
其一:对广播者的权限增加了限制,只有拥有相应权限的广播者发出的广播才能被此接收者接收
其二:BroadcastReceiver对象的onReceiver函数可调度到scheduler所在的线程中执行
*/
public Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter, String broadcastPermission, Handler scheduler){
/*
注意,下面所调用函数的最后一个参数为getOuterContext的返回值。前面曾说过,ContextImpl为
Context家族中真正干活的对象,而它对外的代理人可以是Application和Activity等,
getOuterContext就返回这个对外代理人。一般在Activity中调用registerReceiver函数,故此处
getOuterContext返回的对外代理人的类型就是Activity。
*/
return registerReceiverInternal(receiver, filter, broadcastPermission,
scheduler, getOuterContext());
}
殊途同归,最终的功能由registerReceiverInternal来完成,其代码如下:
[—>ContextImpl.java:registerReceiverInternal]
private Intent registerReceiverInternal(BroadcastReceiver receiver,
IntentFilter filter, String broadcastPermission, Handler scheduler,
Context context){
IIntentReceiver rd=null;
if(receiver!=null){
//①准备一个IIntentReceiver对象
if(mPackageInfo!=null&&context!=null){
//如果没有设置scheduler,则默认使用主线程的Handler
if(scheduler==null)scheduler=mMainThread.getHandler();
//通过getReceiverDispatcher函数得到一个IIntentReceiver类型的对象
rd=mPackageInfo.getReceiverDispatcher(
receiver, context, scheduler, mMainThread.getInstrumentation(),
true);
}else{
if(scheduler==null)scheduler=mMainThread.getHandler();
//直接创建LoadedApk.ReceiverDispatcher对象
rd=new LoadedApk.ReceiverDispatcher(receiver, context, scheduler,
null, true).getIIntentReceiver();
}//if(mPackageInfo!=null&&context!=null)结束
}//if(receiver!=null)结束
try{
//②调用AMS的registerReceiver函数
return ActivityManagerNative.getDefault().registerReceiver(
mMainThread.getApplicationThread(),mBasePackageName,
rd, filter, broadcastPermission);
}……
}
以上代码列出了两个关键点:其一是准备一个IIntentReceiver对象;其二是调用AMS的registerReceiver函数。
先来看IIntentReceiver,它是一个Interface,图6-17列出了和它相关的成员图谱。
图 6-17 IIntentReceiver相关成员示意图
由图6-17可知:
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。
接收广播的处理将放到本节最后再来分析,下面先来看AMS的registerReceiver函数。
2.AMS的registerReceiver分析
AMS的registerReceiver函数比较简单,但是由于其中将出现一些新的变量类型和成员,因此接下来按分两部分进行分析。
(1)registerReceiver分析之一
registerReceiver的返回值是一个Intent,它指向一个匹配过滤条件(由filter参数指明)的Sticky Intent。即使有多个符合条件的Intent,也只返回一个。
[—>ActivityManagerService.java:registerReceiver]
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
IIntentReceiver receiver, IntentFilter filter, String permission){
synchronized(this){
ProcessRecord callerApp=null;
if(caller!=null){
callerApp=getRecordForAppLocked(caller);
……//如果callerApp为null,则抛出异常,即系统不允许未登记照册的进程注册
//动态广播接收者
//检查调用进程是否有callerPackage的信息,如果没有,也抛异常
if(callerApp.info.uid!=Process.SYSTEM_UID&&
!callerApp.pkgList.contains(callerPackage)){
throw new SecurityException(……);
}
}……//if(caller!=null)判断结束
List allSticky=null;
//下面这段代码的功能是从系统中所有Sticky Intent中查询匹配IntentFilter的Intent,
//匹配的Intent保存在allSticky中
Iterator actions=filter.actionsIterator();
if(actions!=null){
while(actions.hasNext()){
String action=(String)actions.next();
allSticky=getStickiesLocked(action, filter, allSticky);
}
}……
//如果存在sticky的Intent,则选取第一个Intent作为本函数的返回值
Intent sticky=allSticky!=null?(Intent)allSticky.get(0):null;
//如果没有设置接收者,则直接返回sticky的Intent
if(receiver==null)return sticky;
//新的数据类型ReceiverList及成员变量mRegisteredReceivers,见下文的解释
//receiver.asBinder将返回IIntentReceiver的Bp端
ReceiverList rl
=(ReceiverList)mRegisteredReceivers.get(receiver.asBinder());
//如果是首次调用,则此处rl的值将为null
if(rl==null){
rl=new ReceiverList(this, callerApp, Binder.getCallingPid(),
Binder.getCallingUid(),receiver);
if(rl.app!=null){
rl.app.receivers.add(rl);
}else{
try{
//监听广播接收者所在进程的死亡消息
receiver.asBinder().linkToDeath(rl,0);
}……
rl.linkedToDeath=true;
}//if(rl.app!=null)判断结束
//将rl保存到mRegisterReceivers中
mRegisteredReceivers.put(receiver.asBinder(),rl);
}
//新建一个BroadcastFilter对象
BroadcastFilter bf=new BroadcastFilter(filter, rl, callerPackage,
permission);
rl.add(bf);//将其保存到rl中
//mReceiverResolver成员变量,见下文解释
mReceiverResolver.addFilter(bf);
以上代码的流程倒是很简单,不过其中出现的几个成员变量和数据类型却严重阻碍了我们的思维活动。先解决它们,BroadcastFilter及相关成员变量如图6-18所示。
图 6-18 BroadcastFilter及相关成员变量
结合代码,对图6-18中各数据类型和成员变量的作用及关系的解释如下:
在AMS中,BroadcastReceiver的过滤条件由BroadcastFilter表示,该类从Intent-Filter派生。由于一个BroadcastReceiver可设置多个过滤条件(即多次为同一个Broadcast-Receiver对象调用registerReceiver函数以设置不同的过滤条件),故AMS使用ReceiverList(从ArrayList<BroadcastFilter>派生)这种数据类型来表达这种一对多的关系。
ReceiverList除了能存储多个BroadcastFilter外,还应该有成员指向某一个具体BroadcastReceiver。如果不这样,那么又是如何知道到底是哪个BroadcastReceiver设置的过滤条件呢?前面说过,BroadcastReceiver接收广播是通过IIntentReceiver接口进行的,故ReceiverList中有receiver成员变量指向IIntentReceiver。
AMS提供mRegisterReceivers用于保存IIntentReceiver和对应ReceiverList的关系。此外,AMS还提供mReceiverResolver变量用于存储所有动态注册的Broadcast-Receiver所设置的过滤条件。
清楚这些成员变量和数据类型之间的关系后,接着来分析registerReceiver第二阶段的工作。(2)registerReceiver分析之二
这部分代码如下:
[—>ActivityManagerService.java:registerReceiver]
//如果allSticky不为空,则表示有Sticky的Intent,需要立即调度广播发送
if(allSticky!=null){
ArrayList receivers=new ArrayList();
receivers.add(bf);
int N=allSticky.size();
for(int i=0;i<N;i++){
Intent intent=(Intent)allSticky.get(i);
//为每一个需要发送的广播创建一个BroadcastRecord(暂称之为广播记录)对象
BroadcastRecord r=new BroadcastRecord(intent, null,
null,-1,-1,null, receivers, null,0,null, null,
false, true, true);
//如果mParallelBroadcasts当前没有成员,则需要触发AMS发送广播
if(mParallelBroadcasts.size()==0)
scheduleBroadcastsLocked();//向AMS发送BROADCAST_INTENT_MSG消息
//所有非ordered广播记录都保存在mParallelBroadcasts中
mParallelBroadcasts.add(r);
}//for循环结束
}//if(allSticky!=null)判断结束
return sticky;
}//synchronized结束
}
这一阶段的工作用一句话就能说清楚:为每一个满足IntentFilter的Sticky的Intent创建一个BroadcastRecord对象,并将其保存到mParllelBroadcasts数组中,最后,根据情况调度AMS发送广播。
从上边的描述中可以看出,一旦存在满足条件的Sticky的Intent,系统需要尽快调度广播发送。说到这里,想和读者分享一种笔者在实际工作中碰到的情况。
我们注册了一个BroadcastReceiver,用于接收USB的连接状态。在注册完后,它的onReceiver函数很快就会被调用。当时笔者的一些同事认为是注册操作触发USB模块又发送了一次广播,却又感到有些困惑,USB模块应该根据USB的状态变化去触发广播发送,而不应理会广播接收者的注册操作,这到底是怎么一回事呢?
相信读者现在应该能轻松解决这种困惑了吧?对于Sticky的广播,一旦有接收者注册,系统会马上将该广播传递给它们。
和ProcessRecord及ActivityRecord类似,AMS定义了一个BroadcastRecord数据结构,用于存储和广播相关的信息,同时还有两个成员变量,它们作用和关系如图6-19所示。
图 6-19 BroadcastReceiver及相关变量
图6-19比较简单,读者可自行研究。
在代码中,registerReceiver将调用scheduleBroadcastsLocked函数,通知AMS立即着手开展广播发送工作,在其内部就发送BROADCAST_INTENT_MSG消息给AMS。相关的处理工作将放到后面再来讨论。
[1]读者可以想一想,由于系统内部保存了Stikcy广播,当一个恶意程序频繁发送这种广播时,是否会把内存都耗光呢?