BroadcastReceiver

注册方式

1.动态注册

IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
registerReceiver(new ScreenBroadcastReceiver(), filter);

另外,注册广播可以通过IntentFilter的 setPriority设置广播的优先级。
如:

IntentFilter intentFilter = new IntentFilter(ACTION);

intentFilter.setPriority(1000);
registerReceiver(firstReceiver, intentFilter);

2.静态注册

<receiver android:name=".ScreenBroadcastReceiver">
    <intent-filter>
        <action android:name="android.intent.action.SCREEN_OFF" />
        <action android:name="android.intent.action.USER_PRESENT" />
    </intent-filter>
</receiver>

也可以通过 android:priority来设置广播的优先级

<receiver android:name=".receiver.FirstReceivcer">
    <intent-filter android:priority="1000">
        <action android:name="com.jack.app.myapplication.action.ORDER_BROADCAST"></action>
    </intent-filter>
</receiver>

使用

public class ScreenBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        action = intent.getAction();
        if (Intent.ACTION_SCREEN_OFF.equals(action)) {              // 锁屏
            BaseActivity.ISSCREENOFF = true;
        } else if (Intent.ACTION_USER_PRESENT.equals(action)) { // 解锁
            BaseActivity.ISSCREENOFF = false;
        }
    }
}

无序广播与有序广播

  • 普通广播:异步,发出时可被所有接受者收到,不可以取消
  • 有序广播:根据优先级依次传播,直到有接受者将其终止或所有接受者都不终止它。可以通过abortBroadcast取消它的继续传播(责任链设计模式)

有序广播接收,传值示例

注册略,请看上面的两者注册方式,FirstReceivcer 的优先级是1000,SecondReceiver的优先级是100.

public class FirstReceivcer extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        int limit = intent.getIntExtra("limit",0);
        if (limit == 1000){
            String msg = intent.getStringExtra("msg");
            Toast.makeText(context,msg,Toast.LENGTH_SHORT).show();
            abortBroadcast();
        } else{
            Bundle b = new Bundle();
            b.putString("new","message from firstReceiver");
            setResultExtras(b);
        }
    }
}

public class SecondReceivcer extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        int limit = intent.getIntExtra("limit", 0);
        if (limit == 100) {
            String msg = intent.getStringExtra("msg");

            /**
             * 取得上一个Receiver增加的信息
             */
            Bundle resultExtras = getResultExtras(true);
            String aNew = resultExtras.getString("new");

            Toast.makeText(context, "get message from first receiver:" + aNew, Toast.LENGTH_SHORT).show();
            abortBroadcast();
        } else {
            Bundle b = new Bundle();
            b.putString("new", "message from secondReceiver");
            setResultExtras(b);
        }
    }
}

在BroadcastReceiver的intent传递过程中

  1. 发送有序广播 sendOrderedBroadcast
Intent i = new Intent();
i.setAction(ACTION);
if (id == R.id.action_menu0){
    i.putExtra("limit", 100);
    i.putExtra("msg","有序广播");
    sendOrderedBroadcast(i, null);
}else if (id == R.id.action_menu1){
    i.putExtra("limit", 100);
    i.putExtra("msg","普通广播");
    sendBroadcast(i);
}
  1. 可以通过abortBroadcast取消广播的继续传递
  2. 在有序广播中,可以通过
Bundle b = new Bundle();
b.putString("new","message from firstReceiver");
setResultExtras(b);

来设置加入对广播传递信息的修改。可以通过getResultExtras取得上一个Receiver增加的信息。

/**
 * 取得上一个Receiver增加的信息
 */
Bundle resultExtras = getResultExtras(true);
String aNew = resultExtras.getString("new");

BroadcastReceiver 工作过程

静态注册的广播在应用安装的时候由系统自动完成注册,具体是由 PackageManagerService 来完成整个注册过程。这里只分析广播的动态注册的流程。

注册过程

  1. 通过ContextWrapper
@Override
public Intent registerReceiver(
    BroadcastReceiver receiver, IntentFilter filter) {
    return mBase.registerReceiver(receiver, filter);
}

mBase 是一个ContextImpl对象。

  1. ContextImpl的registerReceiver方法
    registerReceiver 又会去调用自己的 registerReceiverInternal ,ReceiverDispatcher是 LoadedApk的静态内部类。
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
        IntentFilter filter, String broadcastPermission,
        Handler scheduler, Context context) {
    /**
     * IIntentReceiver 一个 Binder 对象,因为注册过程是一个跨进程通信的过程
     */
    IIntentReceiver rd = null;
    if (receiver != null) {
        if (mPackageInfo != null && context != null) {
            if (scheduler == null) {
                scheduler = mMainThread.getHandler();
            }
            rd = mPackageInfo.getReceiverDispatcher(
                receiver, context, scheduler,
                mMainThread.getInstrumentation(), true);
        } else {
            if (scheduler == null) {
                scheduler = mMainThread.getHandler();
            }
            rd = new LoadedApk.ReceiverDispatcher(
                    receiver, context, scheduler, null, true).getIIntentReceiver();
        }
    }
    try {
        return ActivityManagerNative.getDefault().registerReceiver(
                mMainThread.getApplicationThread(), mBasePackageName,
                rd, filter, broadcastPermission, userId);
    } catch (RemoteException e) {
        return null;
    }
}

ActivityManagerNative.getDefault()就是AMS。
这里关注下 ReceiverDispatcher 的 getReceiverDispatcher 实现,其先创建了 ReceiverDispatcher对象rd,再通过
rd.getIIntentReceiver();
返回 IIntentReceiver 对象,如下:

public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
        Context context, Handler handler,
        Instrumentation instrumentation, boolean registered) {
    synchronized (mReceivers) {
        LoadedApk.ReceiverDispatcher rd = null;
        ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;
        if (registered) {
            map = mReceivers.get(context);
            if (map != null) {
                rd = map.get(r);
            }
        }
        if (rd == null) {
            rd = new ReceiverDispatcher(r, context, handler,
                    instrumentation, registered);
            if (registered) {
                if (map == null) {
                    map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
                    mReceivers.put(context, map);
                }
                map.put(r, rd);
            }
        } else {
            rd.validate(context, handler);
        }
        rd.mForgotten = false;
        return rd.getIIntentReceiver();
    }
}
  1. 由于注册广播真正的过程是在AMS中,我们看看AMS的 registerReceiver。
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {
            ...
    rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                userId, receiver);
    mRegisteredReceivers.put(receiver.asBinder(), rl);
    
    BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
            permission, callingUid, userId);
    rl.add(bf);
    if (!bf.debugCheck()) {
        Slog.w(TAG, "==> For Dynamic broadcast");
    }
    mReceiverResolver.addFilter(bf);
    ...
            
}

最终会把远程的 InnerReceiver 对象以及 IntentFilter 对象存储起来,整个广播的注册就完成了。

广播的发送和接收过程

这里只分析普通广播的发送过程。广播的发送和接受,其本质是一个过程的两个阶段。我们从广播的发送说起:
Context 的 sendBroadcast 是一个抽象方法。和注册的过程一样,ContextWrapper 的sendBroadcast同样将事情交给了 ContextImpl来处理。

1. ContextImpl 的 sendBroadcast 过程如下:

@Override
public void sendBroadcast(Intent intent) {
    warnIfCallingFromSystemProcess();
    String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
    try {
        intent.prepareToLeaveProcess();
        ActivityManagerNative.getDefault().broadcastIntent(
                mMainThread.getApplicationThread(), intent, resolvedType, null,
                Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
                getUserId());
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
}

ContextImpl 几乎什么事都没有干,直接向 AMS 发起了一个异步请求用于发送广播。

2. AMS 的 broadcastIntent 源码如下:

public final int broadcastIntent(IApplicationThread caller,
        Intent intent, String resolvedType, IIntentReceiver resultTo,
        int resultCode, String resultData, Bundle resultExtras,
        String[] requiredPermissions, int appOp, Bundle options,
        boolean serialized, boolean sticky, int userId) {
    enforceNotIsolatedCaller("broadcastIntent");
    synchronized(this) {
        intent = verifyBroadcastLocked(intent);

        final ProcessRecord callerApp = getRecordForAppLocked(caller);
        final int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();
        final long origId = Binder.clearCallingIdentity();
        int res = broadcastIntentLocked(callerApp,
                callerApp != null ? callerApp.info.packageName : null,
                intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
                requiredPermissions, appOp, null, serialized, sticky,
                callingPid, callingUid, userId);
        Binder.restoreCallingIdentity(origId);
        return res;
    }
}

broadcastIntent 调用了 broadcastIntentLocked 。broadcastIntentLocked 代码比较多,看起来比较复杂。

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

从 Android3.1开始广播就具有这种特性-默认情况下广播不会发送给已经停止的应用,这样做是为了防止广播无意间或者在不必要的时候调起已经停止运行的应用。在Android3.1中为Intent添加了两个标记位,分别为

1.FLAG_EXCLUDE_STOPPED_PACKAGES
表示不包含已经停止的应用,这个时候广播不会发送给已经停止的应用
2.FLAG_INCLUDE_STOPPED_PACKAGES
表示包含已经停止的应用,这个时候广播会发送给已经停止的应用

如果确实需要调起未启动的应用,那么只需要为广播的 Intent 添加 FLAG_INCLUDE_STOPPED_PACKAGES 标记即可。当 FLAG_EXCLUDE_STOPPED_PACKAGES 和 FLAG_INCLUDE_STOPPED_PACKAGES 两种标记并存时,以 FLAG_INCLUDE_STOPPED_PACKAGES 为准。

在 broadcastIntentLocked 内部,会根据 intent-filter 查找出匹配的广播接受者并经过一系列条件过滤。最终会把满足条件的广播接受者添加到 BroadcastQueue 中。

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, resolvedType, requiredPermission,
            appOp, registeredReceivers, resultTo, resultCode, resultData, map,
            ordered, sticky, false, userId);
    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;
}

3. 下面看下 BroadcastQueue 中广播的发送过程的实现

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

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

scheduleBroadcastsLocked并没有立即执行发送广播,而是发送了一个BROADCAST_INTENT_MSG类型的消息,BroadcastQueue 收到消息后会调用 BroadcastQueue 的 processNextBroadcast 方法,processNextBroadcast 方法对普通广播的处理如下:

// First, deliver any non-serialized broadcasts right away.
while (mParallelBroadcasts.size() > 0) {
    r = mParallelBroadcasts.remove(0);
    r.dispatchTime = SystemClock.uptimeMillis();
    r.dispatchClockTime = System.currentTimeMillis();
    final int N = r.receivers.size();
    if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing parallel broadcast ["
            + mQueueName + "] " + r);
    for (int i=0; i<N; i++) {
        Object target = r.receivers.get(i);
        if (DEBUG_BROADCAST)  Slog.v(TAG,
                "Delivering non-ordered on [" + mQueueName + "] to registered "
                + target + ": " + r);
        deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false);
    }
    addBroadcastToHistoryLocked(r);
    if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast ["
            + mQueueName + "] " + r);
}

无序广播存储在 mParallelBroadcasts 中,系统会遍历 mParallelBroadcasts 并将其中的广播发送给他们所有的接受者。具体通过 deliverToRegisteredReceiverLocked 实现,deliverToRegisteredReceiverLocked 负责讲一个广播发送给一个特定的接受者,它内部调用了 performReceiveLocked 来完成具体的发送过程。

 performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
    new Intent(r.intent), r.resultCode, r.resultData,
    r.resultExtras, r.ordered, r.initialSticky, r.userId);

performReceiveLocked 实现如下

private static void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
        Intent intent, int resultCode, String data, Bundle extras,
        boolean ordered, boolean sticky, int sendingUser) throws RemoteException {
    // Send the intent to the receiver asynchronously using one-way binder calls.
    if (app != null) {
        if (app.thread != null) {
            // If we have an app thread, do the call through that so it is
            // correctly ordered with other one-way calls.
            /**
             * app.thread 仍然指 ApplicatonThread
             */
            app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
                    data, extras, ordered, sticky, sendingUser, app.repProcState);
        } else {
            // Application has died. Receiver doesn't exist.
            throw new RemoteException("app.thread must not be null");
        }
    } else {
        receiver.performReceive(intent, resultCode, data, extras, ordered,
                sticky, sendingUser);
    }
}

4. 在 ApplicationThread 的 scheduleRegisteredReceiver 中,通过 InnerReceiver 来实现广播的接收。

public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
        int resultCode, String dataStr, Bundle extras, boolean ordered,
        boolean sticky, int sendingUser, int processState) throws RemoteException {
    updateProcessState(processState, false);
    receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
            sticky, sendingUser);
}

通过 InnerReceiver 的performReceive方法会调用 LoadedApk.ReceiverDispatcher 的performReceive。方法如下:

public void performReceive(Intent intent, int resultCode, String data,
            Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
        if (ActivityThread.DEBUG_BROADCAST) {
            int seq = intent.getIntExtra("seq", -1);
            Slog.i(ActivityThread.TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq
                    + " to " + mReceiver);
        }
        Args args = new Args(intent, resultCode, data, extras, ordered,
                sticky, sendingUser);
        if (!mActivityThread.post(args)) {
            if (mRegistered && ordered) {
                IActivityManager mgr = ActivityManagerNative.getDefault();
                if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                        "Finishing sync broadcast to " + mReceiver);
                args.sendFinished(mgr);
            }
        }
    }

}

在上面的代码中,会创建一个 Args 对象,并通过 mActivityThread 的post方法执行Args中的逻辑,而 Args实现了 Runnable 接口。mActivityThread 是一个Handler,它其实就是 ActivityThread 中的 mH,mH 的类型是 ActivityThread的内部类H.在Args的run方法中有如下代码:

final BroadcastReceiver receiver = mReceiver;
receiver.setPendingResult(this);
receiver.onReceive(mContext, intent);

很显然,这个时候 BroadcastReceiver 的 onReceiver 执行了。Ok!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容