四大组件之BroadcastReceiver源码解析

BroadcastReceiver

一 BroadcastReceiver 简介

BroadcastReceiver 是一种消息型组件,称之为广播,用于在不同的组件乃至不同的应用之间传递消息。
广播的注册分为两种方式:

  • 静态注册
    静态注册是指在 AndroidManiifest中注册广播,这种广播在应用安装时会被系统解析、不需要应用启动就可以收到相应的广播.
  • 动态注册。
    动态注册广播需要通过Context.registrerReceiver() 来实现,并且在不需要的时候要通过Context.unregistrerReceiver() 来解除广播,此种形态的广播必须要应用启动才能注册并接收广播。

广播收发器组件可以用来实现低耦合的观察者模式,观察者和被观察者之间可以没有任何耦合,由于BroadcastReceiver的特性,它不适合用来执行耗时操作.BroadcastReceiver一般来说不需要停止,它也没有停止的概念。

二 BroadcastReceiver 的使用

我们回顾一下广播的使用方法,首先要册
定义广播接收者,只需要继承 BroadcastReceiver并重写 on Receive方法即可,下面是一个典型的广播接收者的实现:

public class MyReceiver extends BroadcastReceiver {
   @Override
   public void onReceive(Context context, Intent intent) {
   // onReceive函数不能做耗时的事情,参考值:10s以内
      String action= intent. getAction ();
   // do something
   }
}

定义好了广播接收者,接着还需要注册广播接收者,注册分为两种方式,既可以在AndroidManifest文件中静态注册,也可以通过代码动态注册。

静态注册的示例如下

        <receiver android:name=".MyReceiver">
            <intent-filter>
                <action android:name="com.lsh.receiver.LAUNCH"/>
            </intent-filter>
        </receiver>

通过代码来动态注册广播也是很简单的,如下所示。需要注意的是,动态注册
需要在合适的时机进行解注册,解注册采用 unregisterReceiver方法。

IntentFilter filter = new IntentFilter();       
filter.addAction("com.lsh.receiver.LAUNCH");
registerReceiver(new MyReceiver(), filter);

前面两步都完成了以后,就可以通过send方法来发送广播了,如下所示。

Intent intent= new Intent()
intent.setAction("com.lsh.receiver.LAUNCH");
sendBroadcast(intent);

三 广播的注册过程

广播的注册分为静态注册和动态注册,其中静态注册的广播在应用安装时由系统自动完成注册,具体来说是由PMS( PackageManager Service来完成整个注册过程的,除了广播以外,其他三大组件也都是在应用安装时由PMS解析并注册的。这里只分析广播的动态注册的过程,动态注册的过程是从 ContextWrapper 的 registerReceiver方法开始的,和 Activity以及 Service一样。 ContextWrapper并没有做实际的工作,而是将注册过程直接交给了Contextlmpl(这个文件是保护文件,就是注解了是内部保护文件,所以无法找到,可以去SDk的安装目录中的sources文件夹中直接这个Java文件)完成,如下所

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

ContextImpl的 registerReceiver方法调用了自己的 registerReceiverInternal方法,它的实现如下所示:

 private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
            IntentFilter filter, String broadcastPermission,
            Handler scheduler, Context context, int flags) {
        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 {
            final Intent intent = ActivityManager.getService().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
                    broadcastPermission, userId, flags);
            if (intent != null) {
                intent.setExtrasClassLoader(getClassLoader());
                intent.prepareToEnterProcess();
            }
            return intent;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

在上面的代码中,系统首先从 mPackagelnfo获取 IIntentReceiver对象,然后再采用跨进程的方式向AMS发送广播注册的请求。之所以采用 IIntentReceiver而不是直接采用BroadcastReceiver,这是因为上述注册过程是一个进程间通信的过程,而 BroadcastReceiver作为 Android的一个组件是不能直接跨进程传递的,所以需要通过 IIntentReceiver来中转下。毫无疑问, IIntentReceiver必须是一个 Binder接口,它的具体实现是 LoadedApk Receiver Dispatcher. InnerReceiver, Receiver Dispatcher的内部同时保存了 BroadcastReceiverInner receiver,这样当接收到广播时, ReceiverDispatcher可以很方便地调用BroadcastReceiveron Receive方法。可以发现, BroadcastReceiver的这个过程和 Service的实现原理类似;

下面看一下 ReceiverDispatchergetlIntentReceiver的实现, 如下所示。很显然, getReceiverDispatcher方法重新创建了一个 Receiver Dispatcher对象并,将其保存的 InnerReceiver对象作为返回值返回,其中 Inner Receiver对象和 BroadcastReceiver都是在 ReceiverDispatcher的构造方法中被保存起来的。

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

注册广播的真正实现是在 AMS 中,AMS的 registerReceiver 中,最终会把远程的 InnerReceiver 对象以及 IntentFilter 对象存储起来,这种整个广播的注册过程就完成了,如下代码:

 public Intent reiisterReceiver(IApplicationThread caller, String callerPackage, 
                                   IIntentReceiver receiver, IntentFilter filter, String permission,
                                   int userId) {
        ...
        mRegisterReceivers.put(receiver.asBinder(), rl);      
        BroadcastFilter broadcastFilter = new BroadcastFilter(filter, rl, callerPackage, permission, 
                callingUid, userId);
        rl.add(bf);
        mRegisterResolver.addFilter(bf);
        ...
    }

四 广播的发送和接受过程

广播的发送和接收,其本质是一个过程的两个阶段。这里从广播的发送可以说起,广播的发送仍然开始于 Context WrappersendBroadcast方法,之所以不是 Context,那是因为 Contextsend Broadcast是一个抽象方法。和广播的注册过程一样, Context Wrappersend Broadcast方法仍然什么都不做,只是把事情交给 ContextImpl去处理:

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

从上边看 ContextImpl 直接向 AMS 发起了一个异步请求用于发送广播。

 public final int broadcastIntent(IApplicationThread caller,
            Intent intent, String resolvedType, IIntentReceiver resultTo,
            int resultCode, String resultData, Bundle resultExtras,
            String[] requiredPermissions, int appOp, Bundle bOptions,
            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, bOptions, serialized, sticky,
                    callingPid, callingUid, userId);
            Binder.restoreCallingIdentity(origId);
            return res;
        }
    }

broadcastIntent 调用了 broadcastIntentLocked 方法,AMS 的broadcastIntentLocked,在此方法中加了了

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

表示在 Android5.1 中 默认情况下广播不会发送给已停止的应用了。

在BroadcastIntentLockes的内部,会根据意图intent-Fl1ter查找出匹配的广播接收者并 经过一系列的条件过滤,最终会将满足条件的广播接收者添加到Broadcast Queue中,接着Broadcast Queue就会将广播发送给相应的广播接收者:

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

            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r
                    + ": prev had " + queue.mOrderedBroadcasts.size());
            if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST,
                    "Enqueueing broadcast " + r.intent.getAction());

            final BroadcastRecord oldRecord =
                    replacePending ? queue.replaceOrderedBroadcastLocked(r) : null;
            if (oldRecord != null) {
                // Replaced, fire the result-to receiver.
                if (oldRecord.resultTo != null) {
                    final BroadcastQueue oldQueue = broadcastQueueForIntent(oldRecord.intent);
                    try {
                        oldQueue.performReceiveLocked(oldRecord.callerApp, oldRecord.resultTo,
                                oldRecord.intent,
                                Activity.RESULT_CANCELED, null, null,
                                false, false, oldRecord.userId);
                    } catch (RemoteException e) {
                        Slog.w(TAG, "Failure ["
                                + queue.mQueueName + "] sending broadcast result of "
                                + intent, e);

                    }
                }
            } else {
                queue.enqueueOrderedBroadcastLocked(r);
                queue.scheduleBroadcastsLocked();
            }
        }

下边看一下 BroadcastQueue 中广播的发送过程实现,如下

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

BroadcastQueue的scheduleBroadcastsLocked 方法并没有立即发送广播,而是发送了一个 BROADCAST_INTENT_MSG ,BroadcastQueue收到消息后会调用 process-NextBroadcast 方法,BroadcastQueue 的 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();

                if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                    Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
                        System.identityHashCode(r));
                    Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
                        System.identityHashCode(r));
                }

                final int N = r.receivers.size();
                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
                        + mQueueName + "] " + r);
                for (int i=0; i<N; i++) {
                    Object target = r.receivers.get(i);
                    if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                            "Delivering non-ordered on [" + mQueueName + "] to registered "
                            + target + ": " + r);
                    deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i);
                }
                addBroadcastToHistoryLocked(r);
                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast ["
                        + mQueueName + "] " + r);
            }

可以看到,无序广播存储在mParallelBroadcasts中,系统会遍历mParallelBroadcasts并将其中的广播发送给它们所有的接收者,具体的发送过程是通过delivetToRegisteredReceiveLoack 方法来实现的。delivetToRegisteredReceiveLoack方法负责将一个广播发送给一个特定的接收者,它内部调用了performReceiveLocked来完成具体的发送过程;

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

该方法实现如下

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.
                try {
                    app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
                            data, extras, ordered, sticky, sendingUser, app.repProcState);
                // TODO: Uncomment this when (b/28322359) is fixed and we aren't getting
                // DeadObjectException when the process isn't actually dead.
                //} catch (DeadObjectException ex) {
                // Failed to call into the process.  It's dying so just let it die and move on.
                //    throw ex;
                } catch (RemoteException ex) {
                    // Failed to call into the process. It's either dying or wedged. Kill it gently.
                    synchronized (mService) {
                        Slog.w(TAG, "Can't deliver broadcast to " + app.processName
                                + " (pid " + app.pid + "). Crashing it.");
                        app.scheduleCrash("can't deliver broadcast");
                    }
                    throw ex;
                }
            } 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);
        }
    }

ApplicationThreadscheduleRegisterReceiver 的实现比较简单,他是通过 InnerReceiver 来实现广播的接收。

 void scheduleRegisterReceiver(IIntentReceiver receiver, Intent intent,
                                  int resultCode, String dataStr, Bundle extras,
                                  boolean ordered, boolean sticky, int sendingUser,
                                  int processState) {
        updateProcessState(processState, false);
        receiver.performReceiver(intent, resultCode, dataStr, extras, ordered, sticky, sendingUser);
    }

InnerReceiver的 performReceiver方法会调用LoadApk.ReceiverDispatch的 perform-Receiver 方法

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

    }

创建了一个 Args 对象并通过 mAcvitityThread 的 post 方法来执行 Args 中的逻辑,
而args实现了Runnable接口.mActivityThread是一个Handler,它其实就是ActivityThread中的mh,mh的类型是ActivityThread的内部类Н,关于H这个类前面已经介绍过了,这里就不再多说了.在args的方法中有如下几行代码:


很显然,这个时候BroadcastReceiver的onReceiver方法被执行了,也就是说应用已经接收到广播了,同时 onReceiver 方法是在广播接收者的主线程中被调用的.到这里,整个广播的注册、发送和接收过程已经讲解完毕

参考资料:
《Android开发艺术探索》

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

推荐阅读更多精彩内容