Android 发送广播

发送广播

各个APP可以通过binder调用AMS的广播发送接口,发送广播,各个APP是消息发布/订阅模型中的消息发布端

使用实例

广播按照处理的速度分为:前台广播和后台广播
广播按照AMS处理方式分为:普通广播和有序广播
同时还有黏性广播和非黏性广播(已经废弃,不做分析)

普通广播的发送

Intent intent = new Intent("Test");
//这个flag表明接收该广播的receiver只能动态注册
//intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
//设置显式的Intent,只有静态注册的receiver并且其ComponentName跟这里一致才可以收到
//intent.setComponent(new ComponentName(pkg, cls));
sendBroadcast(intent);

Intent.FLAG_RECEIVER_REGISTERED_ONLY
表示接收该广播的receiver只能动态注册,主要是因为一些病毒程序在被终止后,通过在xml中注册一些系统广播,企图通过一些高频次的广播来实现自启动。比如TIME_TICK这个广播就是每分钟发一次,有些应用会通过监听该广播来启动自己的service。

有序广播:

sendOrderedBroadcast(intent);

黏性广播:

@Deprecated 已经废弃
sendStickyBroadcast(intent);

前台广播:

//设置Flag FLAG_RECEIVER_FOREGROUND表明其为前台广播
intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
//发送时可以选择有序广播或者普通广播或者黏性广播

后台广播:

//未设置FLAG_RECEIVER_FOREGROUND,默认均为后台广播
//发送时可以选择有序广播或者普通广播或者黏性广播

广播发送

这里写图片描述

和前文Android BroadcastReceiver的注册 注册广播接收器类似,Activity调用sendBroadcast实际最后调用AMS的broadcastIntent函数,下面着重分析AMS的broadcastIntentLocked函数。

ActivityManagerService.broadcastIntentLocked
函数定义在frameworks/base/core/java/com/android/server/am/ActivityManagerService.java
该函数代码段很长,不全部贴出,总结一下主要流程,针对部分流程详细分析
1.protected broadcast权限检查
2.处理和package相关的广播以及其他一些系统广播
3.如果是sticky广播,需要更新系统中的sticky广播列表
4.查询和Intent匹配的静态注册的receivers
5.查询和intent匹配的动态注册的receivers
6.若是普通广播,则根据flag标志是前台广播还是后台广播,将动态receivers加入到对应的BroadcastQueue的并行的队列中
7.将静态注册的receivers和有序广播的匹配的动态注册的receivers加入到对应的BroadcastQueue的串行的队列中

Step 1 protected broadcast权限检查

保护性广播是指在一些AndroidManifest.xml中的一级标签<protected-broadcast> 中声明的Action。这类广播只能由系统发送,如果某个不具有系统权限的应用试图发送系统中的保护性广播,那么在这里就会被拦下。保护性广播的声明只能在系统App的AndroidManifest.xml中,系统App指/system/app/ /system/framework/ /system/priv-app/ /vendor/app/四个目录中的应用。第三方安装的应用的声明无效。
在frameworks/base/core/res/AndroidManifest.xml文件中,可以看到标记的具体写法:

<protected-broadcast android:name="android.intent.action.SCREEN_OFF" />
Step 2 处理部分系统广播和package相关的广播

处理如ACTION_PACKAGE_REMOVED,ACTION_PACKAGE_CHANGED等特殊广播,处理完是要继续的,不会被截掉,这样其他应用也能收到这类广播

Step 3 如果是sticky广播,需要更新系统中的sticky广播列表

由于sticky广播在API 21之后被废弃掉了,这里不做详细分析
其主要是检查权限,然后将广播加入到mStickyBroadcasts中。

Step 4 查询和Intent匹配的静态注册的receivers
if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
   receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
}

如果Intent未指定只发送给动态注册的接收器,则调用collectReceiverComponents函数获取匹配的静态注册的接收器信息,得到ResolveInfo列表。其主要是调用PMS的queryIntentReceivers函数获取匹配的ResolveInfo。
queryIntentReceivers
函数定义在frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

    public List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, int flags,
            int userId) {
        if (!sUserManager.exists(userId)) return Collections.emptyList();
        ComponentName comp = intent.getComponent();
        if (comp == null) {
            if (intent.getSelector() != null) {
                intent = intent.getSelector();
                comp = intent.getComponent();
            }     
        }     
        if (comp != null) {//隐式Intent
            List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
            ActivityInfo ai = getReceiverInfo(comp, flags, userId);
            if (ai != null) {
                ResolveInfo ri = new ResolveInfo();
                ri.activityInfo = ai; 
                list.add(ri);
            }     
            return list; 
        }     

        // reader
        synchronized (mPackages) {
            String pkgName = intent.getPackage();
            if (pkgName == null) {
                return mReceivers.queryIntent(intent, resolvedType, flags, userId);
            }     
            final PackageParser.Package pkg = mPackages.get(pkgName);
            if (pkg != null) {
                return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers,
                        userId);
            }     
            return null; 
        }     
    }

该函数主要分三步
1.如果是显示的Intent,即ComponentName不为空,则直接获取匹配的单个ResolveInfo
2.如果PackageName为空,则在所有的App中查找匹配的ResolveInfo列表
3.如果PackageName不为空,则在指定的Package中查找匹配的ResolveInfo

从第三点可以看出,发送广播时,可以指定发给对应的Package。

Step 5 查询和intent匹配的动态注册的receivers
//隐式Intent
if (intent.getComponent() == null) {
    //...这里省略了对UserId的处理,普通的广播执行下面的queryIntent函数
    registeredReceivers = mReceiverResolver.queryIntent(intent,
                        resolvedType, false, userId);
}

从这里可以看出,动态注册的广播接收器只可以接收使用隐式Intent发送的广播。另外动态注册的广播在接收普通广播时的顺序是先注册先接收。因为mReceiverResolver中是按照注册的先后顺序保存的接收器,queryIntent也是顺序匹配的,最后得到的列表就是按照注册的顺序排序的。

Step 6 若是普通广播,将动态receivers加入到对应的BroadcastQueue的并行的队列中
if (!ordered && NR > 0) {
    //如果该广播是普通广播,并且存在动态注册的接收器,那么先将该广播加入到BroadcastQueue中
    // 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, resolvedType, requiredPermissions,
                    appOp, brOptions, registeredReceivers, resultTo, resultCode, resultData,
                    resultExtras, ordered, sticky, false, userId);
    final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r); 
    if (!replaced) {
        queue.enqueueParallelBroadcastLocked(r); 
        queue.scheduleBroadcastsLocked();        
    }
    registeredReceivers = null;
    NR = 0;
}
  1. ordered表示是有序还是还是普通广播, NR > 0 表示存在接收该广播的动态注册的接收器。
  2. broadcastQueueForIntent函数会根据Intent中是否含有FLAG_RECEIVER_FOREGROUND标志来决定返回的是前台广播队列还是后台广播队列。 AMS初始化时会初始化两个队列,一个是前台广播队列,一个是后台广播队列,默认广播均为后台广播,若指定FLAG_RECEIVER_FOREGROUND标志,则表明其为前台广播。前台广播执行的速度更快。
  3. 构造一个BroadcastRecord, 其主要包含了需要分发的接收器对象
  4. 调用BroadcastQueue的enqueueParallelBroadcastLocked和scheduleBroadcastsLocked函数,将BroadcastRecord入队,然后启动分发。
public void enqueueParallelBroadcastLocked(BroadcastRecord r) { 
   mParallelBroadcasts.add(r);
   r.enqueueClockTime = System.currentTimeMillis();
}

入队操作很简单,即将BroadcastRecord加入到mParallelBroadcasts 列表中,并设置BroadcastRecord的入队时间为系统当前时间。mParallelBroadcasts表示并行队列。

Step 7 将静态注册的receivers和有序广播的匹配的动态注册的receivers加入到对应的BroadcastQueue的串行的队列中

串行队列表示一个接一个的处理,那就设计到顺序问题,Android中使用Priority来决定这个顺序。

// Merge into one list.
int ir = 0;
if (receivers != null) {
    //这里省略了对Package广播的一些处理,如包新增,重启,数据擦除等广播时,需要从静态注册的广播接收器中移除这部分的接收器
    int NT = receivers != null ? receivers.size() : 0;
    int it = 0;
    ResolveInfo curt = null;
    BroadcastFilter curr = null;
    if (needLog) Log.i("BR", "NT=" + NT + "NR=" + NR);
    //存在动态和静态receiver
    //这里只可能是有序广播,因为无序广播动态注册的receiver前面已经处理完,NR必定为0
    while (it < NT && ir < NR) {
        if (needLog) Log.i("BR", "while(it< NT && ir < NR) it = " + it + " ir=" + ir);
        if (curt == null) {
                    //初始的静态receivers是按照priority排列的吗?//TODO
            curr = registeredReceivers.get(ir);
        }
        if (curr.getPriority() >= curt.priority) {
            // Insert this broadcast record into the final list.
            receivers.add(it, curr);
            ir++;
            curr = null;
            it++;
            NT++;
        } else {
            // Skip to the next ResolveInfo in the final list.
            it++;
            curt = null;
        }
    }
}
//如果是普通广播,NR为0(前面处理掉了)
while (ir < NR) {
    if (receivers == null) {
        receivers = new ArrayList();
    }
    receivers.add(registeredReceivers.get(ir));
    ir++;
 }

该函数除了对package相关的广播的处理之外,主要是合并静态注册的广播接收器和动态注册的广播接收器,运行到此处,说明当前的广播为有序广播,因此这里需要按照priority进行排序。从排序过程来看,优先级高的在前面,同级别的广播,动态优先于静态(curr.getPriority() >= curt.priority,这里使用的是>=号),同级别的同类型广播,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的。

收集到所以的接收器后,需要加入队列

if ((receivers != null && receivers.size() > 0)
                || resultTo != null) {
    BroadcastQueue queue = broadcastQueueForIntent(intent);
    BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                    callerPackage, callingPid, callingUid, resolvedType,
                    requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode,
                    resultData, resultExtras, ordered, sticky, false, userId);


    boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r);
    if (!replaced) {
        queue.enqueueOrderedBroadcastLocked(r);
        queue.scheduleBroadcastsLocked();
    }
}

这里跟前面动态注册的接收器类似,主要是调用的enqueueOrderedBroadcastLocked,这个函数是将BroadcastRecord加入串行队列。

public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
    mOrderedBroadcasts.add(r);
    r.enqueueClockTime = System.currentTimeMillis();
}

入队操作很简单,即将BroadcastRecord加入到mOrderedBroadcasts 列表中,并设置BroadcastRecord的入队时间为系统当前时间。mOrderedBroadcasts表示串行队列。

从这里可以看出静态注册的广播是不管是有序广播还是普通广播均以串行方式分发的,这可能是因为静态注册的广播有可能需要启动进程,为避免并行启动,负载过高,所以静态注册的广播接收器均以串行方式分发

备注:
这里匹配注册的接收器时,还有个权限问题未分析,这里只做个简单的总结。
1.BroadcastReceiver注册时,可以指定权限,表明广播发送方需要具有相应的权限,才可以发送该广播
2.广播发送方,在sendBroadcast时也可以指定权限,表明只有接收方具有该权限时才可以收到

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值