Android插件化框架使用心得 (细节篇)

在尝试 DroidPlugin 插件化的过程中,会遇到一些挺细节的问题,最终大部分情况还是通过阅读框架代码或者源码解决,个人觉得这个也是一个比较好的学习过程,所以将一些细节问题纪录一下。

关于插件Applaciton的问题(资源加载引发的思考)

这个问题,主要和同事讨论,如果避免资源文件冲突的问题,因为部分插件化框架中,通过编译的时的设置,改变资源文件在R文件中的ID值,然后通过反射的方式借助 AssetsManager 来进行资源文件的加载。
于是在 DroidPlugin 源码中全局搜索了 AssetsManager,Resources 这些关键字,发现其实并没有,多少调用点,也并没有多少和资源加载相关的逻辑。结合 DroidPlugin 框架中多进程运行的原理,猜想插件app是运行在自己的application,调用资源时,使用的是自己R文件中ID,所以不会和宿主冲突。之后仔细看了下框架源码,也验证了这个想法。

之前的原理篇中描述了,其实启动Activity关键的两步流程,其中有一步是 handleLaunchActivity 将真实的跳转 Activity 信息替换回 占坑的Activity 过程中,有一步十分关键的流程。

PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);

从这个命名上也可以看出,这是个关于插件进程的管理。这个方法中的主要代码如下。

boolean found = false;
        synchronized (sPluginLoadedApkCache) {
            Object object = ActivityThreadCompat.currentActivityThread();
            if (object != null) {
                Object mPackagesObj = FieldUtils.readField(object, "mPackages");
                Object containsKeyObj = MethodUtils.invokeMethod(mPackagesObj, "containsKey", pluginInfo.packageName);
                if (containsKeyObj instanceof Boolean && !(Boolean) containsKeyObj) {
                    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
                        loadedApk = MethodUtils.invokeMethod(object, "getPackageInfoNoCheck", pluginInfo.applicationInfo, CompatibilityInfoCompat.DEFAULT_COMPATIBILITY_INFO());
                    } else {
                        loadedApk = MethodUtils.invokeMethod(object, "getPackageInfoNoCheck", pluginInfo.applicationInfo);
                    }
                    sPluginLoadedApkCache.put(pluginInfo.packageName, loadedApk);

                /*添加ClassLoader LoadedApk.mClassLoader*/

                    String optimizedDirectory = PluginDirHelper.getPluginDalvikCacheDir(hostContext, pluginInfo.packageName);
                    String libraryPath = PluginDirHelper.getPluginNativeLibraryDir(hostContext, pluginInfo.packageName);
                    String apk = pluginInfo.applicationInfo.publicSourceDir;
                    if (TextUtils.isEmpty(apk)) {
                        pluginInfo.applicationInfo.publicSourceDir = PluginDirHelper.getPluginApkFile(hostContext, pluginInfo.packageName);
                        apk = pluginInfo.applicationInfo.publicSourceDir;
                    }
                    if (apk != null) {
                        ClassLoader classloader = null;
                        try {
                            classloader = new PluginClassLoader(apk, optimizedDirectory, libraryPath, hostContext.getClassLoader().getParent());
                        } catch (Exception e) {
                        }
                        if(classloader==null){
                            PluginDirHelper.cleanOptimizedDirectory(optimizedDirectory);
                            classloader = new PluginClassLoader(apk, optimizedDirectory, libraryPath, hostContext.getClassLoader().getParent());
                        }
                        synchronized (loadedApk) {
                            FieldUtils.writeDeclaredField(loadedApk, "mClassLoader", classloader);
                        }
                        sPluginClassLoaderCache.put(pluginInfo.packageName, classloader);
                        Thread.currentThread().setContextClassLoader(classloader);
                        found = true;
                    }
                    ProcessCompat.setArgV0(pluginInfo.processName);
                }
            }
        }
        if (found) {
            PluginProcessManager.preMakeApplication(hostContext, pluginInfo);
        }

首先通过反射,拿到需要加载的插件 apk 信息

loadedApk = MethodUtils.invokeMethod(object, "getPackageInfoNoCheck", pluginInfo.applicationInfo, CompatibilityInfoCompat.DEFAULT_COMPATIBILITY_INFO());

之后将它存到一个cache中,以备后续的流程使用,

sPluginLoadedApkCache.put(pluginInfo.packageName, loadedApk);

之后从插件信息中取出插件信息,难道classloader信息,也写入到缓存的loadedApk中

FieldUtils.writeDeclaredField(loadedApk, "mClassLoader", classloader);

再之后会调用 preMakeApplication 方法,启动插件的Applicaiton
主要有如下关键的两步,分别是通过包名拿到之前缓存的 loadedApk 信息,然后 通过反射,创建应用

final Object loadedApk = sPluginLoadedApkCache.get(pluginInfo.packageName);
MethodUtils.invokeMethod(loadedApk, "makeApplication", false, ActivityThreadCompat.getInstrumentation());

至此在启动插件 Activity 之前,就已经将插件所在 Applicaiton 启动起来了(当前,前面的流程,有判空操作,不会重复启动),后续会和 宿主 运行在同一个任务中,所以调用资源文件时,也不会有冲突。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值