从源码看广播之广播发送流程


前言

一直想梳理一下广播的注册和发送流程。有次面试我被问了许多关于广播的问题,很多问题都能从源码中找到答案,其中一些我也看过源码,但当时问的时候完全懵逼了。有些问题甚至没有深入考虑过,问的时候全靠猜。所以在此是做个梳理,也希望读者读完这篇文章,能帮助你解决以下问题。

1、onReceive执行在哪个线程

2、广播是如何发送的

3、进程没启动的时候接收静态广播是否会启动进程

PS:写关于源码的文章自己也比较头疼,不知道从何开始从何结束,源码复杂不说很多内容还交叉在一起。过于深入自己又感觉得不偿失,毕竟不是做framework的。所以对文章有任何意见或者建议,欢迎留言。


广播是如何发送和接收的

广播的发送主要分为两步。第一步是收集匹配的广播接收器,第二步就是给匹配的接收器发送广播。
我们从activity#sendBroadcast来说说整个广播的发送流程。首先需要明白,调用sendBroadcast的时候,请求都被转发到了ContextImpl中。ContextWrapper其实是ContextImpl的包装类,这是一个装饰者设计模式。这里尽量不大范围贴源码了,我画了个类图。

Context类图.png

我们直接从ContextImpl开始看起,这里其实也只是把请求交给了ActivityManagerProxybroadcastIntent方法就是通过binder发起一个异步请求给AMS,而在AMS的broadcastIntent方法中又调用了broadcastIntentLocked方法。

ContextImpl.class

    @Override
    public void sendBroadcast(Intent intent) {
        warnIfCallingFromSystemProcess();
        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
        try {
            intent.prepareToLeaveProcess(this);
            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 e.rethrowFromSystemServer();
        }
    }

ActivityManagerProxy.class

    public int broadcastIntent(IApplicationThread caller,
            Intent intent, String resolvedType, IIntentReceiver resultTo,
            int resultCode, String resultData, Bundle map,
            String[] requiredPermissions, int appOp, Bundle options, boolean serialized,
            boolean sticky, int userId) throws RemoteException
    {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IActivityManager.descriptor);
        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
        intent.writeToParcel(data, 0);
        data.writeString(resolvedType);
        data.writeStrongBinder(resultTo != null ? resultTo.asBinder() : null);
        data.writeInt(resultCode);
        data.writeString(resultData);
        data.writeBundle(map);
        data.writeStringArray(requiredPermissions);
        data.writeInt(appOp);
        data.writeBundle(options);
        data.writeInt(serialized ? 1 : 0);
        data.writeInt(sticky ? 1 : 0);
        data.writeInt(userId);
        mRemote.transact(BROADCAST_INTENT_TRANSACTION, data, reply, 0);//通过binder调用broadcastIntent方法
        reply.readException();
        int res = reply.readInt();
        reply.recycle();
        data.recycle();
        return res;
    }

可以看到这里通过binder调用了AMS的broadcastIntent方法。里面调用了broadcastIntentLocked,其中代码非常多。包括权限检查和一些系统广播的处理,这里就关注静态广播、动态广播的查询和发送。

广播发送及动态广播接收器接收时序图.png

广播接收器的收集

Step1 静态广播接收器的查询

在该方法中调用了collectReceiverComponents查询和intent匹配的静态广播。

ActivityManagerService.class

private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType,
            int callingUid, int[] users) {
                //...
    List<ResolveInfo> newReceivers = AppGlobals.getPackageManager().queryIntentReceivers(intent, resolvedType, pmFlags, user).getList();
               //...
    return receivers;
}

可以看到,这里通过PMS的queryIntentReceivers方法查询和intent匹配的广播。这也就说明,静态广播是由PMS管理的

Step2 动态广播接收器的查询

动态广播通过queryIntent查询

ActivityManagerService.class

        if (intent.getComponent() == null) {
            if (userId == UserHandle.USER_ALL && callingUid == Process.SHELL_UID) {
                // Query one target user at a time, excluding shell-restricted users
                for (int i = 0; i < users.length; i++) {
                    if (mUserController.hasUserRestriction(
                            UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
                        continue;
                    }
                    List<BroadcastFilter> registeredReceiversForUser =
                            mReceiverResolver.queryIntent(intent,
                                    resolvedType, false, users[I]);
                    if (registeredReceivers == null) {
                        registeredReceivers = registeredReceiversForUser;
                    } else if (registeredReceiversForUser != null) {
                        registeredReceivers.addAll(registeredReceiversForUser);
                    }
                }
            } else {
                registeredReceivers = mReceiverResolver.queryIntent(intent,
                        resolvedType, false, userId);
            }
        }

广播的发送

Step1 发送广播给动态广播接收器

根据查询出的receivers构造一个BroadcastRecord,接着调用enqueueParallelBroadcastLocked将广播接收者添加到BroadcastQueue中。最后调用scheduleBroadcastsLocked,这个方法的逻辑主要是把广播发送到之前所收集的广播接收者当中。

ActivityManagerService.class

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();
    }

scheduleBroadcastsLocked中发送了一个BROADCAST_INTENT_MSG消息。

BroadcastQueue.class

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

BroadcastQueue收到消息会调用processNextBroadcast方法。在该方法中会先发送无序广播。看一下发送无序广播的逻辑。其他类型的广播发送大同小异。

BroadcastQueue.class

// 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();
    for (int i=0; i<N; i++) {
        Object target = r.receivers.get(i);
        deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i);
    }
    addBroadcastToHistoryLocked(r);
}

在其内部调用deliverToRegisteredReceiverLocked,该方法内部又通过performReceiveLocked把消息发送给特定的广播接收器。

BroadcastQueue.class

void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
            Intent intent, int resultCode, String data, Bundle extras,
            boolean ordered, boolean sticky, int sendingUser) throws RemoteException {
    if (app != null) {
        if (app.thread != null) {
            //调用scheduleRegisteredReceiver发送广播到接收器
            app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
                        data, extras, ordered, sticky,sendingUser,app.repProcState);
            //...
    } else {
        receiver.performReceive(intent, resultCode, data, extras, ordered,
                    sticky, sendingUser);
    }
}

这里的app.threadApplicationThread,一般都不为null。ApplicationThreadActivityThread的内部类。

ActivityThread.class

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);
}

IIntentReceiver对象的performReceive会调用LoadedApk.ReceiverDispatcherperformReceive方法。在其内部会构建一个Args对象,该类实现的了Runnable接口,接着通过mActivityThread执行args

LoadedApk.class#Args.class

public void run() {
    final BroadcastReceiver receiver = mReceiver;
    final boolean ordered = mOrdered;
                
    final IActivityManager mgr = ActivityManagerNative.getDefault();
    final Intent intent = mCurIntent;

    mCurIntent = null;
    mDispatched = true;
    try {
    ClassLoader cl =  mReceiver.getClass().getClassLoader();
    intent.setExtrasClassLoader(cl);
    intent.prepareToEnterProcess();
    setExtrasClassLoader(cl);
    receiver.setPendingResult(this);
    receiver.onReceive(mContext, intent);
    } catch (Exception e) {
    }
}

可以看到args对象的run方法最终调用了receiver.onReceive

所以onReceive方法在哪个线程执行?

在主线程。因为广播接收最后会通过mH回调到主线程去执行onReceive方法,所以onReceive是执行在主线程的。

Step2 发送广播给静态广播接收器

把广播发送到静态广播接收器的逻辑与发送到动态广播接收器的逻辑大同小异,都需要调用到processNextBroadcast中。需要注意区分两种情况,一种是进程已经启动,一种是进程尚未启动。

// Is this receiver's application already running?
if (app != null && app.thread != null) {
    //...
    processCurBroadcastLocked(r, app);
}
Situation1 进程已经启动

如果进程已经启动,就会调用processCurBroadcastLocked发送广播。其中又会调用processCurBroadcastLocked。其中又会调用app.thread.scheduleReceiver发送广播。app.thread是ActivityThread。

  app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
                    mService.compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo),
                    r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
                    app.repProcState);

ActivityThread.class

public final void scheduleReceiver(Intent intent, ActivityInfo info,
                CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
                boolean sync, int sendingUser, int processState) {
            updateProcessState(processState, false);
            ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
                    sync, false, mAppThread.asBinder(), sendingUser);
            r.info = info;
            r.compatInfo = compatInfo;
            sendMessage(H.RECEIVER, r);
        }

这个方法很简单,就是发了一个RECEIVER消息。所以静态广播的onReceive方法也会运行在主线程mH接收到这个消息会调用到handleReceiver,通过反射构建receiver实例。

Situation2 进程未启动

如果进程没有启动呢?接着看回processNextBroadcast,在该方法的最后面有这样一段逻辑,它主要的作用就是拉起app进程。如果应用已经启动,是不会执行到这段逻辑。startProcessLocked这个方法需要特别留意,这个方法就是用来拉起进程的。

if ((r.curApp=mService.startProcessLocked(targetProcess,        info.activityInfo.applicationInfo, true,
r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
"broadcast", r.curComponent,
(r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false))
 == null) {
    logBroadcastReceiverDiscardLocked(r);
    finishReceiverLocked(r, r.resultCode, r.resultData,
    r.resultExtras, r.resultAbort, false);
    scheduleBroadcastsLocked();
    r.state = BroadcastRecord.IDLE;
    return;
}

所以进程没启动的时候接收静态广播会启动进程,并且会调用Application的onCreate方法


总结

整个广播发送的流程大致如下
1.查询和Intent匹配的静态广播接收器
2.查询和intent匹配的动态广播接收器
3.发送广播给动态广播接收器
4.发送广播给静态广播接收器

最后通过开篇的三个问题总结一下这篇的内容

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

推荐阅读更多精彩内容

  • 2.1 Activity 2.1.1 Activity的生命周期全面分析 典型情况下的生命周期:在用户参与的情况下...
    AndroidMaster阅读 3,014评论 0 8
  • Android系统的广播机制是一种基于消息发布和订阅的事件驱动模型,即广播发送者负责发布消息,而接收者需要先订阅消...
    泡面先生_Jack阅读 1,146评论 0 2
  • 前言 本来想写一下广播的,发现查阅后有整理的不错的,只好转载图个简便,日后好复习转载:http://www.cnb...
    提升即效率阅读 1,384评论 0 10
  • 1、动态注册过程源码分析: 在Activity中动态注册广播室,在注册方法之前其实省略了Context,也就是实际...
    骑着猪的蜗牛阅读 707评论 0 1
  • 这是我对官方文档的一个渣翻译,兼我的学习笔记,原文在此。Android app可以从Android系统和其他And...
    renyjenny阅读 613评论 2 0