1. 类加载器层次
RePlugin号称只有一个唯一的hook点,这个唯一的hook点就是替换掉系统本身的PathClassLoader对象。那它是怎么做的呢?
1.1 替换PathClassLoader
调用路径为RePluginApplication.attachBaseContext()—>RePlugin.attachBaseContext()—>PMF.init()—>PatchClassLoaderUtils.patch(), 而PatchClassLoaderUtils.patch()方法如下:
|
|
这个方法看起来很长,其实做的事情很简单,就是将App的PathClassLoader替换为我们自己的RePluginClassLoader对象,那么App的PathClassLoader对象是藏在哪里呢?
其实就在ContextWrapper.mBase—>ContextImpl.mPackageInfo—>LoadedApk.mClassLoader中,首先Application继承自ContextWrapper,而ContextWrapper中的成员如下:
|
|
而这个mBase其实是ContextImpl对象,感兴趣的读者跟一下ContextWrapper.attachBaseContext()路径就知道了,这个ContextImpl是在ActivityThread中创建的,这里不再展开来讲了。
而ContextImpl的成员如下:
|
|
其中的LoadedApk对象mPackageInfo就含有加载进来的APK的各种数据,包括我们要替换的PathClassLoader对象。
LoadedApk中的成员如下:
|
|
注意其中的mClassLoader就是PathClassLoader对象。
怎么知道它是PathClassLoader对象呢?
看下它的创建过程即LoadedApk.getClassLoader()方法就知道了:
|
|
这个方法虽然有点长,但是其实逻辑很简单,就是如果包名不是”android”(普通App的包名都不是),那么就调用ApplicationLoaders.getClassLoader()方法创建PathClassLoader,该方法如下:
|
|
可以看到在最后调用了PathClassLoader的构造方法创建了PathClassLoader对象。
1.2 RePluginClassLoader
回到PatchClassLoaderUtils.patch()方法,它在最后调用RePluginCallbacks.createClassLoader()方法:
|
|
官方注释都已经写得很清楚了,我就不多解释了。
下面看RePluginClassLoader的构造方法:
|
|
注意这里的mOrig就是系统的PathClassLoader对象。而copyFromOriginal()方法是PathClassLoader中的一些关键字段(包括libPath, libraryPathElements, mDexs, mFiles, mPaths, mZips等)拷贝到RePluginClassLoader中,initMethods()则是初始化反射要调用的方法。
在前一小节中提到,在初始化时会将系统的PathClassLoader对象替换为RePluginClassLoader对象,那么之后有类需要加载时,就会调用到RePluginClassLoader的loadClass()方法:
|
|
显然,这里是先调用PMF.loadClass()加载类(实际上宿主和插件中的业务相关的类都是由它加载的),如果返回null才调用原始的PathClassLoader的loadClass()方法去加载。
PMF只是一个装饰类,最后会调用到PmBase的loadClass()方法:
|
|
这个方法虽然看起来很长,实际上分成如下几个部分就很好理解了:
- 如果类名是以”PluginPitService”开头,则说明需要加载Service中介坑位,返回PluginPitService.class
如果mContainerActivities包含该activity名称,就调用PluginProcessPer.resolveActivityClass()方法进行加载,注意这里的activity是坑位名称,实际上只要是通过RePlugin分配坑位的,都会记录在mContainerActivities. 下面进入PluginProcessPer.resolveActivityClass()方法:
12345678910111213141516171819202122232425262728293031323334353637/*** 类加载器根据容器解析到目标的activity* @param container* @return*/final Class<?> resolveActivityClass(String container) {String plugin = null;String activity = null;// 先找登记的,如果找不到,则用forward activityPluginContainers.ActivityState state = mACM.lookupByContainer(container);if (state == null) {// PACM: loadActivityClass, not register, use forward activity, container=...return ForwardActivity.class;}plugin = state.plugin;activity = state.activity;...Plugin p = mPluginMgr.loadAppPlugin(plugin);if (p == null) {// PACM: loadActivityClass, not found plugin...return null;}ClassLoader cl = p.getClassLoader();...Class<?> c = null;try {c = cl.loadClass(activity);} catch (Throwable e) {...}...return c;}
首先,为了加载真正要启动的activity,需要根据当前的坑位(container)找到记录了要启动的activity名称的ActivityState(ActivityState中记录了插件名称和真正要启动的activity名称),调用PluginContainers.lookupByContainer()进行查找:
|
|
这个逻辑很简单,就是调用mStates.get(container)获取到对应的ActivityState,而这个ActivityState中的activity是什么时候赋值的呢?
就是在之前在allocLocked()方法分配坑位时,调用了ActivityState的occupy()方法,在occupy()方法中将真正要启动的activity记录在了ActivityState的activity字段中,如果有不了解的童鞋,可以看我前面一篇文章: RePlugin解析-startActivity流程分析
再回到PluginProcessPer.resolveActivityClass()方法中,获取到ActivityState对象后,取出其中的plugin和activity字段,之后调用PmBase.loadAppPlugin()方法获取对应的插件:
|
|
可见这里有两个操作:
第一,通过mPlugins这个HashMap获取到对应的Plugin对象;
第二,调用Plugin.load()方法加载插件,并且加载类型为Plugin.LOAD_APP;而Plugin.load()方法如下:
12345678910111213141516final boolean load(int load, boolean useCache) {PluginInfo info = mInfo;boolean rc = loadLocked(load, useCache);// 尝试在此处调用Application.onCreate方法// Added by Jiongxuan Zhangif (load == LOAD_APP && rc) {callApp();}// 如果info改了,通知一下常驻// 只针对P-n的Type转化来处理,一定要通知,这样Framework_Version也会得到更新if (rc && mInfo != info) {UpdateInfoTask task = new UpdateInfoTask((PluginInfo) mInfo.clone());Tasks.post2Thread(task);}return rc;}其中loadLocked()方法在RePlugin解析之插件的安装与加载一文中介绍过loadLocked()方法,主要做的事情如下:
插件加载过程中主要做了以下事情:
将插件文件解压到目标目录中
解析插件的所有组件信息
- 调整插件的进程信息(其实就是进行映射)
- 调整插件中Activity的taskAffinity
- 解析插件中的资源
- 创建PluginDexClassLoader
下面讲callApp()方法:
12345678910111213private void callApp() {if (Looper.myLooper() == Looper.getMainLooper()) {callAppLocked();} else {// 确保一定在UI的最早消息处调用mMainH.postAtFrontOfQueue(new Runnable() {public void run() {callAppLocked();}});}}显然,这就是为了在主线程中调用callAppLocked()方法:
1234567891011121314151617181920212223private void callAppLocked() {// 获取并调用Application的几个核心方法if (!mDummyPlugin) {// NOTE 不排除A的Application中调到了B,B又调回到A,或在同一插件内的onCreate开启Service/Activity,而内部逻辑又调用fetchContext并再次走到这里// NOTE 因此需要对mApplicationClient做判断,确保永远只执行一次,无论是否成功if (mApplicationClient != null) {// 已经初始化过,无需再次处理return;}mApplicationClient = PluginApplicationClient.getOrCreate(mInfo.getName(), mLoader.mClassLoader, mLoader.mComponents, mLoader.mPluginObj.mInfo);if (mApplicationClient != null) {mApplicationClient.callAttachBaseContext(mLoader.mPkgContext);mApplicationClient.callOnCreate();}} else {if (LOGR) {LogRelease.e(PLUGIN_TAG, "p.cal dm " + mInfo.getName());}}}显然,这里就是通过包装一些方法到PluginApplicationClient中,最终调用到插件Application的attachBaseContext()和onCreate()方法。
再回到PluginProcessPer.resolveActivityClass()方法,ClassLoader cl=p.getClassLoader()返回的就是在loadLocked()中创建的PluginDexClassLoader, 其loadClass()方法如下:
|
|
注意PluginDexClassLoader继承自DexClassLoader,所以上述方法中的super.loadClass()其实是采用了双亲委托模型,如果插件中没有此类,那么 就要从宿主ClassLoader中找。
1.3 总结
所以,归纳起来,RePlugin中的类加载层次跟Atlas类似,都是从宿主到插件的加载顺序,用图形表示如下:
2. 四大组件的加载
2.1 activity的加载
在第一节中,就以activity的加载为例进行讲解,这里就不再赘述了。
2.2 service的加载
跟activity类似,只需要根据service坑位进行还原真实的service,然后调用插件的PluginDexClassLoader.loadClass()即可。
2.3 ContentProvider的加载
与service类似,不再赘述。
2.4 broadcast receiver的加载
由于把所有的broadcast receiver都动态注册,不涉及任何坑位的替换等问题,所以跟普通类的加载是一样的,分析见下一节。
3.普通类的加载
其他类的加载是在PmBase.loadDefaultClass()方法中:
|
|
这个方法很简单,就是调用插件的PluginDexClassLoader.loadClass()方法,其实也很好理解,对于插件中除四大组件之外的普通类,不需要根据坑位还原真实组件名称的操作,所以直接调用加载了插件dex文件的PluignDexClassLoader的loadClass()方法即可。