zoukankan      html  css  js  c++  java
  • 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地址

  • 相关阅读:
    51nod 1087 1 10 100 1000(找规律+递推+stl)
    51nod 1082 与7无关的数 (打表预处理)
    51 nod 1080 两个数的平方和
    1015 水仙花数(水题)
    51 nod 1003 阶乘后面0的数量
    51nod 1002 数塔取数问题
    51 nod 1001 数组中和等于K的数对
    51 nod 1081 子段求和
    51nod 1134 最长递增子序列 (O(nlogn)算法)
    51nod 1174 区间中最大的数(RMQ)
  • 原文地址:https://www.cnblogs.com/LuLei1990/p/5647500.html
Copyright © 2011-2022 走看看