• android 基于分包方案的修复


    # 本demo实现原理来自

    https://github.com/dodola/HotFix

    https://zhuanlan.zhihu.com/p/20308548

    # Anti类功能,及其原理

       如上图,A,B,C是三个class,它们在生成apk文件时,被打包入同一个dex文件中,当apk发布出去运行一段时间发现A类有个bug,现在使用上面链接中的修复方案修复bug。如文中所说,如果直接将A类重写,并使用运行时动态地将修复后的A类所在的dex文件加载并插入到加载链前,则A便可以修复。但是因为在优化的过程中,如果dex文件中的class都不依赖外部文件,则dex文件内部的class会被打一个CLASS_ISPREVERIFIED标签,这样当B类再使用修复后的A类时,因它在另一个dex文件中,则会报运行时错误。所以在B类中,添加一行代码使得它依赖另外一个dex中的Anti类,这样B就不会被打标签。值得注意的是,apk运行的真实环境中,因并不确定哪一(哪几)个类将来会出现问题,所以为了将来可以修复它(它们),上面的A,B,C类都应该被加上引用Anti的代码;另外,因为Anti所在的dex是在Application类的onCreate方法中被加载,所以Application类的onCreate方法前不该引用到Anti类。

    # anti_dex.jar 和bug_dex.jar的制作

      主要过程:java -> class -> dex

    java -> class, 可以使用ide如AS,或者直接命令行使用jdk提供的编译工具

    javac -source 1.7 -target 1.7 –cp . –d . <MAIN_CLASS>,该命令需要在包的同一级目录下执行,且MAIN_CLASS包含包路径名。

    class -> dex, 可以使用dx工具,形如

    dx --dex --output=classes_dex.jar  <CLASS_PATH>

    # 测试代码编写

    @Override
    public void onCreate() {
        super.onCreate();
        File dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "anti_dex.jar");
        Utils.prepareDex(this.getApplicationContext(), dexPath, "anti_dex.jar");
        HotFix.patch(this, dexPath.getAbsolutePath(), "hotfix.test.com.example.didi.applicationtesthotfix.Anti");
        dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "bug_dex.jar");
        Utils.prepareDex(getApplicationContext(), dexPath, "bug_dex.jar");
        HotFix.patch(getApplicationContext(), dexPath.getAbsolutePath(), "hotfix.test.com.example.didi.applicationtesthotfix.BugClass");
    
        try {
            Class<?> clazz = Class.forName("hotfix.test.com.example.didi.applicationtesthotfix.Anti");
            equalLoader(clazz);
            Log.e("Ruby", "Anti 's classloader' hashCode " + clazz.getClassLoader().hashCode() + "  , " + clazz.getClassLoader().getClass().getName());
            Class<?> cla = Class.forName("hotfix.test.com.example.didi.applicationtesthotfix.BugClass");
            equalLoader(cla);
            Log.e("Ruby", "BugClass 's classloader' hashCode " + cla.getClassLoader().hashCode() + "  , " + cla.getClassLoader().getClass().getName());
        } catch (Exception e) {
        }
    }

    # 剔除Anti的编译脚本

       因为Anti.class不能被使用AS一起打包入dex文件中,所以,在编译后打包工程的class文件成dex时,需要先将Anti.class删除,然后借助AS已近编译后的class文件和打包后的资源文件包自己命令行打包成apk文件。

    # 打包class文件生成dex 文件
    dx=/Users/didi/Library/Android/sdk/build-tools/23.0.3/dx classes=build/intermediates/classes/release/
    $dx --dex --output=classes.dex   $classes
    
    # 打包res/ assets/ 文件成 resources-release.ap_ 文件 (AS已完成)
    
    # 打包 resources-release.ap_ 和 classes.dex文件成 Hotfix-unsign.apk
    sdklib=/Users/didi/Library/Android/sdk/tools/lib/sdklib.jar
    apk=com.android.sdklib.build.ApkBuilderMain
    app/build/intermediates/res
    resources=build/intermediates/res/resources-release.ap_
    java -classpath $sdklib $apk  Hotfix-unsign.apk  -u -z  $resources  -f classes.dex
    
    # 签名
    keystore=/Users/didi/Documents/DidiChuxing/driver/lulei.keystore
    alia=lulei
    psw=demodebug
    jarsigner=/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/jarsigner
    $jarsigner -digestalg SHA1 -sigalg MD5withRSA -keystore  $keystore  -storepass $psw -keypass $psw -signedjar  Hotfix-sign.apk Hotfix-unsign.apk  $alia

    # 运行时,动态热修复不行的原因

    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
    
        Class<?> clazz = findLoadedClass(className);
    
        if (clazz == null) {
            ClassNotFoundException suppressed = null;
            try {
                clazz = parent.loadClass(className, false);
            } catch (ClassNotFoundException e) {
                suppressed = e;
            }
            if (clazz == null) {
                try {
                    clazz = findClass(className);
                } catch (ClassNotFoundException e) {
                    e.addSuppressed(suppressed);
                    throw e;
                }
            }
        }
        return clazz;
    }

      从上面的ClassLoader源码中,可以看到,查找一个类时,先查看内存中该类是否加载,所以如果在加载修复后的类文件A前,已经使用了A类,则该方案就失败,所以该方案要在Application的onCreate方法中将A所在的dex预先load。

    # “字节码修改”和 “编译依赖”方案的区别

       原则上没有任何区别!

    # google 分包方案,是什么鬼,分包策略是什么?

       I  still don’t  know!

    #  华为i7-ATH-AL00 android版本5.1.1 为什么不插入Anti代码也可以?

       What? Fuck!!!

    #  反思

      虽然使用了自定义的DexClassLoader将修复后的class文件从文件中重新加载到内存中了,但是因为方案中是将DexClassLoader(的父类)的DexFile通过反射赋值给了PathClassLoader(的父类BaseClassLoader 的pathLists成员),所以查看修复后类的classLoader,仍然显示为系统的pathClassLoader。

    demo 代码请参考 github地址

  • 相关阅读:
    如何使得事务使用同一个连接对象Connection呢?
    快速使用上咱的ideal的快捷键小技巧
    委托模式的理解:
    克隆、深拷贝与浅拷贝区别
    mysql存储过程与事务
    sql异常处理以及sql异常处理优先级
    Mysql 遇到神奇的“本次本客户端效现象”,数据库并未被改变 + 神奇“卡顿现象”
    网络传输时既有管道流(PipedInputStream 与 PipedOutStream)又有序列化对象、反序列化对象(ObjectOutputStream与 ObjectInputStream),还有在集合中、流中都有的身影的Properties究竟是何方神物?我们该怎么选择呢?
    数据库设计的三大范式
    实体类(VO,DO,DTO,PO)的划分
  • 原文地址:https://www.cnblogs.com/LuLei1990/p/5647500.html
Copyright © 2020-2023  润新知