美文网首页
33. 热修复-QQ空间超级补丁方案-Android N兼容性

33. 热修复-QQ空间超级补丁方案-Android N兼容性

作者: 任振铭 | 来源:发表于2021-02-10 10:18 被阅读0次

    Android N混合编译

    ART 是在 Android KitKat(Android 4.0)引入并在 Lollipop(Android 5.0)中设为默认运行环境,可以看作Dalvik2.0。 ART模式在Android N(7.0)之前安装APK时会采用AOT(Ahead of time:提前编译、静态编译)预编译为机器码。

    而在Android N使用混合模式的运行时。应用在安装时不做编译,而是运行时解释字节码,同时在JIT编译了一 些代码后将这些代码信息记录至Profile文件,等到设备空闲的时候使用AOT(All-Of-the-Time compilation:全 时段编译)编译生成称为app_image的base.art(类对象映像)文件,这个art文件会在apk启动时自动加载(相当 于缓存)。也就是说这种情况下,当我们的application的onCreate方法执行的时候,可能需要被修复的类已经被加载过了,根据类加载原理,类被加载了无法被替换,即无法修复。

    Tinker团队 Android N混合编译与对热补丁影响解析

    https://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649286341&idx=1&sn=054d595af6e824cbe4edd79427fc2706&scene=0#wechat_redirect

    可以看下tinker对这个问题的描述


    屏幕快照 2021-02-10 上午10.04.23.png
    屏幕快照 2021-02-10 上午10.04.50.png
    屏幕快照 2021-02-10 上午10.05.18.png

    那么既然存在这样的问题,解决的办法是什么?就是运行时替换PathClassLoader方案

    运行时替换PathClassLoader方案

    App image中的class是插入到PathClassloader中的ClassTable中。假设我们完全废弃掉PathClassloader,而 采用一个新建Classloader来加载后续的所有类,即可达到将cache无用化的效果。

    这种方式不会影响没有补丁时的性能,但在加载补丁后,由于废弃了App image带来一定的性能损耗。具体数据如下:


    取自微信技术文档2021-02-10 上午10.11.18.png

    代码实现

    1.新建ClassLoader
            /**
             * 1、先把补丁包的dex拼起来
             */
            // 获得原始的dexPath用于构造classloader
            StringBuilder dexPathBuilder = new StringBuilder();
            String packageName = context.getPackageName();
            boolean isFirstItem = true;
            for (File patch : patchs) {
                //添加:分隔符  /xx/a.dex:/xx/b.dex
                if (isFirstItem) {
                    isFirstItem = false;
                } else {
                    dexPathBuilder.append(File.pathSeparator);
                }
                dexPathBuilder.append(patch.getAbsolutePath());
            }
    
            /**
             * 2、把apk中的dex拼起来
             */
            //得到原本的pathList
            Field pathListField = ShareReflectUtil.findField(oldClassLoader, "pathList");
            Object oldPathList = pathListField.get(oldClassLoader);
    
            //dexElements
            Field dexElementsField = ShareReflectUtil.findField(oldPathList, "dexElements");
            Object[] oldDexElements = (Object[]) dexElementsField.get(oldPathList);
    
            //从Element上得到 dexFile
            Field dexFileField = ShareReflectUtil.findField(oldDexElements[0], "dexFile");
            for (Object oldDexElement : oldDexElements) {
                String dexPath = null;
                DexFile dexFile = (DexFile) dexFileField.get(oldDexElement);
                if (dexFile != null) {
                    dexPath = dexFile.getName();
                }
                if (dexPath == null || dexPath.isEmpty()) {
                    continue;
                }
                if (!dexPath.contains("/" + packageName)) {
                    continue;
                }
                if (isFirstItem) {
                    isFirstItem = false;
                } else {
                    dexPathBuilder.append(File.pathSeparator);
                }
                dexPathBuilder.append(dexPath);
            }
            String combinedDexPath = dexPathBuilder.toString();
    
            /**
             * 3、获取apk中的so加载路径
             */
            //  app的native库(so) 文件目录 用于构造classloader
            Field nativeLibraryDirectoriesField = ShareReflectUtil.findField(oldPathList, "nativeLibraryDirectories");
            List<File> oldNativeLibraryDirectories = (List<File>) nativeLibraryDirectoriesField.get(oldPathList);
            StringBuilder libraryPathBuilder = new StringBuilder();
            isFirstItem = true;
            for (File libDir : oldNativeLibraryDirectories) {
                if (libDir == null) {
                    continue;
                }
                if (isFirstItem) {
                    isFirstItem = false;
                } else {
                    libraryPathBuilder.append(File.pathSeparator);
                }
                libraryPathBuilder.append(libDir.getAbsolutePath());
            }
    
            String combinedLibraryPath = libraryPathBuilder.toString();
    
            //创建自己的类加载器
            ClassLoader result = new EnjoyClassLoader(combinedDexPath, combinedLibraryPath, ClassLoader.getSystemClassLoader());
    
    2.替换ClassLoader
            Thread.currentThread().setContextClassLoader(classLoader);
            Context baseContext = (Context) ShareReflectUtil.findField(app, "mBase").get(app);
            if (Build.VERSION.SDK_INT >= 26) {
                ShareReflectUtil.findField(baseContext, "mClassLoader").set(baseContext, classLoader);
            }
            Object basePackageInfo = ShareReflectUtil.findField(baseContext, "mPackageInfo").get(baseContext);
            ShareReflectUtil.findField(basePackageInfo, "mClassLoader").set(basePackageInfo, classLoader);
            if (Build.VERSION.SDK_INT < 27) {
                Resources res = app.getResources();
                try {
                    ShareReflectUtil.findField(res, "mClassLoader").set(res, classLoader);
    
                    final Object drawableInflater = ShareReflectUtil.findField(res, "mDrawableInflater").get(res);
                    if (drawableInflater != null) {
                        ShareReflectUtil.findField(drawableInflater, "mClassLoader").set(drawableInflater, classLoader);
                    }
                } catch (Throwable ignored) {
                    // Ignored.
                }
            }
    

    相关文章

      网友评论

          本文标题:33. 热修复-QQ空间超级补丁方案-Android N兼容性

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