品茗论道说广播(Broadcast内部机制讲解)(上)
Posted 专注专业
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了品茗论道说广播(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递送广播”的动作基本一致。
转自http://blog.csdn.net/codefly/article/details/42322695
以上是关于品茗论道说广播(Broadcast内部机制讲解)(上)的主要内容,如果未能解决你的问题,请参考以下文章
详解广播机制,Broadcast Receiver,Android进阶必备知识。
详解广播机制,Broadcast Receiver,Android进阶必备知识。(附使用广播实现一键强制下线功能)