Android广播剖析之广播注册

前言:知其所以,知其所以然。

广播使用的三步骤:

  • 注册广播
  • 发送广播
  • 接收广播

本文主要谈一下《注册广播》,务必谈得深入,谈得彻底,解大家心中的一个闷。
下面是开发者执行registerReceiver(...) binder调用到system_server进程中AMS的调用过程。虽然核心的代码是在AMS中执行的,但是之前的步骤也是很重要的。下面按照执行步骤详细讲解一下每一步发生了什么情况?


Android广播剖析之广播注册_第1张图片
广播注册过程.jpg

1.ContextImpl -> registerReceiverInternal(...)

这是执行的第一个重要步骤。这个函数从代码来看分为上下两部分:

  • 获取一个IIntentReceiver binder对象
  • Binder调用AMS中registerReceiver(...)函数

1.1 获取一个IIntentReceiver binder对象

我们先看一下这个获取IIntentReceiver binder对象的代码:

ContextImpl.java

private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
            IntentFilter filter, String broadcastPermission,
            Handler scheduler, Context context, int flags) {
        IIntentReceiver rd = null;
        if (receiver != null) {
            if (mLoadedApk != null && context != null) {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = mLoadedApk.getReceiverDispatcher(
                    receiver, context, scheduler,
                    mMainThread.getInstrumentation(), true);
            } else {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = new LoadedApk.ReceiverDispatcher(
                        receiver, context, scheduler, null, true).getIIntentReceiver();
            }
        }
}

这段代码表示:如果当前的mLoadedApk和context不为空,那么直接从mLoadedApk对象中取对应的IIntentReceiver对象,如果条件不成立的话,需要重新new一个LoadedApk.ReceiverDispatcher对象,然后从中取出IIntentReceiver对象。
看到这里,我想提出三个疑问:

  • LoadedApk类中是做什么的?
  • IIntentReceiver是什么对象?
  • LoadApk初始化什么时候?
1.1.1 解析LoadedApk类
Android广播剖析之广播注册_第2张图片
LoadedApk类结构图.jpg

这是LoadedApk 核心类的图示。LoadedApk这个类就是保存当前加载好的apk的本地状态,给大家看一下LoadedApk中的基本属性:

   private final ActivityThread mActivityThread;
    final String mPackageName;
    private ApplicationInfo mApplicationInfo;
    private String mAppDir;
    private String mResDir;
    private String[] mOverlayDirs;
    private String mDataDir;
    private String mLibDir;
    private File mDataDirFile;
    private File mDeviceProtectedDataDirFile;
    private File mCredentialProtectedDataDirFile;
    private final ClassLoader mBaseClassLoader;
    private final boolean mSecurityViolation;
    private final boolean mIncludeCode;
    private final boolean mRegisterPackage;
    private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
    /** WARNING: This may change. Don't hold external references to it. */
    Resources mResources;
    private ClassLoader mClassLoader;
    private Application mApplication;

    private String[] mSplitNames;
    private String[] mSplitAppDirs;
    private String[] mSplitResDirs;
    private String[] mSplitClassLoaderNames;

    private final ArrayMap> mReceivers
        = new ArrayMap<>();
    private final ArrayMap> mUnregisteredReceivers
        = new ArrayMap<>();
    private final ArrayMap> mServices
        = new ArrayMap<>();
    private final ArrayMap> mUnboundServices
        = new ArrayMap<>();

上面都是当前加载的apk的基本属性:

  • apk的data目录,lib目录,或者其他的基本存储目录等等
  • apk相关的属性:包名、applicationInfo等等
  • apk包含的receiver和service,以及已经取消注册的receiver和解绑的service
  • 如果当前的apk有分包的话,还有apk的分包目录和分包加载的classloader名称,等等。

上面这些信息,就是向我们透露一件事情,就是LoadedApk中包含这加载的apk的基本属性。那么也就不难理解,我们的receiver肯定也需要在这里备份一份了。

  • ReceiverDispatcher:
    显然是处理当前apk中包含的receiver的,具体怎么处理,下面会讲解。
  • ServiceDispatcher:
    对应的,这是处理当前apk中包含的service的。将在service章节中涉及到这个,这儿不展开讲解了。
1.1.2 解析IIntentReceiver

从上面的LoadedApk类的结构图来看,我们知道InnerReceiver是继承IIntentReceiver.Stub,说明这是一个binder结构:

final static class InnerReceiver extends IIntentReceiver.Stub {
            final WeakReference mDispatcher;
            final LoadedApk.ReceiverDispatcher mStrongRef;
        @Override
        public void performReceive(Intent intent, int resultCode, String data,
                    Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
//......
        }
}

从上面的代码中看出了两点:

  • InnerReceiver中持有ReceiverDispatcher 应用,且有强引用和弱引用两种,但是在核心函数performReceive中用到是弱引用。这儿的调用等会讲解完AMS中的registerReceiver之后,自然会明白。
  • 我们之前在传入AMS的registerReceiver时,传入了就是InnerReceiver的实例,很显然,之后在AMS中肯定会回调到performReceiver,现在看起来是不是有点理解了。
1.1.3 LoadedApk 初始化

上面也有说道一个条件: mLoadedApk != null && context != null ,这个mLoadedApk就是LoadedApk的实例,上面我们说了LoadedApk中存放着当前加载的apk的基本信息,既然是基本信息,那么这个类十分重要,那他加载的时候肯定很靠前。
下面简单叙述一下mLoadedApk的过程:

  • ActivityThread -> attach(...) ,非系统进程情况下,执行:
    context.mLoadedApk.makeApplication(...),这儿的mLoadedApk是系统进程的,不是我们当前的应用进程的。
  • LoadedApk -> makeApplication(...) ,执行的函数是ContextImpl.makeApplication(mActivityThread, this),这儿传入的this就是当前的LoadedApk的实例。
  • 传入到ContextImpl中,就让当前的应用进程中存在了一个,mLoadedApk实例了。
1.1.4 LoadedApk.ReceiverDispatcher构造函数

介介绍完背景知识,我们开始从上面的调用开始,了解一下调用的整个过程到底干了什么?
当前的 mLoadedApk != null && context != null 如果不满足的话,说明当前的mLoadedApk为null,也就是还没有初始化,这儿执行分为两步:

  • 执行LoadedApk.ReceiverDispatcher构造函数
  • 从上面构建出来的对象中调用getIIntentReceiver()获取IIntentReceiver.Stub binder对象。
1.1.4.1 执行LoadedApk.ReceiverDispatcher构造函数

构造函数的代码如下:

ReceiverDispatcher(BroadcastReceiver receiver, Context context,
                Handler activityThread, Instrumentation instrumentation,
                boolean registered) {
            if (activityThread == null) {
                throw new NullPointerException("Handler must not be null");
            }

            mIIntentReceiver = new InnerReceiver(this, !registered);
            mReceiver = receiver;
            mContext = context;
            mActivityThread = activityThread;
            mInstrumentation = instrumentation;
            mRegistered = registered;
            mLocation = new IntentReceiverLeaked(null);
            mLocation.fillInStackTrace();
        }

构造函数中传入的参数有5个:

  • receiver:
    就是当前需要注册的receiver,是我们定义的。
  • context:
    这个context是getOuterContext(),这个就是执行setOuterContext(context)时传入的context,执行这个函数的地方在ActivityThread->performLaunchActivity(...)时执行,就是当前的activity的context
  • activityThread:
    这是一个Handler对象,从赋值来看:
    mMainHandler.getHandler() ,是当前activity的主线的handler
  • instrumentation:
    当前传入的是null
  • registered:
    注册的时候传入的参数是true,如果反注册的时候传入的参数是false

除了传入上面的几个参数,其他的都是赋值操作,其中还有一个构造方法:

mIIntentReceiver = new InnerReceiver(this, !registered);

这个InnerReceiver是ReceiverDispatcher的内部类,这个内部类继承了IIntentReceiver.Stub ,本质上是一个binder对象。

1.1.4.2 获取IIntentReceiver.Stub binder对象

上面说过了在执行LoadedApk.ReceiverDispatcher构造函数的时候构造了IIntentReceiver.Stub对象,就是mIIntentReceiver实例,然后赋值。

1.1.5 获取IIntentReceiver对象

之前 《1.1.4 LoadedApk.ReceiverDispatcher构造函数》谈得前提条件是
mLoadedApk != null && context != null 不成立,如果这个条件成立了,那么当前的IIntentReceiver对象要从什么地方取?

rd = mLoadedApk.getReceiverDispatcher(
                    receiver, context, scheduler,
                    mMainThread.getInstrumentation(), true);

当前的mLoadedApk不为空,通过getReceiverDispatcher获取想要的对象。

public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
            Context context, Handler handler,
            Instrumentation instrumentation, boolean registered) {
        synchronized (mReceivers) {
            LoadedApk.ReceiverDispatcher rd = null;
            ArrayMap 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();
                        mReceivers.put(context, map);
                    }
                    map.put(r, rd);
                }
            } else {
                rd.validate(context, handler);
            }
            rd.mForgotten = false;
            return rd.getIIntentReceiver();
        }
    }

mReceivers就是

private final ArrayMap> mReceivers
        = new ArrayMap<>();

ArrayMap 的key就是当前的context,上面也介绍了一下context的来历,value就是我们定义的一个ArrayMap,也是一个ArrayMap,这个ArrayMap的key是当前注册的receiver,value是我们想要的LoadedApk.ReceiverDispatcher对象,这里实际上将所有的注册的receiver和LoadedApk.ReceiverDispatcher对象都放在一个全局的ArrayMap中,在实际使用的时候会从ArrayMap中找一下,如果存在直接复用就可以的,如果不存在,那再重新创建一个新的LoadedApk.ReceiverDispatcher对象。
值得注意的是:rd.validate(context, handler);到底想说什么?

void validate(Context context, Handler activityThread) {
            if (mContext != context) {
                throw new IllegalStateException(
                    "Receiver " + mReceiver +
                    " registered with differing Context (was " +
                    mContext + " now " + context + ")");
            }
            if (mActivityThread != activityThread) {
                throw new IllegalStateException(
                    "Receiver " + mReceiver +
                    " registered with differing handler (was " +
                    mActivityThread + " now " + activityThread + ")");
            }
        }

校验一下当前的context和activityThread,如果不是同一个activity,那当前创建的的LoadedApk.ReceiverDispatcher对象是不合法的,会直接抛出异常。

1.2 Binder调用AMS中registerReceiver(...)函数

Android 8.0 代码和之前已经有了很大的变化,主要的架构没有变化,但是在AMS和AMP之间调用的部分有了一些变化,现在已经找不到AMP代码部分了,之前通过AMPbinder调用到AMS,现在直接通过ActivityManager.getService()直接调用到AMS

ActivityManager.java

private static final Singleton IActivityManagerSingleton =
            new Singleton() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };

通过这样的方式获取ACTIVITY_SERVICE的binder,底层有很复杂的binder通信的过程,最后会在AMS中接收到registerReceiver(...)中。

2.AMS-registerReceiver剖析

到这儿来算是走到了system_server进程了,注册receiver,绑定的service等等都会system_server进程中完成,这儿会统一管理这些组件,所以核心的逻辑也会在这里面。

2.1 registerReceiver参数解析

registerReceiver中传入了7个参数:

public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter, String permission, int userId,
            int flags)

调用的地方:

final Intent intent = ActivityManager.getService().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
                    broadcastPermission, userId, flags);
  • caller:
    传入IApplicationThread binder对象,通过这个对象可以找到当前调用的Process
  • callerPackage:
    当前调用者的package 名称
  • receiver:
    这个参数很关键,就是我们上面一直在讲的IIntentReceiver对象,这个对象的在后面有大用处,别忘了还有一个performReceive等着回调了。
  • filter:
    当前注册的receiver的action属性或者其他的属性就放在这里。
  • permission:
    广播设置的属性
  • userId:
    调用的userId标识
  • flags:
    这个flags标识,默认情况下是0

2.2 获取uid与pid

      if (caller != null) {
                callerApp = getRecordForAppLocked(caller);
                if (callerApp == null) {
                    throw new SecurityException(
                            "Unable to find app for caller " + caller
                            + " (pid=" + Binder.getCallingPid()
                            + ") when registering receiver " + receiver);
                }
                if (callerApp.info.uid != SYSTEM_UID &&
                        !callerApp.pkgList.containsKey(callerPackage) &&
                        !"android".equals(callerPackage)) {
                    throw new SecurityException("Given caller package " + callerPackage
                            + " is not running in process " + callerApp);
                }
                callingUid = callerApp.info.uid;
                callingPid = callerApp.pid;
            } else {
                callerPackage = null;
                callingUid = Binder.getCallingUid();
                callingPid = Binder.getCallingPid();
            }

上面这段代码总结起来:

  • 当前调用者caller binder结构存在,根据caller取出对应的ProcessRecord,如果当前ProcessRecord为空,说明注册广播的进程不存在,抛出异常。
  • 当前ProcessRecord不为空,就要判断当前的调用者时候和传入的pkg属性匹配,如果不匹配,当前的调用不合法,要抛出异常。如果匹配的话,当前PorcessRecord会复制uid和pid。
  • 当前调用者caller binder结构不存在,当前调用pkg也是不存在的,则从Binder底层赋 uid和pid。

总之一句话,就是当前进程要存在且合法。AMS中到处都有这样的校验过程。

2.3 获取当前的粘性广播

要了解粘性广播的话,就知道,所谓粘性广播,如果当前发送的粘性广播在注册广播之前,这个广播也是会被收到的;繁殖不是粘性广播,就收不到。
开始根据上面生成的uid获取特定用户的粘性广播:

      int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
            while (actions.hasNext()) {
                String action = actions.next();
                for (int id : userIds) {
                    ArrayMap> stickies = mStickyBroadcasts.get(id);
                    if (stickies != null) {
                        ArrayList intents = stickies.get(action);
                        if (intents != null) {
                            if (stickyIntents == null) {
                                stickyIntents = new ArrayList();
                            }
                            stickyIntents.addAll(intents);
                        }
                    }
                }
            }

这儿得到stickyIntents 后面还会用到。如果当前receiver的intentFilter匹配粘性广播序列中任何一个intent的话,讲当前的intent加载到allSticky数组中。这个数组也会有用处。

Intent sticky = allSticky != null ? allSticky.get(0) : null;
        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
        if (receiver == null) {
            return sticky;
        }

这儿表示当前即使当前的receiver为空,粘性广播序列也会返回,切返回的是数组中第一个索引的值。这儿恰好能说明粘性广播的特性。

2.4 注册当前的广播

匹配玩粘性广播,这儿要操作普通的广播了。

ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
            if (rl == null) {
                rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                        userId, receiver);
                if (rl.app != null) {
                    rl.app.receivers.add(rl);
                } else {
                    try {
                        receiver.asBinder().linkToDeath(rl, 0);
                    } catch (RemoteException e) {
                        return sticky;
                    }
                    rl.linkedToDeath = true;
                }
                mRegisteredReceivers.put(receiver.asBinder(), rl);
            } 
//......
            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
                    permission, callingUid, userId, instantApp, visibleToInstantApps);
            rl.add(bf);
            mReceiverResolver.addFilter(bf);

这里的是一个mRegisteredReceivers是一个HashMap,他的key是一个binder结构,key是一个ReceiverList

final HashMap mRegisteredReceivers = new HashMap<>();

这个对应关系很好理解,binder对象中有receiver的处理的地方,一对多的关系,这个ReceiverList可以包含多个Receiver,接下来广播需要入队了。

2.5 广播入队列

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, AppOpsManager.OP_NONE, null, receivers,
                            null, 0, null, null, false, true, true, -1);
                    queue.enqueueParallelBroadcastLocked(r);
                    queue.scheduleBroadcastsLocked();
                }
            }
  • AMS中有两种广播队列,前台广播队列和后台广播队列,一般情况下都是后台广播队列。前台广播队列除非手动指定。
  • BroadcastRecord 是描述广播的数据结构,这里描述了一系列广播的属性。
  • queue.enqueueParallelBroadcastLocked(r); 当前receiver入队,广播还分为两种:并行广播和有序广播,并行广播实际上就是无序广播。
  • queue.scheduleBroadcastsLocked(); 这个调用要展开说下,见下文。

queue.scheduleBroadcastsLocked()通过sendMessage(BROADCAST_INTENT_MSG)调用到processNextBroadcast(true),这个函数代码非常庞大,有600行。我们挑关键点讲解。

2.5.1 处理并行广播

记住这儿执行的一个关键的函数:
deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i);

private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
            BroadcastFilter filter, boolean ordered, int index) {
        boolean skip = false;
        //中间省略了很多代码,主要的执行目的是check一下当前receiver的权限是否符合要求。
//如果符合要求,那么skip为false,不符合要求。skip为true
        try {
            if (filter.receiverList.app != null && filter.receiverList.app.inFullBackup) {
                // Skip delivery if full backup in progress
                // If it's an ordered broadcast, we need to continue to the next receiver.
                if (ordered) {
                    skipReceiverLocked(r);
                }
            } else {
                performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
                        new Intent(r.intent), r.resultCode, r.resultData,
                        r.resultExtras, r.ordered, r.initialSticky, r.userId);
            }
            if (ordered) {
                r.state = BroadcastRecord.CALL_DONE_RECEIVE;
            }
        } catch (RemoteException e) {
            Slog.w(TAG, "Failure sending broadcast " + r.intent, e);
            if (ordered) {
                r.receiver = null;
                r.curFilter = null;
                filter.receiverList.curBroadcast = null;
                if (filter.receiverList.app != null) {
                    filter.receiverList.app.curReceivers.remove(r);
                }
            }
        }
    }

下面这个函数会直接回调performReceive,结尾处会讲解一下这个performReceive做什么工作。

performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
                        new Intent(r.intent), r.resultCode, r.resultData,
                        r.resultExtras, r.ordered, r.initialSticky, r.userId);
2.5.2 处理当前挂起的广播

这要看当前的进程是否存在或者crash,如果不存在或者crash,那么当前的进程死掉了,直接return,不继续往下走了。

2.5.3 处理有序广播

处理有序广播要关注一下注册时间,这是判断有序的依据,了解这个之后,它的处理逻辑还是放在performReceiveLocked(...)中。

2.5.4 处理下一个广播

之前的处理并行广播、挂起广播、有序广播都是处理当前存在queue中的广播,处理完了之后,开始处理这之后的广播,就是俗称的下一个广播。

2.5.5 判断广播调起的一系列权限问题

如果开始的时候申请了一些权限,这儿需要对权限进行校验,合法的情况下才能继续下去。

2.5.6 处理当前接收者

当前的接收者进程,判断有两种情况:

  • 当前接收者进程存在,直接执行 processCurBroadcastLocked(r, app);这个函数最终也会回调到performReceive中。
  • 当前接收者进程如果不存在,需要创建一个新的进程,然后调用scheduleBroadcastsLocked(...),这个函数就是如队列的函数,重新开始一遍。

3.performReceive回调函数

前面我们描述调用过程的时候,描述的重点都是performReceive(...)函数,现在要揭秘一下这个函数到底在执行什么?

InnerReceiver内部类
public void performReceive(Intent intent, int resultCode, String data,
                    Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
                final LoadedApk.ReceiverDispatcher rd;
       //......
                if (rd != null) {
                    rd.performReceive(intent, resultCode, data, extras,
                            ordered, sticky, sendingUser);
                } else {
                    try {
                        if (extras != null) {
                            extras.setAllowFds(false);
                        }
                        mgr.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }
            }
        }

这儿又调用到ReceiverDispatcher.performReceive(...)

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 || !mActivityThread.post(args.getRunnable())) {
                if (mRegistered && ordered) {
                    IActivityManager mgr = ActivityManager.getService();
                    args.sendFinished(mgr);
                }
            }
        }
    }

这儿看到了一个执行这样一段话:
if (intent == null || !mActivityThread.post(args.getRunnable()))
这个判断条件,尤其是后一个mActivityThread.post(args.getRunnable()),这儿肯定执行了什么。
下面到执行的run()方法中总结代码,提取其执行要点:

ClassLoader cl = mReceiver.getClass().getClassLoader();
intent.setExtrasClassLoader(cl);
intent.prepareToEnterProcess();
setExtrasClassLoader(cl);
receiver.setPendingResult(this);
receiver.onReceive(mContext, intent);

这儿才是onReceive(...)的地方,但是我们知道,这里只是注册,还没有发送广播,但是可以确定的时,发送广播只是改变一些开关,保证执行到这里。

感谢大家,下文继续分享 发送广播。

你可能感兴趣的:(Android广播剖析之广播注册)