Android开发经验谈Android高级技术

性能优化——APP 稳定性之热修复原理探索

2019-06-17  本文已影响11人  df556ada620a

作者:DevYK
链接:https://juejin.im/post/5cfce989f265da1b6c5f6991

先扔效果图

热修复的背景

..等等, 这里只是拿几个常见的举例说明。

热修复的效率

热修复框架对比

框架名称 所属公司 是否开源 修复方式
Dexposed alibaba 开源 实时修复
Andfix alibaba 开源 实时修复
Hotfix alibaba 暂未开源 实时修复
Qzone 超级补丁 QQ 空间 暂未开源 冷启动修复
QFix 手 Q 团队 开源 冷启动修复
Robust 美团 开源 实时修复
Nuwa 大众点评 开源 冷启动修复
RocooFix 百度金融 开源 冷启动修复
Aceso 美丽说蘑菇街 开源 实时修复
Amigo 饿了么 开源 冷启动修复
Tinker 微信 开源 冷启动修复
Sophix alibaba 未开源 实时修复 + 冷启动修复

代码修复(今日主题 - 类加载方式)

底层替换方式

ClassLoader 类加载方式

实现自己的热修复框架

Dex 分包

65536 限制

com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536

当应用程序报 65536 错误的根本原因是,应用的方法数量超过了最大数 65536 个,因为 DVM Bytecode 的限制, DVM 指令集的方法调用指令 invoke-kind 索引为 16 bits, 最多能引用 65535 个方法

LinearAlloc 限制

INSTALL_FAILED_DEXOPT

在安装应用时可能会提示 上面的错误,产生的原因是 LinearAlloc 限制。 DVM 中的 LinearAlloc 是一个固定的缓存区,当方法数超出缓存区的大小时会报错。

解决

为了解决 65536 限制和 LinearAlloc 限制,从而产生了 Dex 分包机制。 Dex 分包方案主要做的时在打包时将应用代码分成多个 Dex,将应用启动时必须用到的类和这些类的直接引用类放到主 Dex 中,其它代码放到次 Dex 中。当应用启动时先加载主 Dex,等到应用启动后再动态地加载次Dex,从而缓解了主 Dex 的 65536 限制和 LinearAlloc 限制

什么是插桩?

源码:

/**遍历需要找到需要加载的 class */ 
public Class findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;

            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

插桩原理:

通过源码得知 findClass 是通过遍历 dexElements 来找到 class, 如果我们反射得到 DexPathList 的私有数组 dexElements,我们外部改变这个数组内部顺序索引,将修复好的 dex 放入 [0] 的位置,那么是不是能够优先使用修复好的 dex 勒? 很明显,是成立的。下面开始撸代码吧。

代码实现

  1. 接收来至服务器发来的补丁包,如果修复包已经存在则删除,copy 到私有目录防止用户不小心删除。

    /**这里模拟已经下载好的 dex 补丁包*/    
    private void downloadPatch() {
            //1 从服务器下载dex文件 比如v1.1修复包文件(classes2.dex)
            File sourceFile = new File(Environment.getExternalStorageDirectory(), "classes2.dex");
            // 目标路径:私有目录
            //getDir("odex", Context.MODE_PRIVATE) data/user/0/包名/app_odex
            File targetFile = new File(getDir("hotfix",
                    Context.MODE_PRIVATE).getAbsolutePath() + File.separator + "classes2.dex");
            if (targetFile.exists()) {
                targetFile.delete();
            }
            try {
                // 复制dex到私有目录
                FileUtils.copyFile(sourceFile, targetFile);
                Toast.makeText(this, "Bug 修复成功!", Toast.LENGTH_SHORT).show();
                FixDexUtils.loadFixedDex(this);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    
  2. 创建修复包的类加载器 DexClassLoader (通过源码得知是继承的 BaseDexClassLoader)

        /**
         * 创建类加载器
         *
         * @param context
         * @param fileDir
         */
        private static void createDexClassLoader(Context context, File fileDir) {
            String optimizedDirectory = fileDir.getAbsolutePath() + File.separator + "opt_dex";
            File fOpt = new File(optimizedDirectory);
            if (!fOpt.exists()) {
                fOpt.mkdirs();
            }
            DexClassLoader classLoader;
            for (File dex : loadedDex) {
                //初始化类加载器
                classLoader = new DexClassLoader(dex.getAbsolutePath(), optimizedDirectory, null,
                        context.getClassLoader());
                //热修复
                hotFix(classLoader, context);
            }
        }
    
    
  3. 获取系统的 PathClassLoader

    PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
    
    
  4. 获取修复包的 dexElements

    Object pathList = ReflectUtils.reflect(myClassLoader).field("pathList").get();
    Object myDexElements = ReflectUtils.reflect(pathList).field("dexElements").get();
    
    
  5. 获取系统的 dexElements

    Object sysPathList = ReflectUtils.reflect(pathClassLoader).field("pathList").get();
    Object sysDexElements = ReflectUtils.reflect(sysPathList).field("dexElements").get();
    
    
  6. 将系统的 dexElements 和 修复包的 dexElements merge 成新的 dexElements

    // 合并,这里利用插桩原理进行合并数组,将修复好的 class2.dex 放入第一位,优先加入就行了
    Object dexElements = ArrayUtils.combineArray(myDexElements, sysDexElements);
    
    
  7. 重新赋值给 DexPathList 的 dexElements 属性

    //重新赋值
    ReflectUtils.reflect(sysPathList).field("dexElements", dexElements);
    
    

热修复未来发展

  1. 热修复 = “黑科技”?

    • 热修复不同于国内 APP 进程保活这种 “黑科技”,让 app 常驻后台,既耗电又占用内存,浪费很多手机资源。还有 APP 的推送服务,无节操地对用户进行信息轰炸。还有更无节操的全家桶 app。导致 Android手机卡顿不堪,这些所谓的 “黑科技” 都是为了手机厂商的利益而损害用户的体验。

    • 而热修复是能够让开发者和用户双赢的。不仅厂商能快速迭代更新 app,使功能尽快上线,而且热更新过程用户无感知,节省大量更新时间,提高用户体验。更重要的能保证 app 的功能稳定,bug 能及时修复。

  2. IOS 封杀了热修复功能,Android 的热修复也会被 pass 掉吗?

    • google 和 apple 公司在中国的 diwei 不一样

    • Android 和 IOS 的开放性不同

  3. 热修复未来发展前景是很乐观的。

热修复进阶学习视频链接https://pan.baidu.com/s/1pXCwbVhxIN873Mk79JJ8BA
提取码:bxwz

Android高级系列视频教程

深度对接腾讯T3高级工程师级别的技术体系,并且综合了目前的各大互联网公司如华为,抖音,OPPO,阿里等主流技术(即使你不想选择腾讯,其它的大厂照样适合)

腾讯T3级别 Android高级技术视频教程.png
免费学习可以加入QQ群Android架构师预备营,群内提供高级进阶免费学习课程和视频等资源;964557053 点击链接加入群聊
注意,以下几类读者请千万不要加入本群:

1.不愿意潜心学习,幻想只靠短期学习几个星期就可以月薪30K+,就可以进大厂的人。
2.认为我在打广告骗大家去学习高级进阶技术从而影响转行发展打算的人
3.不相信努力可以改变命运的人。

上一篇 下一篇

猜你喜欢

热点阅读