美文网首页
插件化和热修复对资源和类加载对比分析

插件化和热修复对资源和类加载对比分析

作者: enjoycc97 | 来源:发表于2019-08-09 11:15 被阅读0次

    插件化和热修复对资源和类加载的管理

    1 插件化为什么宿主可以解析插件资源
    2 热修复为什么可以解析补丁资源
    3 插件化为什么宿主可以加载插件代码
    4 热修复为什么可以加载补丁代码

    插件化框架以VirtualApk为例子
    热修复以Tinker为例子

    1 插件化分析加载流程

    插件化加载插件插件时候,在VirtualApk中每一次调用加载插件使用如下方法

    File plugin = new File(pluginPath);
    PluginManager.getInstance(this).loadPlugin(plugin);
    

    拆开来看分为LoadedPlugin对象初始化,然后反射Application的onCreate

    LoadedPlugin plugin = LoadedPlugin.create(this, this.mContext, apk)
    plugin.invokeApplication();
    

    在构造插件LoaderPlugin对象,构造函数初始化很多成员变量,很多类似系统framework做的事情,比如四大组件信息存储。包括apk包解析流程。

            this.mPluginManager = pluginManager;
            this.mHostContext = context;
            this.mLocation = apk.getAbsolutePath();
            this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);
            this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;
            this.mPackageInfo = new PackageInfo();
            this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo;
            this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();
            this.mPackageInfo.versionCode = this.mPackage.mVersionCode;
            this.mPackageInfo.versionName = this.mPackage.mVersionName;
            this.mPackageInfo.permissions = new PermissionInfo[0];
            this.mPackageManager = new PluginPackageManager();
            this.mPluginContext = new PluginContext(this);
            this.mNativeLibDir = context.getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE);
            this.mResources = createResources(context, apk);
            this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());
    

    这里面把很多apk的信息基本挖掘一个遍,
    包括四大组件信息,同时还要关注2个成员变量
    this.mResources = createResources(context, apk);
    this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());

    这也是加载代码和资源的核心所在
    对于资源加载,主要是是反射AssetManager.class的addAssetPath方法然后有了这个assetManager就可以再new 一个Resource了 ,反射Resource对象内部assetsManager对象addAssetPath方法如下ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", apk);

            if (Constants.COMBINE_RESOURCES) {
                Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());
                ResourcesManager.hookResources(context, resources);
                return resources;
            } else {
                Resources hostResources = context.getResources();
                AssetManager assetManager = createAssetManager(context, apk);
                return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
            }
    

    再看类加载器,使用DexClassLoader。如果是合并模式,是直接把插件的dex文件合并到宿主的dexElements数组中去

      File dexOutputDir = context.getDir(Constants.OPTIMIZE_DIR, Context.MODE_PRIVATE);
      String dexOutputPath = dexOutputDir.getAbsolutePath();
      DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);
      if (Constants.COMBINE_CLASSLOADER) {
                try {
                    DexUtil.insertDex(loader);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
      return loader;
    
    public static void insertDex(DexClassLoader dexClassLoader) throws Exception {
            Object baseDexElements = getDexElements(getPathList(getPathClassLoader()));
            Object newDexElements = getDexElements(getPathList(dexClassLoader));
            Object allDexElements = combineArray(baseDexElements, newDexElements);
            Object pathList = getPathList(getPathClassLoader());
            ReflectUtil.setField(pathList.getClass(), pathList, "dexElements", allDexElements);
    
            insertNativeLibrary(dexClassLoader);
    
        }
    

    继续看
    invokeApplication

      this.mApplication = instrumentation.newApplication(this.mClassLoader, appClass, this.getPluginContext());
                instrumentation.callApplicationOnCreate(this.mApplication);
                return this.mApplication;
    

    通过模拟framework加载application,这段代码不是无中生有,在framework代码里面的ActivityThread中performLaunchActivity时候可以找到调用的逻辑。 Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    这里面makeApplication就是懒加载,如果没有加载去new一个,当然也是反射来构造对象

            Application app = null;
            String appClass = mApplicationInfo.className;
            if (forceDefaultAppClass || (appClass == null)) {
                appClass = "android.app.Application";
            }
             java.lang.ClassLoader cl = getClassLoader();
             if (!mPackageName.equals("android")) {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                            "initializeJavaContextClassLoader");
                    initializeJavaContextClassLoader();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                }
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                        cl, appClass, appContext);
                appContext.setOuterContext(app);
                                ...
            instrumentation.callApplicationOnCreate(app);
    
    

    感觉代码几乎一样

    插件化小结:加载插件时候去初始化Resources和ClassLoader对象。反射模拟framework来初始化Application对象,这时候去调用Application的onCreate模拟插件应用启动。同时类加载器也已经设置好,资源文件也设置好,也就可以加载插件的代码和资源文件了

    继续看热修复里面对资源文件和 补丁代码的处理

    热修复针对资源文件的处理

    在Tinker中

     newAssetManager = (AssetManager) findConstructor(assets).newInstance();
    final Resources resources = context.getResources();
                assetsFiled = findField(resources, "mAssets");
     packagesFields = new Field[]{packagesFiled};
            }
    for (Field field : packagesFields) {
            final Object value = field.get(currentActivityThread);
            addAssetPathMethod = findMethod(assets, "addAssetPath", String.class);
            if (((Integer) addAssetPathMethod.invoke(newAssetManager, externalResourceFile)) == 0) {
            }
    
              
    

    getResources()返回的Resources对象内部也是有assetsManager对象,不过关系是Resources中有ResourcesImpl,ResourcesImpl有assetsManager
    对于AssetsManager可以反射addAssetPath,这样getResource()就可以识别指定补丁文件的资源文件

    Tinker加载补丁代码部分
    TinkerDexLoader.loadTinkerJars
    里面针对不同版本适配,以V23为例
    V23.install(classLoader, files, dexOptDir);

    Field pathListField = ShareReflectUtil.findField(loader, "pathList");
    Object dexPathList = pathListField.get(loader);
    ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
    ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList,
                    new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
                    suppressedExceptions));
    

    这里面仍然是反射ClassLoader中makeElements使dexElements数组容纳补丁,这样类加载器也就可以加载对应补丁了

    总而言之:如果是希望当前类加载器能够加载额外的dex文件,是通过反射BaseClassLoader里面pathList的dexElements对象

    如果是加载额外的资源文件,需要反射Resources对象成员变量AssetsManager的addAssetPath方法

    相关文章

      网友评论

          本文标题:插件化和热修复对资源和类加载对比分析

          本文链接:https://www.haomeiwen.com/subject/tdpxjctx.html