首页 > 代码库 > android 广播的插件化
android 广播的插件化
------本文转载自 Android插件化原理解析——广播的管理 这一系列的文章实在是写的好!
1, 概述
为了实现Activity的插件化我们付出了相当多的努力;那么Android系统的 其他组件,比如BroadcastReceiver,Service还有ContentProvider,它们又该如何处理呢?
相比Activity,BroadcastReceiver要简单很多——广播的生命周期相当简单;如果希望插件能够支持广播,这意味着什么?
回想一下我们日常开发的时候是如何使用BroadcastReceiver的:注册, 发送和接收;因此,要实现BroadcastReceiver的插件化就这三种操作提供支持;
接下来我们将一步步完成这个过程。
阅读本文之前,可以先clone一份understand-plugin-framework,参考此项目的receiver-management模块。本编文章的源码基于android 6.0.
2, 广播源码分析
我们可以注册一个BroadcastReceiver然后接收我们感兴趣的广播,也可以给某有缘人发出某个广播;因此,我们对源码的分析按照两条路线展开:
2.1 注册过程
不论是静态广播还是动态广播,在使用之前都是需要注册的;动态广播的注册需要借助Context类的registerReceiver方法,
而静态广播的注册直接在AndroidManifest.xml中声明即可;我们首先分析一下动态广播的注册过程。
Context类的registerReceiver的真正实现在ContextImpl里面,而这个方法间接调用了registerReceiverInternal,源码如下:
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId, IntentFilter filter, String broadcastPermission, Handler scheduler, Context context) { IIntentReceiver rd = null; // Important !!!!! 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; } }
可以看到,BroadcastReceiver的注册也是通过AMS完成的;在进入AMS跟踪它的registerReceiver方法之前,
我们先弄清楚这个IIntentReceiver类型的变量rd是什么。这个类是通过AIDL工具生成的,它是一个Binder对象,
因此可以用来跨进程传输;它是用来进行广播分发的。
由于广播的分发过程是在AMS中进行的,而AMS所在的进程和BroadcastReceiver所在的进程不一样,
因此要把广播分发到BroadcastReceiver具体的进程需要进行跨进程通信,这个通信的载体就是IIntentReceiver类。
其实这个类的作用跟Activity生命周期管理 中提到的IApplicationThread相同,都是App进程给AMS进程用来进行通信的对象。
另外,IIntentReceiver是一个接口,从上述代码中可以看出,它的实现类为LoadedApk.ReceiverDispatcher。
OK,我们继续跟踪源码,AMS类的registerReceiver方法代码有点多,这里不一一解释了,感兴趣的话可以自行查阅;
这个方法主要做了以下两件事:
1.对发送者的身份和权限做出一定的校检
2.把这个BroadcastReceiver以BroadcastFilter的形式存储在AMS的mReceiverResolver变量中,供后续使用。
就这样,被传递过来的BroadcastReceiver已经成功地注册在系统之中,能够接收特定类型的广播了;
那么注册在AndroidManifest.xml中的静态广播是如何被系统感知的呢?
在 插件加载机制 中我们知道系统会通过PackageParser解析Apk中的AndroidManifest.xml文件,因此我们有理由认为,
系统会在解析AndroidMafest.xml的<receiver>标签(也即静态注册的广播)的时候保存相应的信息;
而Apk的解析过程是在PMS 中进行的,因此静态注册广播的信息存储在PMS中。接下来的分析会证实这一结论。
2.2 发送过程
发送广播很简单,就是一句context.sendBroadcast(),我们顺藤摸瓜,跟踪这个方法。前文也提到过,
Context中方法的调用都会委托到ContextImpl这个类,我们直接看ContextImpl对这个方法的实现:
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); } }
发送广播也是通过AMS进行的,我们直接查看ActivityManagerService类的broadcastIntent方法,
这个方法 仅仅是调用了broadcastIntentLocked方法,我们继续跟踪;broadcastIntentLocked这个方法相当长,
处理了诸如粘性广播,顺序广播,各种Flag以及动态广播静态广播的接收过程,这些我们暂时不关心;
值得注意的是,在这个方法中我们发现,其实广播的发送和接收是融为一体的。某个广播被发送之后,
AMS会找出所有注册过的BroadcastReceiver中与这个广播匹配的接收者,然后将这个广播分发给相应的接收者处理。
2.3 匹配过程
某一条广播被发出之后,并不是阿猫阿狗都能接收它并处理的;BroadcastReceiver可能只对某些类型的广播感兴趣,
因此它也只能接收和处理这种特定类型的广播;在broadcastIntentLocked方法内部有如下代码:
// Figure out who all will receive this broadcast. List receivers = null; List<BroadcastFilter> registeredReceivers = null; // Need to resolve the intent to interested receivers... if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) { receivers = collectReceiverComponents(intent, resolvedType, callingUid, users); } if (intent.getComponent() == null) { if (userId == UserHandle.USER_ALL && callingUid == Process.SHELL_UID) { // Query one target user at a time, excluding shell-restricted users // 略 } else { registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false, userId); } }
这里有两个列表receivers和registeredReceivers,看名字好像是广播接收者的列表;下面是它们的赋值过程:
receivers = collectReceiverComponents(intent, resolvedType, callingUid, users); registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false, userId);
1.receivers是对这个广播感兴趣的静态BroadcastReceiver列 表;collectReceiverComponents
通过PackageManager获取了与这个广播匹配的静态BroadcastReceiver信息;
这里也证实了我们在分析BroadcasrReceiver注册过程中的推论——静态BroadcastReceiver的注册过程的确实在PMS中进行的。
2.mReceiverResolver存储了动态注册的BroadcastReceiver的信息;还记得这个mReceiverResolver吗?
我们在分析动态广播的注册过程中发现,动态注册的BroadcastReceiver的相关信息最终存储在此对象之中;
在这里,通过mReceiverResolver对象匹配出了对应的BroadcastReceiver供进一步使用。
现在系统通过PMS拿到了所有符合要求的静态BroadcastReceiver,然后从AMS中获取了符合要求的动态BroadcastReceiver;
因此接下来的工作非常简单:唤醒这些广播接受者。简单来说就是回调它们的onReceive方法。
2.4 接收过程
通过上文的分析过程我们知道,在AMS的broadcastIntentLocked方法中找出了符合要求的所有BroadcastReceiver;
接下来就需要把这个广播分发到这些接收者之中。在broadcastIntentLocked方法的后半部分有如下代码:
BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, resolvedType, requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId); boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); if (!replaced) { queue.enqueueOrderedBroadcastLocked(r); queue.scheduleBroadcastsLocked(); }
首先创建了一个BroadcastRecord代表此次发送的这条广播,然后把它丢进一个队列,
最后通过scheduleBroadcastsLocked通知队列对广播进行处理。
在BroadcastQueue中通过Handle调度了对于广播处理的消息,调度过程由processNextBroadcast方法完成,
而这个方法通过performReceiveLocked最终调用了IIntentReceiver的performReceive方法。
这个IIntentReceiver正是在广播注册过程中由App进程提供给AMS进程的Binder对象,
现在AMS 通过这个Binder对象进行IPC调用通知广播接受者所在进程完成余下操作。
在上文我们分析广播的注册过程中提到过,这个IItentReceiver 的实现是LoadedApk.ReceiverDispatcher;
我们查看这个对象的performReceive方法,源码如下:
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { Args args = new Args(intent, resultCode, data, extras, ordered, sticky, sendingUser); if (!mActivityThread.post(args)) { if (mRegistered && ordered) { IActivityManager mgr = ActivityManagerNative.getDefault(); args.sendFinished(mgr); } } }
这个方法创建了一个Args对象,然后把它post到了mActivityThread这个Handler中;我们查看Args类的run方法:
public void run() { final BroadcastReceiver receiver = mReceiver; final boolean ordered = mOrdered; final IActivityManager mgr = ActivityManagerNative.getDefault(); final Intent intent = mCurIntent; mCurIntent = null; if (receiver == null || mForgotten) { if (mRegistered && ordered) { sendFinished(mgr); } return; } try { ClassLoader cl = mReceiver.getClass().getClassLoader(); // Important!! load class intent.setExtrasClassLoader(cl); setExtrasClassLoader(cl); receiver.setPendingResult(this); receiver.onReceive(mContext, intent); // callback } catch (Exception e) { if (mRegistered && ordered) { sendFinished(mgr); } if (mInstrumentation == null || !mInstrumentation.onException(mReceiver, e)) { throw new RuntimeException( "Error receiving broadcast " + intent + " in " + mReceiver, e); } } if (receiver.getPendingResult() != null) { finish(); } }
这里,我们看到了相应BroadcastReceiver的onReceive回调;因此,广播的工作原理到这里就水落石出了;
我们接下来将探讨如何实现对于广播的插件化。
3, service插件化
3.1 思路分析
上文中我们分析了BroadcastReceiver的工作原理,那么怎么才能实现对BroadcastReceiver的插件化呢?
从分析过程中我们发现,Framework对于静态广播和动态广播的处理是不同的;
不过,这个不同之处仅仅体现在注册过程——静态广播需要在AndroidManifest.xml中注册,
并且注册的信息存储在PMS中;动态广播不需要预注册,注册的信息存储在AMS中。
从实现Activity的插件化过程中我们知道,需要在AndroidManifest.xml中预先注册是一个相当麻烦的事情——
我们需要使用『替身』并在合适的时候进行『偷梁换柱』;因此看起来动态广播的处理要容易那么一点,
我们先讨论一下如何实现动态注册BroadcastReceiver 的插件化。
首先,广播并没有复杂的生命周期,它的整个存活过程其实就是一个onReceive回调;
而动态广播又不需要在 AndroidManifest.xml中预先注册,所以动态注册的BroadcastReceiver
其实可以当作一个普通的Java对象;我们完全可 以用纯ClassLoader技术实现它——
不就是把插件中的Receiver加载进来,然后想办法让它能接受onReceive回调嘛。
静态BroadcastReceiver看起来要复杂一些,但是我们连Activity都搞定了,还有什么难得到我们呢?
对于实现静态BroadcastReceiver插件化的问题,有的童鞋或许会想,我们可以借鉴Activity的工作方式——
用替身和Hook解决。但是很遗憾,这样是行不通的。为什么呢?
BroadcastReceiver有一个IntentFilter的概念,也就是说,每一个BroadcastReceiver只对特定的 Broadcast感兴趣;
而且,AMS在进行广播分发的时候,也会对这些BroadcastReceiver与发出的广播进行匹配,
只有Intent匹配的Receiver才能收到广播;在分析源码的时候也提到了这个匹配过程。
如果我们尝试用替身Receiver解决静态注册的问题,那么它的 IntentFilter该写什么?
我们无法预料插件中静态注册的Receiver会使用什么类型的IntentFilter,就算我们在 AndroidManifest.xml中
声明替身也没有用——我们压根儿收不到与我们的IntentFilter不匹配的广播。其实,
我们对于Activity的处理方式也有这个问题;如果你尝试用IntentFilter的方式启动Activity,这并不能成功;这算得上是 DroidPlugin的缺陷之一。
那么,我们就真的对静态BroadcastReceiver无能为力吗?想一想这里的难点是什么?
没错,主要是在静态BroadcastReceiver里面这个IntentFilter我们事先无法确定,它是动态变化的;
但是,动态BroadcastReceiver不是可以动态添加IntentFilter吗?
可以把静态广播当作动态广播处理
既然都是广播,它们的功能都是订阅一个特定的消息然后执行某个特定的操作,
我们完全可以把插件中的静态广播全部注册为动态广播,这样就解决了静态广播的问题。
当然,这样也是有缺陷的,静态BroadcastReceiver与动态BroadcastReceiver一个非常大的不同之处在于:
动态 BroadcastReceiver在进程死亡之后是无法接收广播的,而静态BroadcastReceiver则可以——
系统会唤醒Receiver所 在进程;这算得上缺陷之二,当然,瑕不掩瑜。
3.2 静态广播非静态的实现
可以把静态BroadcastReceiver当作动态BroadcastReceiver处理;我们接下来实现这个过程。
3.2.1解析
要把插件中的静态BroadcastReceiver当作动态BroadcastReceiver处理,我 们首先得知道插件中到底注册了哪些广播;
这个过程归根结底就是获取AndroidManifest.xml中的<receiver>标签下面的内容,我们可以选择手动解析xml文件;
这里我们选择使用系统的 PackageParser 帮助解析,这种方式在之前的 [插件加载过程][] 中也用到过,如果忘记了可以温习一下。
PackageParser中有一系列方法用来提取Apk中的信息,可是翻遍了这个类也没有找到与「Receiver」名字相关的方法;
最终我们发现BroadcastReceiver信息是用与Activity相同的类存储的!
这一点可以在PackageParser的内部类Package中发现端倪——成员变量receivers和activities的范型类型相同。
所以,我们要解析apk的<receiver>的信息,可以使用PackageParser的generateActivityInfo方法。
知道这一点之后,代码就比较简单了;使用反射调用相应的隐藏接口,并且在必要的时候构造相应
参数的方式我们在插件化系列文章中已经讲述过很多,相信读者已经熟练,这里就不赘述,直接贴代码:
private static void parserReceivers(File apkFile) throws Exception { Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser"); Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class); Object packageParser = packageParserClass.newInstance(); // 首先调用parsePackage获取到apk对象对应的Package对象 Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, PackageManager.GET_RECEIVERS); // 读取Package对象里面的receivers字段,注意这是一个 List<Activity> (没错,底层把<receiver>当作<activity>处理) // 接下来要做的就是根据这个List<Activity> 获取到Receiver对应的 ActivityInfo (依然是把receiver信息用activity处理了) Field receiversField = packageObj.getClass().getDeclaredField("receivers"); List receivers = (List) receiversField.get(packageObj); // 调用generateActivityInfo 方法, 把PackageParser.Activity 转换成 Class<?> packageParser$ActivityClass = Class.forName("android.content.pm.PackageParser$Activity"); Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState"); Class<?> userHandler = Class.forName("android.os.UserHandle"); Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId"); int userId = (Integer) getCallingUserIdMethod.invoke(null); Object defaultUserState = packageUserStateClass.newInstance(); Class<?> componentClass = Class.forName("android.content.pm.PackageParser$Component"); Field intentsField = componentClass.getDeclaredField("intents"); // 需要调用 android.content.pm.PackageParser#generateActivityInfo(android.content.pm.ActivityInfo, int, android.content.pm.PackageUserState, int) Method generateReceiverInfo = packageParserClass.getDeclaredMethod("generateActivityInfo", packageParser$ActivityClass, int.class, packageUserStateClass, int.class); // 解析出 receiver以及对应的 intentFilter for (Object receiver : receivers) { ActivityInfo info = (ActivityInfo) generateReceiverInfo.invoke(packageParser, receiver, 0, defaultUserState, userId); List<? extends IntentFilter> filters = (List<? extends IntentFilter>) intentsField.get(receiver); sCache.put(info, filters); } }
3.2.2注册
我们已经解析得到了插件中静态注册的BroadcastReceiver的信息,现在我们只需要把这些静态广播动态注册一遍就可以了;
但是,由于BroadcastReceiver的实现类存在于插件之后,我们需要手动用ClassLoader来加载它;这一点在插件加载机制已有讲述,不啰嗦了。
ClassLoader cl = null; for (ActivityInfo activityInfo : ReceiverHelper.sCache.keySet()) { Log.i(TAG, "preload receiver:" + activityInfo.name); List<? extends IntentFilter> intentFilters = ReceiverHelper.sCache.get(activityInfo); if (cl == null) { cl = CustomClassLoader.getPluginClassLoader(apk, activityInfo.packageName); } // 把解析出来的每一个静态Receiver都注册为动态的 for (IntentFilter intentFilter : intentFilters) { BroadcastReceiver receiver = (BroadcastReceiver) cl.loadClass(activityInfo.name).newInstance(); context.registerReceiver(receiver, intentFilter); } }
就这样,我们对插件静态BroadcastReceiver的支持已经完成了,是不是相当简单?
至于插件中的动态广播如何实现插件化,这一点交给读者自行完成,希望你在解决这个问题的过程中能够加深对于插件方案的理解.
4小节
本文我们介绍了BroadcastReceiver组件的插件化方式,可以看到,插件方案对于
BroadcastReceiver的处理相对简单;同时「静态广播非静态」的特性以及BroadcastReceiver
先天的一些特点导致插件方案没 有办法做到尽善尽美,不过这都是大醇小疵——在绝大多数情况下,
这样的处理方式是可以满足需求的。
虽然对于BroadcastReceiver的处理方式相对简单,但是文章的内容却并不短——
我们花了大量的篇幅讲述BroadcastReceiver的原理,这也是我的初衷:借助DroidPlugin更深入地了解Android Framework。
android 广播的插件化