6.4.2 sendBroadcast流程分析
在SDK中同样定义了几个函数用于发送广播。不过,根据之前的经验,最终和AMS交互的函数可能通过一个接口就能完成。下面来看最简单的广播发送函数sendBroadcast,其代码如下:
[—>ContextImpl.java:sendBroadcast]
public void sendBroadcast(Intent intent){
String resolvedType=intent.resolveTypeIfNeeded(getContentResolver());
try{
intent.setAllowFds(false);
//调用AMS的brodcastIntent,在SDK中定义的广播发送函数最终都会调用它
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(),intent, resolvedType, null,
Activity.RESULT_OK, null, null, null, false, false);
}……
}
AMS的broadcastIntent函数的主要工作将交由AMS的broadcastIntentLocked来完成,故此处直接分析broadcastIntentLocked。
我们分阶段来分析broadcastIntentLocked的工作,先来看第一阶段工作。
1.broadcastIntentLocked分析之一
这部分的代码如下:
[—>ActivityManagerService.java:broadcastIntentLocked]
private final int broadcastIntentLocked(ProcessRecord callerApp,
String callerPackage, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle map, String requiredPermission,
boolean ordered, boolean sticky, int callingPid, int callingUid){
intent=new Intent(intent);
//为Intent增加FLAG_EXCLUDE_STOPPED_PACKAGES标志,表示该广播不会传递给被STOPPED
//的Package
intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
//处理一些特殊的广播,包括UID_REMOVED, PACKAGE_REMOVED和PACKAGE_ADDED等
final boolean uidRemoved=Intent.ACTION_UID_REMOVED.equals(
intent.getAction());
……//处理特殊的广播,主要和PACKAGE相关,例如接收到PACKAGE_REMOVED广播后,AMS
//需将该Package的组件从相关成员中删除,相关代码可自行阅读
//处理TIME_ZONE变化广播
if(intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction()))
mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
//处理CLEAR_DNS_CACHE广播
if(intent.ACTION_CLEAR_DNS_CACHE.equals(intent.getAction()))
mHandler.sendEmptyMessage(CLEAR_DNS_CACHE);
//处理PROXY_CHANGE广播
if(Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())){
ProxyProperties proxy=intent.getParcelableExtra("proxy");
mHandler.sendMessage(mHandler.obtainMessage(
UPDATE_HTTP_PROXY, proxy));
}
由以上代码可知,broadcastIntentLocked第一阶段的工作主要是处理一些特殊的广播消息。下面来看broadcastIntentLocked第二阶段的工作。
2.broadcastIntentLocked分析之二
这部分的代码如下:
[—>ActivityManagerService.java:broadcastIntentLocked]
//处理发送sticky广播的情况
if(sticky){
……//检查发送进程是否有BROADCAST_STICKY权限
if(requiredPermission!=null){
//在发送Sticky广播的时候,不能携带权限信息
return BROADCAST_STICKY_CANT_HAVE_PERMISSION;
}
//在发送Stikcy广播的时候,也不能指定特定的接收对象
if(intent.getComponent()!=null)……//抛出异常
//将这个Sticky的Intent保存到mStickyBroadcasts中
ArrayList<Intent>list=mStickyBroadcasts.get(intent.getAction());
if(list==null){
list=new ArrayList<Intent>();
mStickyBroadcasts.put(intent.getAction(),list);
}
……//如果list中已经有该Intent,则直接用新的Intent替换旧的Intent
//否则将该Intent加入到list中保存
if(i>=N)list.add(new Intent(intent));
}
//定义两个变量,其中receivers用于保存匹配该Intent的所有广播接收者,包括静态或动态
//注册的广播接收者,registeredReceivers用于保存符合该Intent的所有动态注册者
List receivers=null;
List<BroadcastFilter>registeredReceivers=null;
try{
if(intent.getComponent()!=null){
……//如果指定了接收者,则从PKMS中查询它的信息
}else{
//FLAG_RECEIVER_REGISTERED_ONLY标签表明该广播只能发给动态注册者
if((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)==0){
//如果没有设置前面的标签,则需要查询PKMS,获得那些在AndroidManifest.xml
//中声明的广播接收者,即静态广播接收者的信息,并保存到receivers中
receivers=
AppGlobals.getPackageManager().queryIntentReceivers(
intent, resolvedType, STOCK_PM_FLAGS);
}
//再从AMS的mReceiverResolver中查询符合条件的动态广播接收者
registeredReceivers=mReceiverResolver.queryIntent(intent,
resolvedType, false);
}
}……
/*
判断该广播是否设置了REPLACE_PENDING标签。如果设置了该标签,并且之前的那个Intent还没
有被处理,则可以用新的Intent替换旧的Intent。这么做的目的是减少不必要的广播发送,但笔
者感觉,这个标签的作用并不靠谱,因为只有旧的Intent没被处理的时候,它才能被替换。因为旧
Intent被处理的时间不能确定,所以不能保证广播发送者的一番“好意”能够实现。因此,在发送广
播时,千万不要以为设置了该标志就一定能节约不必要的广播发送。
*/
final boolean replacePending=
(intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING)!=0;
//先处理动态注册的接收者
int NR=registeredReceivers!=null?registeredReceivers.size():0;
//如果此次广播为非串行化发送,并且符合条件的动态注册接收者个数不为零
if(!ordered&&NR>0){
//创建一个BroadcastRecord对象即可,注意,一个BroadcastRecord对象可包括所有的
//接收者(可参考图6-19)
BroadcastRecord r=new BroadcastRecord(intent, callerApp,
callerPackage, callingPid, callingUid, requiredPermission,
registeredReceivers, resultTo, resultCode, resultData, map,
ordered, sticky, false);
boolean replaced=false;
if(replacePending){
……//从mParalledBroadcasts中查找是否有旧的Intent,如果有就替代它,并设置
//replaced为true
}
if(!replaced){//如果没有被替换,则保存到mParallelBroadcasts中
mParallelBroadcasts.add(r);
scheduleBroadcastsLocked();//调度一次广播发送
}
//至此,动态注册的广播接收者已处理完毕,设置registeredReceivers为null
registeredReceivers=null;//
NR=0;
}
broadcastIntentLocked第二阶段的工作有两项:
查询满足条件的动态广播接收者及静态广播接收者。
当本次广播不为ordered时,需要尽快发送该广播。另外,非ordered的广播都被AMS保存在mParallelBroadcasts中。
3.broadcastIntentLocked分析之三
下面来看broadcastIntentLocked最后一阶段的工作,其代码如下:
[—>ActivityManagerService.java:broadcastIntentLocked]
int ir=0;
if(receivers!=null){
String skipPackages[]=null;
……//处理PACKAGE_ADDED的Intent,系统不希望有些应用程序一安装就启动。
//实现这一目的的工作原理即是在该程序内部监听PACKAGE_ADDED广播。如果系统
//没有这一招防备,则PKMS安装完程序后所发送的PAKCAGE_ADDED消息将触发该应用的启动
……//处理ACTION_EXTERNAL_APPLICATIONS_AVAILABLE广播
……//将动态注册的接收者registeredReceivers的元素合并到receivers中去
//处理完毕后,所有的接收者(无论动态还是静态注册的)都存储到receivers变量中了
if((receivers!=null&&receivers.size()>0)||resultTo!=null){
//创建一个BroadcastRecord对象,注意它的receivers中包括所有的接收者
BroadcastRecord r=new BroadcastRecord(intent, callerApp,
callerPackage, callingPid, callingUid, requiredPermission,
receivers, resultTo, resultCode, resultData, map, ordered,
sticky, false);
boolean replaced=false;
if(replacePending){
……//替换mOrderedBroadcasts中旧的Intent
}//
if(!replaced){//如果没有替换,则添加该条广播记录到mOrderedBroadcasts中
mOrderedBroadcasts.add(r);
scheduleBroadcastsLocked();//调度AMS进行广播发送工作
}
}
return BROADCAST_SUCCESS;
}
由以上代码可知,AMS将动态注册者和静态注册者都合并到receivers中去了。注意,如果本次广播不是ordered,那么表明动态注册者就已经在第二阶段工作中被处理了。因此在合并时,将不会有动态注册者被加到receivers中。最终所创建的广播记录存储在mOrderedBroadcasts中,也就是说,不管是否串行化发送,静态接收者对应的广播记录都将保存在mOrderedBroadcasts中。
为什么不将它们保存在mParallelBroadcasts中呢?结合代码会发现,保存在mParallel-Broadcasts中的BroadcastRecord所包含的都是动态注册的广播接收者信息,这是因为动态接收者所在的进程是已经存在的(如果该进程不存在,如何去注册动态接收者呢?),而静态广播接收者就不能保证它已经和一个进程绑定在一起了(静态广播接收者此时可能还仅仅是在AndroidManifest.xml中声明的一个信息,相应的进程并未创建和启动)。根据前一节分析的Activity启动流程,AMS还需要进行创建应用进程,初始化Android运行环境等一系列复杂的操作。读到后面内容时你会发现,AMS将在一个循环中逐条处理mParallelBroadcasts中的成员(即派发给接收者)。如果将静态广播接收者也保存到mParallelBroadcasts中,会有什么后果呢?假设这些静态接收者所对应的进程全部未创建和启动,那么AMS将在那个循环中创建多个进程!这样,系统压力一下就上去了。所以,对于静态注册者,它们对应的广播记录都被放到mOrderedBroadcasts中保存。AMS在处理这类广播信息时,一个进程一个进程地处理,只有处理完一个接收者,才继续下一个接收者。这种做法的好处是,避免了惊群效应的出现,坏处则是延时较长。
下面进入AMS的BROADCAST_INTENT_MSG消息处理函数,看看情况是否如上所说。