BroadcastReciever

 

 以上两张图转自gityuan的http://gityuan.com/images/ams/broadcast/broadcast_record.jpg以及http://gityuan.com//images/ams/send_broadcast.jpg

 

1.在应用进程中,一个Receiver对象,无论使用多少个Intent Filter注册,在应用进程中只会产生一个ReceiverDispatcher。如果是使用同一个Receiver对象和同一个IntentFilter对象,是重复注册,不会真正成功注册。在AMS中会使用IntentResolver#filterEquals方法去判断两个IntentFilter是否相等,如果有同一ReceiverList中BroadcastFilter对应的IntentFilter和目前注册的IntentFilter值相等,则无法注册。

2.在应用进程中不会存储和IntentFilter及其相关的对象。而在AMS中,每个IntentFilter都对应了一个BroadcastFilter。而Broadcast Filter是派发广播的基本单位。最终会调用BroadcastFilter中对应的IIntentReceiver,即InneReceiver的Bp端。

3.为什么AMS中的成员mRegisteredReceiver:<IIntentReceiver,ReceiverList<BroadcastFilter>>,一个IIntentReceiver可以对应多个BroadcastFilter?  因为一个BroadcastReceiver对象,和不同的IntentFilter对象注册,那么应用进程中不用重新创建一个ReceiverDispatcher,只需用原来的就可以了。

4.既然一个IIntentReceiver对应多个BroadcastFilter,那么BroadcastFilter中为什么保存了一个ReceiverList成员,这是因为一个BroadcastFilter也可以对应多个BroadcastFilter吗?  从源码的注释可以知道,这是一个向后索引,即ReceiverFilter成员是该BroadcastFilter的容器。而BroadcastFilter本身是没有保存IIntentFilter的引用了,而派发广播的时候是需要调用IIntentReceiver的performReceive方法的,所以BroadcastFilter需要ReceiverFilter成员去获取到IIntentReceiver,因为ReceiverList有一个IIntentReceiver成员。

5.IIntentReceiver与BroadcastFilter的关系是一对多的,即一个IIntentReceiver可以对应多个BroadcastFilter,而一个BroadcastFilter只能对应一个IIntentReceiver。

6.在ActivityThread中没有保存IIntentReceiver的引用,只在LoadedApk中保存了,在对应组件退出后,就会由应用进程主动发起注销该组建注册的Receiver和解绑该组件对Service的绑定。

7.在决定是否将某个广播派发给Receiver,会检查双方的权限。

8.关于前台广播和后台广播的区别:

在前台广播和后台广播之间,并不会优先处理完所有前台广播再处理后台广播。而是接收前台广播的的进程在处理该广播时该进程会被视为前台进程,即提高了进程优先级。但是处理前台广播的超时时间比后台时间的要短的。还有BroadcastQueue#mDelayBehindService的区别。

9.只有串行执行的广播,才会有超时限制,所有静态Receiver无论接收的是否串行广播,都会串行执行,而动态注册的Receiver在接收串行广播时是串行执行的,而接收非串行广播时是并行执行的。即动态注册的Receiver接收非串行广播时,执行结束后是不用调用AMS#finisheReceiver告诉AMS的。

 

问题:

1.串行广播是否需要上个receiver处理完毕还是只需把intent传到目标进程就好,那么又是怎么知道什么时候上个receiver已经处理完?

2.一个普通广播,是否一般需要创建两个BroadcastRecord,因为一个要存储BroadcastFilter放在mParallelBroadcasts中,一个要存储ResolveInfo放在mOrderedBroadcasts中?

 

 

只有串行广播才会调用BroadcastQueue#broadcastTimeoutLocked()和BroadcastQueue#cancelBroadcastTimeoutLocked(),分别时发送定时消息和取消定时消息(以上两个方法在BroadcastQueue#processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj)中被调用,有兴趣的可以去看看代码)。所以只有串行广播才会有超时限制,即onReceiver需要在一定时间内返回,否则报ANR。在测试也也验证了这一点。sendOrderedBroadcast发送广播会超时报ANR,而直接使用sendBroadcast不会超时。不用的sdk版本有不同的超时限制,7.0中的前台广播超时是20s,而9.0中是10s,后台广播60s。

 

对于那些因为需要等待静态Receiver所在进程启动的广播,会将该广播放在BroadcastQueue的mPendingBroadcast中。在进程启动后,调用AMS#attachApplicationLocked时会去检查是否有pending Broadcast等待该进程。如果一个BroadcastQueue的mPendingBroadcast不为null,则不处理有序广播,而mParallelBroadcasts中的BroadcastRecord还是可以继续被处理的。而mFgBroadcastQueue和mBgBroadcastQueue的是互不影响的,即mFgBroadcastQueue的mPendingBroadcast不为null,如果此时mBgBroadcastQueue#processNextBroadcastsLocked被调用后,mBgBroadcastQueue中的有序广播可以继续处理。

AMS#attachApplicationLocked

        // See if the top visible activity is waiting to run in this process...
        if (normalMode) {
            try {
                if (mStackSupervisor.attachApplicationLocked(app)) {
                    didSomething = true;
                }
            } catch (Exception e) {
                Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
                badApp = true;
            }
        }

        // Find any services that should be running in this process...
        if (!badApp) {
            try {
                didSomething |= mServices.attachApplicationLocked(app, processName);
                checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");
            } catch (Exception e) {
                Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
                badApp = true;
            }
        }

        // Check if a next-broadcast receiver is in this process...
        if (!badApp && isPendingBroadcastProcessLocked(pid)) {
            try {
                didSomething |= sendPendingBroadcastsLocked(app);
                checkTime(startTime, "attachApplicationLocked: after sendPendingBroadcastsLocked");
            } catch (Exception e) {
                // If the app died trying to launch the receiver we declare it 'bad'
                Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
                badApp = true;
            }
        }

上面的代码中就是处理那些因进程启动而未被执行的Activity,Service,Receiver。contentprovider则无论是否有其他进程查询,都会在进程handleBindApplication中会安装在应用进程,并发布到AMS中。所以attachApplicationLocked中没有处理对content provider的启动。在调用IApplicationThread#scheduleReceivered后,AMS不会马上processNextReceiver,而是等待onReceive执行完毕后,调用AMS#finishReceiver,其中会判断这个Receiver是否是ordered,如果是则调用processNextReceiver。

 

BroadcastQueue#scheduleBroadcastsLocked

    public void scheduleBroadcastsLocked() {
        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
                + mQueueName + "]: current="
                + mBroadcastsScheduled);

        if (mBroadcastsScheduled) {
            return;
        }
        mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
        mBroadcastsScheduled = true;
    }

 

mFgBroadcastQueue和mBgBroadcastQueue初始化:

        mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
                "foreground", BROADCAST_FG_TIMEOUT, false);
        mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
                "background", BROADCAST_BG_TIMEOUT, true);
        mBroadcastQueues[0] = mFgBroadcastQueue;
        mBroadcastQueues[1] = mBgBroadcastQueue;
    BroadcastQueue(ActivityManagerService service, Handler handler,
            String name, long timeoutPeriod, boolean allowDelayBehindServices) {
        mService = service;
        mHandler = new BroadcastHandler(handler.getLooper());
        mQueueName = name;
        mTimeoutPeriod = timeoutPeriod;
        mDelayBehindServices = allowDelayBehindServices;
    }

 

/**
 * If set, when sending a broadcast the recipient is allowed to run at
 * foreground priority, with a shorter timeout interval.  During normal
 * broadcasts the receivers are not automatically hoisted out of the
 * background priority class.
 */
public static final int FLAG_RECEIVER_FOREGROUND = 0x10000000;

 

 

 

 

sendBroadcast:

三种broadcast,分别是普通broadcast,ordered broadcast,sticky broadcast

AMS#broadcastIntentLocked:

1.根据flag是否包含FLAG_RECEIVER_FOREGROUND选择mFgBroadcastQueue还是mBgBroadcastQueue。

2.根据广播是否ordered,分情况处理。如果非order广播,那么需要创建两个BroadcastRecord;首先是创建一个BroadcastRecord把所有符合这个intent的代表RegisteredReceiver的BroadcastFilter放进去,然后这个BroadcastRecord通过BroadcastQueue#enqueueParallelBroadcastLocked放入mParallelBroadcasts,然后调用该BroadcastQueue#scheduleBroadcastsLocked。上面说了创建第一个BroadcastRecord,现在说说要创建的第二个BroadcastRecord,这个Record存放匹配该广播intent的所有ResolveInfo,即代表静态广播的在manifest中的注册信息。接下来的要做的和第一个BroadcastRecord一样。

BroadcastQueue#scheduleBroadcastsLocked

1.scheduleBroadcastsLocked->发送BROADCAST_INTENT_MSG->Handler调用BroadcastQueue#processNextBroadcastLocked

BroadcastQueue#processNextBroadcastLocked

1.其中processNextBroadcastLocked会先处理BroadcastQueue#mParallelBroadcasts,即先处理完可并行处理的Broadcast(使用两层循环处理mParallelBroadcasts中的BroadcastRecord和BroadcastRecord中的BroadcastFilter,而处理串行广播时是每次处理一个BroadcastFilter或ResolveInfo就得退出一次processNextBroadcastLocked,等待目标进程通知处理完了再调用processNextBroadcastLocked处理下一个Broadcast Filter或ResolveInfo),再处理串行的BroadcastRecord,在processNextBroadcastLocked中,处理RegisteredReceiver的deliverToRegisteredReceiverLocked,处理ResolveInfo的是processCurBroadcastLocked。

2.详细说一下BroadcastQueue#processNextBroadcastLocked处理串行广播的逻辑,首先从BroadcastQueue取到index为0的串行BroadcastRecord,然后根据BroadcastRecord#nextReceiver这个int类型成员确定目前该处理哪个ResolveInfor(注意:这里说的非ordered广播,所以该Record只有ResolveInfo,如果是ordered广播,串行BroadcastRecord中可同时含有BroadcastFilter,因为如果使用sendOrderedBroadcast的话,那么Registered Receiver也要串行处理),如果该BroadcastRecord要处理的是ResolveInfo,需要传递到目标进程的时广播intent和ActivityInfo,这个ActivityInfo就是静态Receiver的注册信息,在发送binder消息给目标目标进程后就要return退出processNextBroadcastLocked方法,即不再处理其他串行,等待该目标进程调用AMS#finishReceiver结束处理Receier,在等待期间即使有串行广播发过来,调用scheduleBroadcastsLocked也不会马上处理,但是如果有其他非串行广播放送过来,那么在mParallelBroadcasts中的BroadcastRecord还是会马上处理,而不会等串行广播结束;如果串行BroadcastRecord下一个要处理的是BroadcastFilter,那么依然是调用deliverToRegisteredReceiverLocked,只是ordered参数为true。在目标进程处理ordered的RegisteredReceiver结束后,需要调用AMS#finishReceiver,好让AMS处理下一个串行广播(严谨应该是说下一个ResolveInfo或者BroadcastFilter)。

sendOrderBroadcast

1.如果sendOrderBroadcast,那么在broadcastIntentLocked方法只会创建一个BroadcastRecord,其中也会同时查询匹配的RegisteredReceiver和ResolveInfo,把所有RegisteredReceiver和ResolveInfo放入BroadcastRecord#receivers:List<Object>,看这个类型就知道,取的时候需要判断类型得知是静态广播还是动态广播。然后将该BroadcastRecord放入BroadcastQueue#mOrderedBroadcasts中

 

 

 

 

有序广播处理动态广播时,在BroadcastQueue#processCurBroadcastLocked中:

 if (ordered) {
            r.receiver = filter.receiverList.receiver.asBinder();
            r.curFilter = filter;
            filter.receiverList.curBroadcast = r;
            r.state = BroadcastRecord.CALL_IN_RECEIVE;
    // The following are set when we are calling a receiver (one that
    // was found in our list of registered receivers).
    BroadcastFilter curFilter;

    // The following are set only when we are launching a receiver (one
    // that was found by querying the package manager).
    ProcessRecord curApp;       // hosting application of current receiver.
    ComponentName curComponent; // the receiver class that is currently running.
    ActivityInfo curReceiver;   // info about the receiver that is currently running.



 

 

处理静态广播,在BroadcastQueue#processCurBroadcastLocked中:

        r.state = BroadcastRecord.APP_RECEIVE;
        r.curComponent = component;
        r.curReceiver = info.activityInfo;

 

一个Queue处理有序广播时,如果这个Queue有其他有序BroadcastRecord添加进来,并调用BroadcastQueue#scheduleBroadcastLocked,怎么知道是否能处理有序广播,就靠上面添加的一些标记了。

 

 

sticky广播注册:

如果这个注册的Intent跟之前的一样,那么这个会替换之前的,这个是很有必要的,像电量变化广播,会不断发出,如果不替换的话,那么动态注册一个接收电量变化的Receiver,将会收到大量的sticky广播。而这个Intent的相等时定义在Intent#filterEquals中。下面是在AMS#sendBroadcast中,替换重复的sticky intent的代码片段:

    ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);
    if (stickies == null) {
        stickies = new ArrayMap<>();
        mStickyBroadcasts.put(userId, stickies);
    }
    ArrayList<Intent> list = stickies.get(intent.getAction());
    if (list == null) {
        list = new ArrayList<>();
        stickies.put(intent.getAction(), list);
    }
    final int stickiesCount = list.size();
    int i;
    for (i = 0; i < stickiesCount; i++) {
        if (intent.filterEquals(list.get(i))) {
            //替换已存在的sticky intent
            list.set(i, new Intent(intent));
            break;
        }
    }
    //新的intent追加到list
    if (i >= stickiesCount) {
        list.add(new Intent(intent));
    }

 

Intent#filterEquals

    /**
     * Determine if two intents are the same for the purposes of intent
     * resolution (filtering). That is, if their action, data, type,
     * class, and categories are the same.  This does <em>not</em> compare
     * any extra data included in the intents.
     *
     * @param other The other Intent to compare against.
     *
     * @return Returns true if action, data, type, class, and categories
     *         are the same.
     */
    public boolean filterEquals(Intent other) {
        if (other == null) {
            return false;
        }
        if (!Objects.equals(this.mAction, other.mAction)) return false;
        if (!Objects.equals(this.mData, other.mData)) return false;
        if (!Objects.equals(this.mType, other.mType)) return false;
        if (!Objects.equals(this.mPackage, other.mPackage)) return false;
        if (!Objects.equals(this.mComponent, other.mComponent)) return false;
        if (!Objects.equals(this.mCategories, other.mCategories)) return false;

        return true;
    }

Sticky广播,在receiver动态注册时会在AMS#registerReceiver中派发:

            // Enqueue broadcasts for all existing stickies that match
            // this filter.
            if (allSticky != null) {
                ArrayList receivers = new ArrayList();
                receivers.add(bf);

                final int stickyCount = allSticky.size();
                for (int i = 0; i < stickyCount; i++) {
                    Intent intent = allSticky.get(i);
                    BroadcastQueue queue = broadcastQueueForIntent(intent);
                    BroadcastRecord r = new BroadcastRecord(queue, intent, null,
                            null, -1, -1, false, null, null, OP_NONE, null, receivers,
                            null, 0, null, null, false, true, true, -1);
                    queue.enqueueParallelBroadcastLocked(r);
                    queue.scheduleBroadcastsLocked();
                }
            }

----------------------------------------------------------------------------------------------------------------------------------------------------

以下内容转自:https://juejin.im/post/5c13a61c6fb9a049f746167e

Broadcast之前/后台广播队列

文中的源代码版本为api23

ActivityManagerService中有两个广播队列:mFgBroadcastQueuemBgBroadcastQueue。它们都是BroadcastQueue的实例,只不过初始化时的入参有些不一样。

1. 区别


//ActivityManagerService.java
static final int BROADCAST_FG_TIMEOUT = 10 * 1000;
static final int BROADCAST_BG_TIMEOUT = 60 * 1000;

public ActivityManagerService(Context systemContext){
    //...
    mFgBroadcastQueue = new BroadcastQueue(this, 
                                    mHandler,
                                    "foreground",
                                    BROADCAST_FG_TIMEOUT,
                                    false);
    mBgBroadcastQueue = new BroadcastQueue(this,
                                    mHandler,
                                    "background",
                                    BROADCAST_BG_TIMEOUT,
                                    true);
    //...
}

//BroadcastQueue.java
BroadcastQueue(ActivityManagerService service,
            Handler handler,
            String name,//名称
            long timeoutPeriod,//超时时间
            boolean allowDelayBehindServices) {//是否允许等待上一个广播接收者所在的进程的Service结束流程
    mService = service;
    mHandler = new BroadcastHandler(handler.getLooper());
    mQueueName = name;
    mTimeoutPeriod = timeoutPeriod;
    mDelayBehindServices = allowDelayBehindServices;
}

复制代码

可以看到前台广播与后台广播主要用三个方面的区别:名称、超时时间以及mDelayBehindServices的值。启动最主要的区别在于后面两个

  1. 超时时间 前台广播超时时间:10s 后台广播超时时间:60s 可以看到前/后台广播的超时时间差距非常大,一旦发生超时,就会进入broadcastTimeoutLocked方法,触发我们所熟知的ANR
  2. mDelayBehindServices 这个标志就比较有意思了,它对有序广播会有一定的影响。如果该变量为true,那么BroadcastQueue就可能陷入一种名为WAITING_SERIVCES的状态。相关代码在BroadcastQueue.finishReceiverLocked方法中(广播派发成功之后由接收者回调AMS时触发),

1.1 mDelayBehindServices字段

public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
            String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
    //...

    if (waitForServices && r.curComponent != null && r.queue.mDelayBehindServices
            && r.queue.mOrderedBroadcasts.size() > 0
            && r.queue.mOrderedBroadcasts.get(0) == r) {
        ActivityInfo nextReceiver;
        if (r.nextReceiver < r.receivers.size()) {
            Object obj = r.receivers.get(r.nextReceiver);
            nextReceiver = (obj instanceof ActivityInfo) ? (ActivityInfo)obj : null;
        } else {
            nextReceiver = null;
        }
        // Don't do this if the next receive is in the same process as the current one.
        // 当前接收者与下一个接收者必须不处于同一个进程
        // 才会进入该if语句内
        if (receiver == null || nextReceiver == null
                || receiver.applicationInfo.uid != nextReceiver.applicationInfo.uid
                || !receiver.processName.equals(nextReceiver.processName)) {
            //...
            //如果当前用户有正在启动的后台服务
            //且待启动服务已经超过可一个阈值(hasBackgroundServices内部有做判断)
            //则进入WAITING_SERVICES状态
            if (mService.mServices.hasBackgroundServices(r.userId)) {
                Slog.i(TAG, "Delay finish: " + r.curComponent.flattenToShortString());
                r.state = BroadcastRecord.WAITING_SERVICES;
                return false;
            }
        }
    }
    //...
    return state == BroadcastRecord.APP_RECEIVE
            || state == BroadcastRecord.CALL_DONE_RECEIVE;

复制代码

可以看到,如果当前的广播接收者和下一个广播接收者不处于同一个进程,且当前有正在启动的后台服务(用户维度)那么BroadcastQueue进入WAITING_SERVICES状态。 该状态对processNextBroadcast内对有序广播的处理以及广播超时方法broadcastTimeoutLocked都有一定的影响

  1. processNextBroadcast 之前我们分析过,在派发有序广播之前,BroadcastQueue首先会通过一个while循环找到下一个广播接收者。这个循环内部有一段代码:
if (r.state != BroadcastRecord.IDLE) {
    if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,
            "processNextBroadcast("
            + mQueueName + ") called when not idle (state="
            + r.state + ")");
    return;
}
复制代码

可见,当处于WAITING_SERVICES状态,有序广播的派发会被阻断。

  1. broadcastTimeoutLocked 广播超时的代码中有下面这个代码段:
BroadcastRecord br = mOrderedBroadcasts.get(0);
if (br.state == BroadcastRecord.WAITING_SERVICES) {
    //...
    br.curComponent = null;
    br.state = BroadcastRecord.IDLE;
    processNextBroadcast(false);
    return;
}
复制代码

可见,对于处于WAITING_SERVICES状态的广播超时,并不会触发ANR,而是默默地将状态重新设置为IDLE,然后主动调用processNextBroadcast方法处理下一个消息。

mDelayBehindServices字段体现了AMS的一个策略:如果当前用户正在启动的后台服务数量超过阈值,那么会暂停后台广播队列中的有序广播的派发。

2. 广播队列选择策略

针对不同的IntentAMS会选用不同的广播队列,相关的逻辑在broadcastQueueForIntent方法中

//ActivityManagerService.java
BroadcastQueue broadcastQueueForIntent(Intent intent) {
    final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
    //...
    return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue;
}
复制代码

可以看到逻辑非常简单,就是判断了一下是否包含了FLAG_RECEIVER_FOREGROUND标志,如果是,则使用前台广播队列,否则使用后台广播队列。 参考源码中对于FLAG_RECEIVER_FOREGROUND的注解

/**
 * If set, when sending a broadcast the recipient is allowed to run at
 * foreground priority, with a shorter timeout interval.  During normal
 * broadcasts the receivers are not automatically hoisted out of the
 * background priority class.
 */
public static final int FLAG_RECEIVER_FOREGROUND = 0x10000000;
复制代码

可以得到两个关键点

  1. 接收者将被允许以前台优先级运行
  2. 更短的时间间隔(也即是前面讲到的超时时间)

Intent默认情况下是不带FLAG_RECEIVER_FOREGROUND标志的,因此通常我们的广播都是在后台广播队列中进行传播的。

3. 总结

通过上面对于源码的分析,我们知道了前台广播队列与后台广播队列的区别

  1. 前台广播队列的超时时间比后台广播队列短很多
  2. 后台广播队列在当前用户后台启动服务超过阈值时会暂停广播
  3. 前台广播允许接收者以前台优先级运行

以及如何在发送广播的时候指定广播传输的队列:通过设置FLAG_RECEIVER_FOREGROUND标志即可。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值