品茗论道说广播(Broadcast内部机制讲解)(上)

侯 亮

1 概述

我们在编写Android程序时,常常会用到广播(Broadcast)机制。从易用性的角度来说,使用广播是非常简单的。不过,这个不是本文关心的重点,我们希望探索得再深入一点儿。我想,许多人也不想仅仅停留在使用广播的阶段,而是希望了解一些广播机制的内部机理。如果是这样的话,请容我斟一杯红茶,慢慢道来。

简单地说,Android广播机制的主要工作是为了实现一处发生事情,多处得到通知的效果。这种通知工作常常要牵涉跨进程通讯,所以需要由AMS(Activity Manager Service)集中管理。

在Android系统中,接收广播的组件叫作receiver,而且receiver还分为动态和静态的。动态receiver是在运行期通过调用registerReceiver()注册的,而静态receiver则是在AndroidManifest.xml中声明的。动态receiver比较简单,静态的就麻烦一些了,因为在广播递送之时,静态receiver所从属的进程可能还没有启动呢,这就需要先启动新的进程,费时费力。另一方面,有些时候用户希望广播能够按照一定顺序递送,为此,Android又搞出了ordered broadcast的概念。

细节如此繁杂,非一言可以说清。我们先从receiver这一侧入手吧。

2 两种receiver

Android中的receiver,分为“动态receiver”和“静态receiver”。

2.1 动态receiver

动态receiver必须在运行期动态注册,其实际的注册动作由ContextImpl对象完成:

?
1
2
3
4
5
6
7
8
9
10
11
12
@Override
public  Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) 
{    
     return  registerReceiver(receiver, filter,  null null );
}
@Override
public  Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
                                String broadcastPermission, Handler scheduler) 
{   
     return  registerReceiverInternal(receiver, filter, broadcastPermission,
                                     scheduler, getOuterContext());
}

注册之时,用户会把一个自定义的receiver对象作为第一个参数传入。当然,用户的receiver都是继承于BroadcastReceiver的。使用过广播机制的程序员,对这个BroadcastReceiver应该都不陌生,这里就不多说了。我们需要关心的是,这个registerReceiverInternal()内部还包含了什么重要的细节。

registerReceiverInternal()代码的截选如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private  Intent registerReceiverInternal(BroadcastReceiver receiver,
                                         IntentFilter filter, String broadcastPermission,
                                         Handler scheduler, Context context) 
{
     IIntentReceiver rd =  null ;    
     if  (receiver !=  null
     {        
         if  (mPackageInfo !=  null  && context !=  null
         {            
             if  (scheduler ==  null
             {
                 scheduler = mMainThread.getHandler();
             }            
             // 查找和context对应的“子哈希表”里的ReceiverDispatcher,如果找不到,就重新new一个
             rd = mPackageInfo.getReceiverDispatcher(receiver, context, scheduler,
                                                     mMainThread.getInstrumentation(),  true );
        
         . . . . . .
     }    
     try 
     {        
         return  ActivityManagerNative.getDefault().registerReceiver(
                 mMainThread.getApplicationThread(), mBasePackageName,
                 rd, filter, broadcastPermission);
    
     catch  (RemoteException e) 
     {        
         return  null ;
     }
}

请大家注意那个rd对象(IIntentReceiver rd)。我们知道,在Android架构中,广播动作最终其实都是由AMS递送出来的。AMS利用binder机制,将语义传递给各个应用进程,应用进程再辗转调用到receiver的onReceive(),完成这次广播。而此处的rd对象正是承担“语义传递工作“的binder实体。

为了管理这个重要的binder实体,Android搞出了一个叫做ReceiveDispatcher的类。该类的定义截选如下:

【frameworks/base/core/java/android/app/LoadedApk.java】

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static  final  class  ReceiverDispatcher 
{
     final  static  class  InnerReceiver  extends  IIntentReceiver.Stub {
         . . . . . .
         . . . . . .
     }
     final  IIntentReceiver.Stub mIIntentReceiver;    // 请注意这个域!它就是传到外面的rd。
     final  BroadcastReceiver mReceiver;
     final  Context mContext;
     final  Handler mActivityThread;
     final  Instrumentation mInstrumentation;
     final  boolean  mRegistered;
     final  IntentReceiverLeaked mLocation;
     RuntimeException mUnregisterLocation;
     boolean  mForgotten;
     . . . . . .

这样看来,“动态注册的BroadcastReceiver”和“ReceiverDispatcher节点”具有一一对应的关系。示意图如下:

一个应用里可能会注册多个动态receiver,所以这种一一对应关系最好整理成表,这个表就位于LoadedApk中。前文mPackageInfo.getReceiverDispatcher()一句中的mPackageInfo就是LoadedApk对象。

在Android的架构里,应用进程里是用LoadedApk来对应一个apk的,进程里加载了多少个apk,就会有多少LoadedApk。每个LoadedApk里会有一张“关于本apk动态注册的所有receiver”的哈希表(mReceivers)。当然,在LoadedApk初创之时,这张表只是个空表。

mReceivers表的定义如下:

?
1
2
3
private  final 
HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mReceivers
     new  HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();

该表的key项是我们比较熟悉的Context,也就是说可以是Activity、Service或Application。而value项则是另一张“子哈希表”。这是个“表中表”的形式。言下之意就是,每个Context(比如一个activity),是可以注册多个receiver的,这个很好理解。mReceivers里的“子哈希表”的key值为BroadcastReceiver,value项为ReceiverDispatcher,示意图如下:

图:客户进程中的mReceivers表

接下来我们继续看registerReceiverInternal(),它最终调用到

?
1
2
3
ActivityManagerNative.getDefault().registerReceiver(
                     mMainThread.getApplicationThread(), mBasePackageName,
                     rd, filter, broadcastPermission);

registerReceiver()函数的filter参数指明了用户对哪些intent感兴趣。对同一个BroadcastReceiver对象来说,可以注册多个感兴趣的filter,就好像声明静态receiver时,也可以为一个receiver编写多个<intent-filter>一样。这些IntentFilter信息会汇总到AMS的mRegisteredReceivers表中。在AMS端,我们可以这样访问相应的汇总表:

?
1
ReceiverList rl = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());

其中的receiver参数为IIntentReceiver型,正对应着ReceiverDispatcher中那个binder实体。也就是说,每个客户端的ReceiverDispatcher,会对应AMS端的一个ReceiverList。

ReceiverList的定义截选如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
class  ReceiverList  extends  ArrayList<BroadcastFilter>
         implements  IBinder.DeathRecipient 
{
     final  ActivityManagerService owner; 
     public  final  IIntentReceiver receiver;    
     public  final  ProcessRecord app;    
     public  final  int  pid;    
     public  final  int  uid;
     BroadcastRecord curBroadcast =  null ;
     boolean  linkedToDeath =  false ;
     String stringName;
     . . . . . .

ReceiverList继承于ArrayList<BroadcastFilter>,而BroadcastFilter又继承于IntentFilter,所以ReceiverList可以被理解为一个IntentFilter数组列表。

?
1
2
3
4
5
class  BroadcastFilter  extends  IntentFilter {
     final  ReceiverList receiverList;
     final  String packageName;
     final  String requiredPermission;
     . . . . . .

现在,我们可以绘制一张完整一点儿的图:

这张图只画了一个用户进程,在实际的系统里当然会有很多用户进程了,不过其关系是大致统一的,所以我们不再重复绘制。关于动态receiver的注册,我们就先说这么多。至于激发广播时,又会做什么动作,我们会在后文阐述,现在我们先接着说明和动态receiver相对的静态receiver。

2.2 静态receiver

静态receiver是指那些在AndroidManifest.xml文件中声明的receiver,它们的信息会在系统启动时,由Package Manager Service(PKMS)解析并记录下来。以后,当AMS调用PKMS的接口来查询“和intent匹配的组件”时,PKMS内部就会去查询当初记录下来的数据,并把结果返回AMS。有的同学认为静态receiver是常驻内存的,这种说法并不准确。因为常驻内存的只是静态receiver的描述性信息,并不是receiver实体本身。

在PKMS内部,会有一个针对receiver而设置的Resolver(决策器),其示意图如下:

关于PKMS的查询动作的细节,可参考PKMS的相关文档。目前我们只需知道,PKMS向外界提供了queryIntentReceivers()函数,该函数可以返回一个List<ResolveInfo>列表。

我们举个实际的例子:

?
1
2
3
4
Intent intent =  new  Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
List<ResolveInfo> ris =  null ; try  {
     ris = AppGlobals.getPackageManager().queryIntentReceivers(intent,  null 0 0 );
catch  (RemoteException e) {}

这是AMS的systemReady()函数里的一段代码,意思是查找有多少receiver对ACTION_PRE_BOOT_COMPLETED感兴趣。

ResolveInfo的定义截选如下:

?
1
2
3
4
5
6
7
8
9
10
public  class  ResolveInfo  implements  Parcelable 
{    
     public  ActivityInfo activityInfo;    
     public  ServiceInfo serviceInfo;    
     public  IntentFilter filter;    
     public  int  priority;    
     public  int  preferredOrder;    
     public  int  match;
     . . . . . .
     . . . . . .

总之,当系统希望发出一个广播时,PKMS必须能够决策出,有多少静态receiver对这个广播感兴趣,而且这些receiver的信息分别又是什么。

关于receiver的注册动作,我们就先说这么多。下面我们来看看激发广播时的动作。

3 激发广播

大家常见的激发广播的函数有哪些呢?从ContextImpl.java文件中,我们可以看到一系列发送广播的接口,列举如下:

  • public void sendBroadcast(Intent intent)

  • public void sendBroadcast(Intent intent, int userId)

  • public void sendBroadcast(Intent intent, String receiverPermission)

  • public void sendOrderedBroadcast(Intent intent, String receiverPermission)

  • public void sendOrderedBroadcast(Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)

  • public void sendStickyBroadcast(Intent intent)

  • public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)

其中sendBroadcast()是最简单的发送广播的动作。而sendOrderedBroadcast(),则是用来向系统发出有序广播(Ordered broadcast)的。这种有序广播对应的所有接收器只能按照一定的优先级顺序,依次接收intent。这些优先级一般记录在AndroidManifest.xml文件中,具体位置在<intent-filter>元素的android:priority属性中,其数值越大表示优先级越高,取值范围为-1000到1000。另外,有时候我们也可以调用IntentFilter对象的setPriority()方法来设置优先级。

对于有序广播而言,前面的接收者可以对接收到的广播intent进行处理,并将处理结果放置到广播intent中,然后传递给下一个接收者。需要注意的是,前面的接收者有权终止广播的进一步传播。也就是说,如果广播被前面的接收者终止了,那么后面的接收器就再也无法接收到广播了。

还有一个怪东西,叫做sticky广播,它又是什么呢?简单地说,sticky广播可以保证“在广播递送时尚未注册的receiver”,一旦日后注册进系统,就能够马上接到“错过”的sticky广播。有关它的细节,我们在后文再说。

在发送方,我们熟悉的调用sendBroadcast()的代码片段如下:

?
1
2
3
4
5
mContext = getApplicationContext(); 
Intent intent =  new  Intent();  
intent.setAction( "com.android.xxxxx" );  
intent.setFlags( 1 );  
mContext.sendBroadcast(intent);

上面的mContext的内部其实是在调用一个ContextImpl对象的同名函数,所以我们继续查看ContextImpl.java文件。

【frameworks/base/core/java/android/app/ContextImpl.java】

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public  void  sendBroadcast(Intent intent) 
{
     String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());    
     try 
     {
         intent.setAllowFds( false );
         ActivityManagerNative.getDefault().broadcastIntent(
             mMainThread.getApplicationThread(), intent, resolvedType,  null ,
             Activity.RESULT_OK,  null null null false false ,
             Binder.getOrigCallingUser());
     catch  (RemoteException e) {
     }
}

简单地调用broadcastIntent()向AMS发出请求了。

3.1 AMS一侧的broadcastIntentLocked()

用户进程把发送广播的语义传递到AMS之后,最终会由AMS的broadcastIntentLocked()处理。其原型如下:

?
1
2
3
4
5
6
7
8
9
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,                   
                                         int  userId)

broadcastIntentLocked()需要考虑以下方面的技术细节。

首先,有些广播intent只能由具有特定权限的进程发送,而有些广播intent在发送之前需要做一些其他动作。当然,如果发送方进程是系统进程、phone进程、shell进程,或者具有root权限的进程,那么必然有权发出广播。

另外,有时候用户希望发送sticky广播,以便日后注册的receiver可以收到“错过”的sticky广播。要达到这个目的,系统必须在内部维护一张sticky广播表,在具体的实现中,AMS会把广播intent加入mStickyBroadcasts映射表中。mStickyBroadcasts是一张哈希映射表,其key值为intent的action字符串,value值为“与这个action对应的intent数组列表”的引用。当我们发送sticky广播时,新的广播intent要么替换掉intent数组列表中的某项,要么作为一个新项被添加进数组列表,以备日后使用。

发送广播时,还需要考虑所发送的广播是否需要有序(ordered)递送。而且,receiver本身又分为动态注册和静态声明的,这让我们面对的情况更加复杂。从目前的代码来看,静态receiver一直是按照有序方式递送的,而动态receiver则需要根据ordered参数的值,做不同的处理。当我们需要有序递送时,AMS会把动态receivers和静态receivers合并到一张表中,这样才能依照receiver的优先级,做出正确的处理,此时动态receivers和静态receivers可能呈现一种交错顺序。

另一方面,有些广播是需要发给特定目标组件的,这个也要加以考虑。

现在我们来分析broadcastIntentLocked()函数。说得难听点儿,这个函数的实现代码颇有些裹脚布的味道,我们必须耐下性子解读这部分代码。经过一番努力,我们可以将其逻辑大致整理成以下几步:

1) 为intent添加FLAG_EXCLUDE_STOPPED_PACKAGES标记; 
2) 处理和package相关的广播; 
3) 处理其他一些系统广播; 
4) 判断当前是否有权力发出广播; 
5) 如果要发出sticky广播,那么要更新一下系统中的sticky广播列表; 
6) 查询和intent匹配的静态receivers; 
7) 查询和intent匹配的动态receivers; 
8) 尝试向并行receivers递送广播; 
9) 整合(剩下的)并行receivers,以及静态receivers,形成一个串行receivers表; 
10) 尝试逐个向串行receivers递送广播。

下面我们来详细说这几个部分。

3.1.1 为intent添加FLAG_EXCLUDE_STOPPED_PACKAGES标记

对应的代码为:

?
1
intent =  new  Intent(intent); // By default broadcasts do not go to stopped apps.intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);

为什么intent要添加FLAG_EXCLUDE_STOPPED_PACKAGES标记呢?原因是这样的,在Android 3.1之后,PKMS加强了对“处于停止状态的”应用的管理。如果一个应用在安装后从来没有启动过,或者已经被用户强制停止了,那么这个应用就处于停止状态(stopped state)。为了达到精细调整的目的,Android增加了2个flag:FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES,以此来表示intent是否要激活“处于停止状态的”应用。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
  * If set, this intent will not match any components in packages that
  * are currently stopped.  If this is not set, then the default behavior
  * is to include such applications in the result.
  */
public  static  final  int  FLAG_EXCLUDE_STOPPED_PACKAGES =  0x00000010 ;
/**
  * If set, this intent will always match any components in packages that
  * are currently stopped.  This is the default behavior when
  * {@link #FLAG_EXCLUDE_STOPPED_PACKAGES} is not set.  If both of these
  * flags are set, this one wins (it allows overriding of exclude for
  * places where the framework may automatically set the exclude flag).
  */
public  static  final  int  FLAG_INCLUDE_STOPPED_PACKAGES =  0x00000020 ;

从上面的broadcastIntentLocked()函数可以看到,在默认情况下,AMS是不会把intent广播发给“处于停止状态的”应用的。据说Google这样做是为了防止一些流氓软件或病毒干坏事。当然,如果广播的发起者认为自己的确需要广播到“处于停止状态的”应用的话,它可以让intent携带FLAG_INCLUDE_STOPPED_PACKAGES标记,从这个标记的注释可以了解到,如果这两个标记同时设置的话,那么FLAG_INCLUDE_STOPPED_PACKAGES标记会“取胜”,它会覆盖掉framework自动添加的FLAG_EXCLUDE_STOPPED_PACKAGES标记。

3.1.2 处理和package相关的广播

接下来需要处理一些系统级的“Package广播”,这些主要从PKMS(Package Manager Service)处发来。比如,当PKMS处理APK的添加、删除或改动时,一般会发出类似下面的广播:ACTION_PACKAGE_ADDED、ACTION_PACKAGE_REMOVED、ACTION_PACKAGE_CHANGED、ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE、ACTION_UID_REMOVED。

AMS必须确保发送“包广播”的发起方具有BROADCAST_PACKAGE_REMOVED权限,如果没有,那么AMS会抛出异常(SecurityException)。接着,AMS判断如果是某个用户id被删除了的话(Intent.ACTION_UID_REMOVED),那么必须把这件事通知给“电池状态服务”(Battery Stats Service)。另外,如果是SD卡等外部设备上的应用不可用了,这常常是因为卡被unmount了,此时PKMS会发出Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE,而AMS则需要把SD卡上的所有包都强制停止(forceStopPackageLocked()),并立即发出另一个“Package广播”——EXTERNAL_STORAGE_UNAVAILABLE。

如果只是某个外部包被删除或改动了,则要进一步判断intent里是否携带了EXTRA_DONT_KILL_APP额外数据,如果没有携带,说明需要立即强制结束package,否则,不强制结束package。看来有些应用即使在删除或改动了包后,还会在系统(内存)中保留下来并继续运行。另外,如果是删除包的话,此时要发出PACKAGE_REMOVED广播。

3.1.3 处理其他一些系统广播

broadcastIntentLocked()不但要对“Package广播”进行处理,还要关心其他一些系统广播。比如ACTION_TIMEZONE_CHANGED、ACTION_CLEAR_DNS_CACHE、PROXY_CHANGE_ACTION等等,感兴趣的同学可以自行研究这些广播的意义。

3.1.4 判断当前是否有权力发出广播

接着,broadcastIntentLocked()会判断当前是否有权力发出广播,代码截选如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*
  * Prevent non-system code (defined here to be non-persistent
  * processes) from sending protected broadcasts.
  */
if  (callingUid == Process.SYSTEM_UID || callingUid == Process.PHONE_UID
         || callingUid == Process.SHELL_UID || callingUid ==  0
{
     // Always okay.
else  if  (callerApp ==  null  || !callerApp.persistent) 
{
     try 
     {
         if  (AppGlobals.getPackageManager().isProtectedBroadcast(intent.getAction())) 
         {
             String msg =  "Permission Denial: not allowed to send broadcast "
                     + intent.getAction() +  " from pid="
                     + callingPid +  ", uid="  + callingUid;
             Slog.w(TAG, msg);
             throw  new  SecurityException(msg);
         }
    
     catch  (RemoteException e) 
     {
         Slog.w(TAG,  "Remote exception" , e);
         return  ActivityManager.BROADCAST_SUCCESS;
     }
}

如果发起方的Uid为SYSTEM_UID、PHONE_UID或SHELL_UID,或者发起方具有root权限,那么它一定有权力发送广播。

另外,还有一个“保护性广播”的概念,也要考虑进来。网上有一些人询问AndroidManifest.xml中的一级标记<protected-broadcast>是什么意思。简单地说,Google认为有一些广播是只能由系统发送的,如果某个系统级AndroidManifest.xml中写了这个标记,那么在PKMS解析该文件时,就会把“保护性广播”标记中的名字(一般是Action字符串)记录下来。在系统运作起来之后,如果某个不具有系统权限的应用试图发送系统中的“保护性广播”,那么到AMS的broadcastIntentLocked()处就会被拦住,AMS会抛出异常,提示"Permission Denial: not allowed to send broadcast"。

我们在frameworks/base/core/res/AndroidManifest.xml文件中,可以看到<protected-broadcast>标记的具体写法,截选如下:

3.1.5 必要时更新一下系统中的sticky广播列表

接着,broadcastIntentLocked()中会判断当前是否在发出sticky广播,如果是的话,必须把广播intent记录下来。

一开始会判断一下发起方是否具有发出sticky广播的能力,比如说要拥有android.Manifest.permission.BROADCAST_STICKY权限等等。判断合格后,broadcastIntentLocked()会更新AMS里的一张表——mStickyBroadcasts,其大致代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
     ArrayList<Intent> list = mStickyBroadcasts.get(intent.getAction());
     if  (list ==  null
     {
         list =  new  ArrayList<Intent>();
         mStickyBroadcasts.put(intent.getAction(), list);
     }
     int  N = list.size();
     int  i;
     for  (i= 0 ; i<N; i++) 
     {
         if  (intent.filterEquals(list.get(i))) 
         {
             // This sticky already exists, replace it.
             list.set(i,  new  Intent(intent));
             break ;
         }
     }
     if  (i >= N) 
     {
         list.add( new  Intent(intent));
     }

mStickyBroadcasts的定义是这样的:

?
1
2
     final  HashMap<String, ArrayList<Intent>> mStickyBroadcasts =
             new  HashMap<String, ArrayList<Intent>>();

上面代码的filterEquals()函数会比较两个intent的action、data、type、class以及categories等信息,但不会比较extra数据。如果两个intent的action是一样的,但其他信息不同,那么它们在ArrayList<Intent>中会被记成两个不同的intent。而如果发现新发送的intent在ArrayList中已经有个“相等的”旧intent时,则会用新的替掉旧的。

以后,每当注册新的动态receiver时,注册动作中都会遍历一下mStickyBroadcast表,看哪些intent可以和新receiver的filter匹配,只有匹配的intent才会递送给新receiver,示意图如下:

图中新receiver的filter只对a1和a3这两个action感兴趣,所以遍历时就不会考虑mStickyBroadcast表中的a2表项对应的子表,而a1、a3子表所对应的若干intent中又只有一部分可以和filter匹配,比如a1的intent1以及a3的intent2,所以图中只选择了这两个intent递送给新receiver。

除了记入mStickyBoradcast表的动作以外,sticky广播和普通广播在broadcastIntentLocked()中的代码是一致的,并没有其他什么不同了。

3.1.6 尝试向并行receivers递送广播

然后broadcastIntentLocked()会尝试向并行receivers递送广播。此时会调用到queue.scheduleBroadcastsLocked()。相关代码截选如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int  NR = registeredReceivers !=  null  ? registeredReceivers.size() :  0 ;
if  (!ordered && NR >  0
{
     // If we are not serializing this broadcast, then send the
     // registered receivers separately so they don't wait for the
     // components to be launched.
     final  BroadcastQueue queue = broadcastQueueForIntent(intent);
     BroadcastRecord r =  new  BroadcastRecord(queue, intent, callerApp,
             callerPackage, callingPid, callingUid, requiredPermission,
             registeredReceivers, resultTo, resultCode, resultData, map,
             ordered, sticky,  false );
     if  (DEBUG_BROADCAST) Slog.v(
             TAG,  "Enqueueing parallel broadcast "  + r);
     final  boolean  replaced = replacePending && queue.replaceParallelBroadcastLocked(r);
     if  (!replaced) {
         queue.enqueueParallelBroadcastLocked(r);
         queue.scheduleBroadcastsLocked();     // 注意这句。。。
     }
     registeredReceivers =  null ;
     NR =  0 ;
}

简单地说就是,new一个BroadcastRecord节点,并插入BroadcastQueue内的并行处理队列,最后发起实际的广播调度(scheduleBroadcastsLocked())。关于上面代码中的registeredReceivers列表,我们会在后文说明,这里先跳过。

其实不光并行处理部分需要一个BroadcastRecord节点,串行处理部分也需要BroadcastRecord节点。也就是说,要激发一次广播,AMS必须构造一个或两个BroadcastRecord节点,并将之插入合适的广播队列(mFgBroadcastQueue或mBgBroadcastQueue)。插入成功后,再执行队列的scheduleBroadcastsLocked()动作,进行实际的派发调度。示意图如下:

请注意图中BroadcastRecord节点所携带的节点链。在mParallelBroadcasts表中,每个BroadcastRecord只可能携带BroadcastFilter,因为平行处理的节点只会对应动态receiver,而所有静态receiver只能是串行处理的。另一方面,在mOrderedBroadcasts表中,BroadcastRecord中则既可能携带BroadcastFilter,也可能携带ResolveInfo。这个其实很容易理解,首先,ResolveInfo对应静态receiver,放到这里自不待言,其次,如果用户在发送广播时明确指定要按ordered方式发送的话,那么即使目标方的receiver是动态注册的,它对应的BroadcastFilter也会被强制放到这里。

好,现在让我们再整合一下思路。BroadcastRecord节点内部的receivers列表,记录着和这个广播动作相关的目标receiver信息,该列表内部的子节点可能是ResolveInfo类型的,也可能是BroadcastFilter类型的。ResolveInfo是从PKMS处查到的静态receiver的描述信息,它的源头是PKMS分析的那些AndroidManifest.xml文件。而BroadcastFilter事实上来自于本文一开始阐述动态receiver时,提到的AMS端的mRegisteredReceivers哈希映射表。现在,我们再画一张示意图:

因为BroadcastRecord里的BroadcastFilter,和AMS的mRegisteredReceivers表中(间接)所指的对应BroadcastFilter是同一个对象,所以我是用虚线将它们连起来的。

Ok,我们接着看scheduleBroadcastsLocked()动作。scheduleBroadcastsLocked()的代码如下:

【frameworks/base/services/java/com/android/server/am/BroadcastQueue.java】

?
1
2
3
4
5
6
7
8
9
10
public  void  scheduleBroadcastsLocked() 
{
     . . . . . .
     if  (mBroadcastsScheduled) 
     {
         return ;
     }
     mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG,  this ));
     mBroadcastsScheduled =  true ;
}

发出BROADCAST_INTENT_MSG消息。

上面用到的mHandler是这样创建的:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
final  Handler mHandler =  new  Handler() 
{
     public  void  handleMessage(Message msg) 
     {
         switch  (msg.what) 
         {
             case  BROADCAST_INTENT_MSG: 
             {
                 if  (DEBUG_BROADCAST) 
                     Slog.v(TAG,  "Received BROADCAST_INTENT_MSG" );
                 processNextBroadcast( true );
            
             break ;
             
             case  BROADCAST_TIMEOUT_MSG: 
             {
                 synchronized  (mService) 
                 {
                     broadcastTimeoutLocked( true );
                 }
            
             break ;
         }
     }
};

也就是说,AMS端会在BroadcastQueue.java中的processNextBroadcast()具体处理广播。

3.1.7 整理两个receiver列表

我们前文已经说过,有些广播是需要有序递送的。为了合理处理“有序递送”和“平行递送”,broadcastIntentLocked()函数内部搞出了两个list:

?
1
2
List receivers =  null ;
List<BroadcastFilter> registeredReceivers =  null ;

其中,receivers主要用于记录“有序递送”的receiver,而registeredReceivers则用于记录与intent相匹配的动态注册的receiver。

关于这两个list的大致运作是这样的,我们先利用包管理器的queryIntentReceivers()接口,查询出和intent匹配的所有静态receivers,此时所返回的查询结果本身已经排好序了,因此,该返回值被直接赋值给了receivers变量,代码如下:

?
1
receivers = AppGlobals.getPackageManager().queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, userId);

而对于动态注册的receiver信息,就不是从包管理器获取了,这些信息本来就记录在AMS之中,此时只需调用:

?
1
registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType,  false , userId);

就可以了。注意,此时返回的registeredReceivers中的子项是没有经过排序的。而关于PKMS的queryIntentReceivers(),我们可以参考PKMS的专题文档,此处不再赘述。

如果我们要“并行递送”广播, registeredReceivers中的各个receiver会在随后的queue.scheduleBroadcastsLocked()动作中被并行处理掉。如果大家折回头看看向并行receivers递送广播的代码,会发现在调用完queue.scheduleBroadcastsLocked()后,registeredReceivers会被强制赋值成null值。

如果我们要“串行递送”广播,那么必须考虑把registeredReceivers表合并到receivers表中去。我们知道,一开始receivers列表中只记录了一些静态receiver,这些receiver将会被“有序递送”。现在我们只需再遍历一下registeredReceivers列表,并将其中的每个子项插入到receivers列表的合适地方,就可以合并出一条顺序列表了。当然,如果registeredReceivers已经被设为null了,就无所谓合并了。

为什么静态声明的receiver只会“有序递送”呢?我想也许和这种receiver的复杂性有关系,因为在需要递送广播时,receiver所属的进程可能还没有启动呢,所以也许会涉及到启动进程的流程,这些都是比较复杂的流程。

当然,上面所说的是没有明确指定目标组件的情况,如果intent里含有明确的目标信息,那么就不需要调用包管理器的queryIntentReceivers()了,只需new一个ArrayList,并赋值给receivers,然后把目标组件对应的ResolveInfo信息添加进receivers数组列表即可。

3.1.8 尝试逐个向串行receivers递送广播

当receivers列表整理完毕之后,现在要开始尝试逐个向串行receivers递送广播了。正如前文所说,这里要重新new一个新的BroadcastRecord节点:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if  ((receivers !=  null  && receivers.size() >  0 )
     || resultTo !=  null
{
     BroadcastQueue queue = broadcastQueueForIntent(intent);
     BroadcastRecord r =  new  BroadcastRecord(queue, intent, callerApp,
             callerPackage, callingPid, callingUid, requiredPermission,
             receivers, resultTo, resultCode, resultData, map, ordered,
             sticky,  false );
     . . . . . .
     boolean  replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); 
     if  (!replaced) {
         queue.enqueueOrderedBroadcastLocked(r);
         queue.scheduleBroadcastsLocked();
     }
}

而scheduleBroadcastsLocked()最终会间接导致走到 BroadcastQueue.java中的processNextBroadcast()。这一点和前文所说的“向并行receivers递送广播”的动作基本一致。

品茗论道说广播(Broadcast内部机制讲解)(上)相关推荐

  1. 品茗论道说广播(Broadcast内部机制讲解)

    1 概述 我们在编写Android程序时,常常会用到广播(Broadcast)机制.从易用性的角度来说,使用广播是非常简单的.不过,这个不是本文关心的重点,我们希望探索得再深入一点儿.我想,许多人也不 ...

  2. (链接)品茗论道说广播(Broadcast内部机制讲解)

    我们在编写Android程序时,常常会用到广播(Broadcast)机制.从易用性的角度来说,使用广播是非常简单的.不过,这个不是本文关心的重点,我们希望探索得再深入一点儿.我想,许多人也不想仅仅停留 ...

  3. Android Loader 异步加载详解二:探寻Loader内部机制

    Android Loader 异步加载详解二:探寻Loader内部机制 转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/7025991 ...

  4. Android四大组件系列7 Broadcast广播机制(上)

    一 概述 广播 (Broadcast) 机制用于进程或线程间通信,广播分为广播发送和广播接收两个过程,其中广播接收者 BroadcastReceiver 是 Android 四大组件之一.Broadc ...

  5. 【收藏】万字综述,核心开发者全面解读PyTorch内部机制

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:Edward Z.Yang,Pytorch核心开发者 斯坦福大学博 ...

  6. 综述|核心开发者全面解读Pytorch内部机制

    ↑ 点击蓝字 关注视学算法 作者丨Edward Z. Yang 来源丨机器之心 编辑丨极市平台 极市导读 Edward Z. Yang 是PyTorch开源项目的核心开发者之一.在PyTorch纽约聚 ...

  7. 全面解读PyTorch内部机制

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自|深度学习这件小事 斯坦福大学博士生与 Facebook ...

  8. 万字综述,核心开发者全面解读PyTorch内部机制

    选自ezyang博客 作者:Edward Z. Yang 机器之心编译 参与:panda 斯坦福大学博士生与 Facebook 人工智能研究所研究工程师 Edward Z. Yang 是 PyTorc ...

  9. Android中的广播Broadcast详解

    今天来看一下Android中的广播机制,我们知道广播Broadcast是Android中的四大组件之一,可见他的重要性了,当然它的用途也很大的,比如一些系统的广播:电量低.开机.锁屏等一些操作都会发送 ...

最新文章

  1. vs2010快捷方式
  2. 1051 Pop Sequence(两种双指针思路)
  3. python requests post请求_实例解析Python3 如何利用requests 库进行post携带账号密码请求数据...
  4. GPT-3获NeurIPS 2020最佳论文奖,苹果华人学者获经典论文奖
  5. 软件开发定律系列之布鲁克斯定律有感
  6. 【五校联考3day2】B
  7. “黑天鹅”,正在改变 AI 落地医疗领域的加速度
  8. java复制和上传_java文件上传复制等功能
  9. LinQ学习之旅(1)
  10. H3C OSPF综合实验
  11. 关于mongodb的学习与探索二
  12. 计算机网络故障检测,计算机网络故障处理
  13. linux 终端复制剪贴板,命令行剪贴板复制和粘贴工具?
  14. ComputeShader
  15. 超好用的线上评选微信小程序、投票微信小程序
  16. iOS 支付宝授权登录,思路
  17. python 小说爬虫_初次尝试python爬虫,爬取小说网站的小说。
  18. 计算机组成原理思维导图——中央处理器(CPU)
  19. 眼部识别和指纹识别,你会选择谁?
  20. Cantor‘s paradox

热门文章

  1. PYCHARM 之 VIM 操作
  2. video在某些浏览器默认静音
  3. ZBC陆续在主要CEX开启Staking,锁定市场大部分流通量成大利好
  4. 游戏陪玩公众号H5软件开发方案图文详解
  5. 【有奖调研】即时消息服务的产品需求规划就交给你了
  6. 线性代数学习打卡(一)向量概述
  7. mysql skip用法_MySQL性能参数详解之Skip-External-Locking参数介绍
  8. 物联网通讯协议之MQTT协议详解(V3.1.1)
  9. javascript光速入门
  10. [CF1512F] Education题解