1.插件的加载:从PmBase.loadAppPlugin()说起
在RePlugin解析-startActivity流程分析 文章的最后提到PmBase.loadAppPlugin()方法, 如下:
|
|
这里的3个参数都很重要,首先看mPlugins是如何来的。
mPlugins其实包含的就是插件信息,key为插件的包名或别名,而value则为插件对象。它其实是在常驻进程初始化时赋值的。当然,中间如果安装了新的插件,mPlugins中的信息也会有更新。
1.1 插件信息的加载
首先看一下插件信息是如何来的,关注initForServer()中调用的Builder.builder()方法.
|
|
其中的重点在于Finder.search()这个方法调用,因为它会搜索所有本地插件和V5插件,如下:
|
|
真正执行查询的是FinderBuiltin.loadPlugins()这个方法:
|
|
显然,这里就是查询assets目录下的plugins-builtin.json这个文件,获取所有内建的插件信息,该文件示例如下:
|
|
而读取里面的json信息的代码如下:
|
|
其实就是读取json信息然后赋值给PluginInfo,其实PluginInfo本来应该设计成一个标准的Java bean,而现在竟然仍然用JSONObject来保存信息,然后后面需要时又要读出来,这其实是一个不太好的设计。
1.2 initForServer()
如果允许开启常驻进程来管理插件进程,则在在首次启动:GuardService这个常驻进程时,会调用initForServer()进行初始化:
|
|
在其中的refreshPluginMap()中会进行mPlugins的赋值:
|
|
可见,首先根据之前的插件信息生成Plugin对象,然后分别以插件的包名和别名为key,以Plugin对象作为value插入到mPlugins中,经过初始化后,Demo中的结果如下图:
所以,再回到PluginCommImpl.getActivityInfo()这个方法,显然可以根据”demo1”这个别名获取到对应的Plugin对象。
1.3 插件解压到目标目录中
到这里为止,我们只是分析了读取插件信息并且创建Plugin对象的过程,但是还没有分析完插件的加载过程,实际上插件的加载涉及到dex文件,so库等的处理。
而内建插件的加载流程如下:
Plugin.load(int, boolean)方法如下:
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()方法完成加载,然后调用callApp()以调用到插件的Application.onCreate()方法。
loadLocked()方法如下:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117private boolean loadLocked(int load, boolean useCache) {// 若插件被“禁用”,则即便上次加载过(且进程一直活着),这次也不能再次使用了// Added by Jiongxuan Zhangint status = PluginStatusController.getStatus(mInfo.getName(), mInfo.getVersion());if (status < PluginStatusController.STATUS_OK) {...return false;}if (mInitialized) {if (mLoader == null) {...return false;}if (load == LOAD_INFO) {boolean rl = mLoader.isPackageInfoLoaded();...return rl;}if (load == LOAD_RESOURCES) {boolean rl = mLoader.isResourcesLoaded();...return rl;}if (load == LOAD_DEX) {boolean rl = mLoader.isDexLoaded();...return rl;}boolean il = mLoader.isAppLoaded();...return il;}mInitialized = true;...// 这里先处理一下,如果cache命中,省了后面插件提取(如释放Jar包等)操作if (useCache) {boolean result = loadByCache(load);// 如果缓存命中,则直接返回if (result) {return true;}}Context context = mContext;ClassLoader parent = mParent;PluginCommImpl manager = mPluginManager;//String logTag = "try1";String lockFileName = String.format(Constant.LOAD_PLUGIN_LOCK, mInfo.getApkFile().getName());ProcessLocker lock = new ProcessLocker(context, lockFileName);...if (!lock.tryLockTimeWait(5000, 10)) {// 此处仅仅打印错误...}//long t1 = System.currentTimeMillis();boolean rc = doLoad(logTag, context, parent, manager, load);...lock.unlock();...if (rc) {...try {// 至此,该插件已开始运行PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());} catch (Throwable e) {...}return true;}//logTag = "try2";lock = new ProcessLocker(context, lockFileName);if (!lock.tryLockTimeWait(5000, 10)) {// 此处仅仅打印错误...}// 清空数据对象mLoader = null;// 删除优化dex文件File odex = mInfo.getDexFile();if (odex.exists()) {...odex.delete();}if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {// support for multidex below LOLLIPOP:delete Extra odex,if needtry {FileUtils.forceDelete(mInfo.getExtraOdexDir());} catch (IOException e) {e.printStackTrace();}}t1 = System.currentTimeMillis();rc = doLoad(logTag, context, parent, manager, load);...//lock.unlock();if (!rc) {...return false;}...try {// 至此,该插件已开始运行PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());} catch (Throwable e) {...}return true;}
这个方法其实更多的是对异常情况的判断与处理,真正进行加载的是在Plugin.doLoad()方法中:
|
|
可见,加载插件时分两种类型,一种是TYPE_BUILTIN,另外一种是TYPE_PN_JAR(deprecated),由于后一种已经deprecated了,这里就不分析了。
对于TYPE_BUILTIN类型的插件,先将插件文件从asset中解压到/data/data/pkg/plugins_v3/目录下。然后将新的目录赋值给clone出的PluginInfo.
1.4 四大组件信息解析
在将插件文件解压到指定目录之后,创建了Loader对象,并且调用Loader.loadDex()方法,如下:
|
|
这个方法看似很长,其实没多少东西,主要做了以下事情:
从缓存中获取PackageInfo,如果没有获取到,说明是第一次,需要创建,创建方式是将插件解压后的目录传递给PackageManager,代码如下:
12mPackageInfo = pm.getPackageArchiveInfo(mPath,PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA);如果顺利创建PackageInfo对象,则还要为其设置soureDir, publicSourceDir和nativeLibraryDir. 之后将packageInfo对象放入Plugin.FILENAME 2 PACKAGE_INFO这个缓存中。
获取插件中的所有组件信息并保存在ComponentList对象mComponents中,其中ComponentList这个对象中定义的域如下:
123456789101112131415161718192021222324252627public class ComponentList {/*** Class类名 - Activity的Map表*/final HashMap<String, ActivityInfo> mActivities = new HashMap<>();/*** Class类名 - Provider的Map表*/final HashMap<String, ProviderInfo> mProvidersByName = new HashMap<>();/*** Authority - Provider的Map表*/final HashMap<String, ProviderInfo> mProvidersByAuthority = new HashMap<>();/*** Class类名 - Service的Map表*/final HashMap<String, ServiceInfo> mServices = new HashMap<>();/*** Application对象*/ApplicationInfo mApplication = null;...}显然,就是四大组件加上ApplicationInfo, 而4大组件的获取就在它的构造方法中:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768public ComponentList(PackageInfo pi, String path, PluginInfo pli) {if (pi.activities != null) {for (ActivityInfo ai : pi.activities) {...ai.applicationInfo.sourceDir = path;// todo extract to functionif (ai.processName == null) {ai.processName = ai.applicationInfo.processName;}if (ai.processName == null) {ai.processName = ai.packageName;}mActivities.put(ai.name, ai);}}if (pi.providers != null) {for (ProviderInfo ppi : pi.providers) {...if (ppi.processName == null) {ppi.processName = ppi.applicationInfo.processName;}if (ppi.processName == null) {ppi.processName = ppi.packageName;}mProvidersByName.put(ppi.name, ppi);mProvidersByAuthority.put(ppi.authority, ppi);}}if (pi.services != null) {for (ServiceInfo si : pi.services) {if (LOG) {LogDebug.d(PLUGIN_TAG, "service=" + si.name);}if (si.processName == null) {si.processName = si.applicationInfo.processName;}if (si.processName == null) {si.processName = si.packageName;}mServices.put(si.name, si);}}if (pi.receivers != null) {for (ActivityInfo ri : pi.receivers) {...if (ri.processName == null) {ri.processName = ri.applicationInfo.processName;}if (ri.processName == null) {ri.processName = ri.packageName;}mReceivers.put(ri.name, ri);}}// 解析 Apk 中的 AndroidManifest.xmlString manifest = getManifestFromApk(path);...// 生成组件与 IntentFilter 的对应关系ManifestParser.INS.parse(pli, manifest);mApplication = pi.applicationInfo;if (mApplication.dataDir == null) {mApplication.dataDir = Environment.getDataDirectory() + File.separator + "data" + File.separator + mApplication.packageName;}...}那么pi.activities等组件信息又是怎么来的呢?其实就来自于PackageInfo的创建过程中,前面说过它是由PackageManager.getPackageArchiveInfo()方法创建的,从这个方法开始,包的解析过程的调用为PackageManager.getPackageArchiveInfo()–>PackageParser.generatePackageInfo()—>PackageParser.generatePackageInfo()(重载方法), 在PackageParser.generatePackageInfo()中有四大组件的解析,代码片段如下:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556if ((flags & PackageManager.GET_ACTIVITIES) != 0) {final int N = p.activities.size();if (N > 0) {int num = 0;final ActivityInfo[] res = new ActivityInfo[N];for (int i = 0; i < N; i++) {final Activity a = p.activities.get(i);if (state.isMatch(a.info, flags)) {res[num++] = generateActivityInfo(a, flags, state, userId);}}pi.activities = ArrayUtils.trimToSize(res, num);}}if ((flags & PackageManager.GET_RECEIVERS) != 0) {final int N = p.receivers.size();if (N > 0) {int num = 0;final ActivityInfo[] res = new ActivityInfo[N];for (int i = 0; i < N; i++) {final Activity a = p.receivers.get(i);if (state.isMatch(a.info, flags)) {res[num++] = generateActivityInfo(a, flags, state, userId);}}pi.receivers = ArrayUtils.trimToSize(res, num);}}if ((flags & PackageManager.GET_SERVICES) != 0) {final int N = p.services.size();if (N > 0) {int num = 0;final ServiceInfo[] res = new ServiceInfo[N];for (int i = 0; i < N; i++) {final Service s = p.services.get(i);if (state.isMatch(s.info, flags)) {res[num++] = generateServiceInfo(s, flags, state, userId);}}pi.services = ArrayUtils.trimToSize(res, num);}}if ((flags & PackageManager.GET_PROVIDERS) != 0) {final int N = p.providers.size();if (N > 0) {int num = 0;final ProviderInfo[] res = new ProviderInfo[N];for (int i = 0; i < N; i++) {final Provider pr = p.providers.get(i);if (state.isMatch(pr.info, flags)) {res[num++] = generateProviderInfo(pr, flags, state, userId);}}pi.providers = ArrayUtils.trimToSize(res, num);}}可以看到,pi.activities就是在这里被赋值的。
生成组件与IntentFilter的对应关系。在完成四大组件的信息获取之后,最重要的一个调解就是下面这句了:
1ManifestParser.INS.parse(pli, manifest);其中INS是ManifestParser的单例,ManifestParser.parse()方法如下:
1234567891011121314151617181920public void parse(PluginInfo pli, String manifestStr) {XmlHandler handler = parseManifest(manifestStr);Map<String, List<IntentFilter>> activityFilterMap = new HashMap<>();putToMap(mPluginActivityInfoMap, activityFilterMap, pli);parseComponent(pli.getName(), activityFilterMap, handler.getActivities(), mActivityActionPluginsMap);Map<String, List<IntentFilter>> serviceFilterMap = new HashMap<>();putToMap(mPluginServiceInfoMap, serviceFilterMap, pli);parseComponent(pli.getName(), serviceFilterMap, handler.getServices(), mServiceActionPluginsMap);Map<String, List<IntentFilter>> receiverFilterMap = new HashMap<>();putToMap(mPluginReceiverInfoMap, receiverFilterMap, pli);parseComponent(pli.getName(), receiverFilterMap, handler.getReceivers(), null);/* 打印日志 */if (LOG) {printFilters(activityFilterMap, serviceFilterMap, receiverFilterMap);}}这里就是解析四大组件的intent-filter以及action信息的,逻辑很简单,就不赘述了。这里唯一需要注意的就是,RePlugin没有像DroidPlugin那样借用Android系统的Parser,而是自己利用SAXParser来解析,这样的好处就是不存在兼容性问题。
最后将pi.applicationInfo赋值给mApplication, 并且为mApplication.dataDir赋值为/data/data/host_pkg_name/data/plugin_pkg_name目录。
1.5 调整进程
再回到Loader.loadDex()方法中,在创建ComponentList对象后,有如下代码:
|
|
其中regReceivers()是将静态的BroadcastReceiver全部注册为动态的BroadcastReceiver, 然后将mComponents放入Plugin.FILENAME_ 2_COMPONENT_LIST缓存中。之后有两个非常重要的调用:
adjustPluginProcess(mPackageInfo.applicationInfo)用以调整插件中组件的进程名称。那它是如何做到的呢?看代码就知道了。
123456789101112private void adjustPluginProcess(ApplicationInfo appInfo) {HashMap<String, String> processMap = getConfigProcessMap(appInfo);if (processMap == null || processMap.isEmpty()) {processMap = genDynamicProcessMap();}...doAdjust(processMap, mComponents.getActivityMap());doAdjust(processMap, mComponents.getServiceMap());doAdjust(processMap, mComponents.getReceiverMap());doAdjust(processMap, mComponents.getProviderMap());...}
首先看下getConfigProcessMap()方法:
|
|
其中的appInfo.meta-data其实就是插件中Manifest中定义的meta-data的key-value数据,比如这里demo1这个插件中定义了如下meta-data:
|
|
可以很清楚地看到插件开发者希望将com.qihoo360.replugin.sample.demo1:bg这个进程映射到p0这个进程(其实是hostpkg:p0进程)。
所以这里getConfigProcessMap()的含义就是如果插件开发者指定了进程的映射关系,就要按照这个映射关系来分配插件进程(当然,如果发现插件进程已经被占用,还是会给它分配别的进程),如果插件开发者根本没指定,那就返回一个空的HashMap.
再回到adjustPluginProcess()方法中,剩下的事情就是对于四大组件逐一去调整那些有自定义进程的组件中的processName.
1.6 调整taskAffinity
在Loader.loadDex()方法中,调整完插件进程信息后,接下来就调用adjustPluginTaskAffinity()方法调整taskAffinity了:
|
|
这个方法其实很简单,即如果use_default_task_affinity为false,即不使用默认的taskAffinity值,那么就将Activity的taskAffinity修改为在之前的基础上加上”.”和plugin名称,代码为:
|
|
显然,这样做的目的是为了将各个插件的Activity的任务栈完全区分开来,以避免出现雷同导致影响用户体验。
1.7 资源解析
再回到Loader.loadDex()方法中,如果加载类型是Plugin.LOAD_INFO,那么到这里就可以直接返回了。否则下一步进行资源的解析:
|
|
这里分两种情况,如果是Debug模式,为了防止与Instant Run冲突,就new一个资源对象; 否则调用PackageManager的getResourcesForApplication()获取插件的资源对象,之后将资源放入缓存中。
1.8 创建PluginDexClassLoader
再继续看Loader.loadDex()方法,首次加载插件时,需要创建PluginDexClassLoader对象,创建之前需要以下参数:
- optimizedDirectory,即loadDex()方法中的out,其路径类似”/data/user/0/me.ele.repluginhostsample/app_plugins_v3/oat/arm64”这样
- 父ClassLoader,由于ClassLoader都是双亲委托的方式工作,release模式时采用的是getClass().getClassLoader().getParent(),我们知道getClass().getClassLoader()获取的是宿主的PathClassLoader,所以这里最后获取的是PathClassLoader的parent
- so文件夹,可通过PackageInfo.applicationInfo.nativeLibraryDir获得
之后就开始了PluginDexClassLoader的创建:
|
|
super()的调用就不说了,因为PluginDexClassLoader继承自DexClassLoader嘛,之后是对于Android 5.0之前的ROM需要安装多个dex,代码如下:
|
|
可以看到,这个安装方式跟Google提供的MultiDex安装方式基本相同,不再赘述。
之后是通过反射获取到宿主的loadClass()方法,后面在类的加载时会用到。
1.9 总结
到这里,就全部完成了一个插件的加载。总结一下,插件加载过程中主要做了以下事情:
- 将插件文件解压到目标目录中
- 解析插件的所有组件信息
- 调整插件的进程信息(其实就是进行映射)
- 调整插件中Activity的taskAffinity
- 解析插件中的资源
- 创建PluginDexClassLoader
- 可以看出,每个插件有自己的资源对象(Resources对象),类加载器(PluginDexClassLoader)
2.上下文替换
2.1 插件中ApplicationContext替换
在Loader.loadDex()方法的最后,有如下语句:
|
|
在这里就创建了一个PluginContext对象,那这个PluginContext对象在哪里被调用呢?
其实就在插件被加载之后!!!
看Plugin.load()方法:
|
|
其中loadLocked()方法就是我们前面讨论过的插件的加载过程,插件的所有数据都加载到内存之后,下一步要做的事情是什么?
当然就是启动插件了,启动的入口当然就是插件的Application.
其中的callApp()方法如下:
|
|
显然,这个方法的作用就是为了在宿主的UI线程中调用callAppLocked()方法:
|
|
可以看到,这个方法主要做的事情就是创建一个PluginApplicationClient对象,这个对象中封装了对于插件Application的操作,之后调用PluginApplicationClient.callAttachBaseContext()和PluginApplicationClient.callOnCreate()其实是通过反射调用插件Application的attachBaseContext()和onCreate()方法。
而在PluginApplication调用的callAttachBaseContext()中传入的Context就是在Loader.loadDex()中创建的PluginContext对象,可见PluginContext充当的作用是插件中的ApplicationContext.
2.2 插件中ActivityContext替换
PluginContext除了替换了插件中的ApplicationContext之外,其实还替换了插件中各个Activity的Context, 看replugin-plugin-library这个库中的PluginActivity.attachBaseContext()方法:
|
|
这里的RePluginInternal.createActivity()最终会通过反射调用到Factory2的createActivityContext()方法:
|
|
之后调用到PluginLibraryInternalProxy中的createActivityContext()方法:
|
|
而这里的Loader.createBaseContext()方法中就创建了PluginContext对象:
|
|
2.3 为什么要进行上下文替换
最主要的原因就是对于插件中依赖Context的调用,都需要重写,让它以插件的身份进行合适的操作,所以可以看到PluginContext这个类中重写了ContextWrapper和ContextThemeWrapper的很多方法, 包括我们常用的startActivity(), startService(), stopService(), bindService()等等:
- bindService(Intent,ServiceConnection, int):boolean
- deleteFile(String): boolean
- getApplicationContext(): Context
- getApplicationInfo(): ApplicationInfo
- getAssets(): AssetManager,这个是重写了ContextThemeWrapper中的方法
- getCacheDir(): File
- getClassLoader(): ClassLoader
- getDatabasePath(String): File
- getDatabaseDir():File
- getDir(String, int): File
- getFilesDir(): File
- getFileStreamPath(String): File
- getPackageCodePath(): String
- getPackageName():String
- getResources(): Resources
- getSharedPreferences(String, int): SharedPreference
- getSystemService(String): Object,这个是重写了ContextThemeWrapper的方法
- handleCreateView(String, Context, AttributeSet): View
- makeFilename(File, String): File
- openFileInput(String): FileInputStream
- openFileOutput(String, int): FileOutputStream
- startActivity(Intent): void
- startActivity(Intent, Bundle): void
- startService(Intent): ComponentName
- stopService(Intent): boolean
- unbindService(ServiceConnection): void