美文网首页
65.热修复原理

65.热修复原理

作者: SlideException | 来源:发表于2020-08-05 10:37 被阅读0次

/**
*每天一个知识点day65 TODO 热修复原理
*
* https://www.jianshu.com/p/e179fcc97666
* https://www.jianshu.com/p/0399f5e4cdb2
* https://www.jianshu.com/p/cb1f0702d59f
*
* 热修复能完成代码修复、资源修复、so库修复
*
* 热修复技术 百花齐放
* Dexposed 阿里 实时修复 Native hook
* Andfix 阿里 实时修复 Native hook
* 阿里百川Hotfix 阿里 实时修复 Native hook 混合
* Sophix 阿里 实时修复+冷启动修复
* Qzone超级补丁 腾讯 冷启动修复 Java
* QFix 腾讯 冷启动修复 Java
* Robust 美团 实时修复 Java
* Nuwa 大众点评 冷启动修复 Java
* RocooFix 百度 冷启动修复 Java
* Aceso 美丽说蘑菇街 实施修复
* Amigo 饿了么 冷启动修复 Java
* Tinker 微信 冷启动修复 Java
*
*
*
*
* 代码热修复原理主要是类替换,Android中可以动态加载代码的ClassLoader有
* PathClassLoader和DexClassLoader。
*
* 因为PathClassLoader只能加载已经安装到Android系统中的apk文件(data/data目录),
* 是安卓默认使用的类加载器
* 而DexClassLoader在Dalvik和ART虚拟机中可以加载任意目录下的dex/jar/apk/zip文件,
* 但是需要指定一个optimizedDirectory,所以热修复使用DexClassLoader来加载补丁包中的类。
*
* DexClassLoader extends BaseDexClassLoader extends ClassLoader。
*
* PathClassLoader与DexClassLoader都继承于BaseDexClassLoader。
* PathClassLoader与DexClassLoader在构造函数中都调用了父类的构造函数,
* 但DexClassLoader多传了一个optimizedDirectory。
*
* public class BaseDexClassLoader extends ClassLoader {
* ...
* public BaseDexClassLoader(String dexPath,
* File optimizedDirectory,
* String libraryPath,
* ClassLoader parent){
* super(parent);
* this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
* }
* ...
* }
* dexPath:要加载的程序文件所在目录,一般是dex文件,也可以是jar/apk/zip文件。
* optimizedDirectory:dex文件的输出目录
* (因为在加载jar/apk/zip等压缩格式的程序文件时会解压出其中的dex文件,
* 该目录就是专门用于存放这些被解压出来的dex文件的)
* libraryPath:加载程序文件时需要用到的库路径。
* parent:父加载器。
*
* Android 8.0开始,optimizedDirectory过时,不再生效。
*
* 从一个完整App的角度来说,程序文件指定的就是apk包中的classes.dex文件;
* 但从热修复的角度来看,程序文件指的是补丁。
*
* 类加载器肯定会提供有一个方法来供外界找到它所加载到的class,该方法就是findClass()
* BaseDexClassLoader的findClass():
* public class BaseDexClassLoader extends ClassLoader {
* // 需要加载的dex列表
* private final DexPathList pathList;

 *     public BaseDexClassLoader(String dexPath, 
 *                               File optimizedDirectory,
 *                               String libraryPath, 
 *                               ClassLoader parent) {
 *         super(parent);
 *         this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
 *     }
 *     
 *     @Override
 *     protected Class<?> findClass(String name) throws ClassNotFoundException {
 *         List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
 *         // 实质是通过pathList的对象findClass()方法来获取class
 *         Class c = pathList.findClass(name, suppressedExceptions);
 *         if (c == null) {
 *             ClassNotFoundException cnfe = 
 *             new ClassNotFoundException(
 *             "Didn't find class \"" + name + "\" on path: " + pathList);
 *             for (Throwable t : suppressedExceptions) {
 *                 cnfe.addSuppressed(t);
 *             }
 *             throw cnfe;
 *         }
 *         return c;
 *     }
 * }
 * DexPathList在BaseDexClassLoader构造函数中被创建,BaseDexClassLoader的findClass方法是核心。
 * 
 * Element类
 *  static class Element {
 *     private final File file;
 *     private final boolean isDirectory;
 *     private final File zip;
 *     private final DexFile dexFile;
 *
 *     private ZipFile zipFile;
 *     private boolean initialized;
 *
 *     // file文件,是否是目录,zip文件通常都是apk或jar文件,dexFile就是.dex文件
 *     public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
 *         this.file = file;
 *         this.isDirectory = isDirectory;
 *         this.zip = zip;
 *         this.dexFile = dexFile;
 *     }
 * }
 * 
 * DexPathList的构造函数做了什么事?
 * DexPathList的findClass()方法是怎么获取class的?
 * 
 * private final Element[] dexElements;
 * public DexPathList(ClassLoader definingContext, String dexPath,
 *         String libraryPath, File optimizedDirectory) {
 *     ...
 *     this.definingContext = definingContext;
 *     //splitDexPath(dexPath)方法将dexPath目录下的所有程序文件转变成一个File集合。
 *     dexPath是一个用冒号(":")作为分隔符把多个程序文件目录拼接起来的字符串(如:/data/dexdir1:/data/dexdir2:...)。
 *     this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);
 *     ...
 * }
 * 
 * private static Element[] makeDexElements(ArrayList<File> files,
 *                                          File optimizedDirectory,
 *                                          ArrayList<IOException> suppressedExceptions) {
 *     // 1.创建Element集合
 *     ArrayList<Element> elements = new ArrayList<Element>();
 *     // 2.遍历所有dex文件(也可能是jar、apk或zip文件)
 *     for (File file : files) {
 *         ZipFile zip = null;
 *         DexFile dex = null;
 *         String name = file.getName();
 *         ...
 *         // 如果是dex文件
 *         if (name.endsWith(DEX_SUFFIX)) {
 *             dex = loadDexFile(file, optimizedDirectory);
 *
 *         // 如果是apk、jar、zip文件(这部分在不同的Android版本中,处理方式有细微差别)
 *         } else {
 *             zip = file;
 *             dex = loadDexFile(file, optimizedDirectory);
 *         }
 *         ...
 *         // 3.将dex文件或压缩文件包装成Element对象,并添加到Element集合中
 *         if ((zip != null) || (dex != null)) {
 *             elements.add(new Element(file, false, zip, dex));
 *         }
 *     }
 *     // 4.将Element集合转成Element数组返回
 *     return elements.toArray(new Element[elements.size()]);
 * }
 * 
 * 总体来说,DexPathList的构造函数是将一个个的程序文件
 * (可能是dex、apk、jar、zip)封装成一个个Element对象,最后添加到Element集合中。
 * 
 * DexPathList的findClass():
 * 
 * public Class findClass(String name, List<Throwable> suppressed) {
 *     for (Element element : dexElements) {
 *         // 遍历出一个dex文件
 *         DexFile dex = element.dexFile;
 *
 *         if (dex != null) {
 *             // 在dex文件中查找类名与name相同的类
 *             Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
 *             if (clazz != null) {
 *                 return clazz;
 *             }
 *         }
 *     }
 *     if (dexElementsSuppressedExceptions != null) {
 *         suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
 *     }
 *     return null;
 * }
 * 
 * findClass方法是从Element数组中拿出一个个dex文件,再从dex文件中搜索class,
 * 正因为这个特性我们只需要把补丁文件作为Element数组的首个元素,就可以实现动态修复bug了。
 * 
 * 为什么是调用DexFile的loadClassBinaryName()方法来加载class?
 * 这是因为一个Element对象对应一个dex文件,而一个dex文件则包含多个class。
 * 也就是说Element数组中存放的是一个个的dex文件,而不是class文件
 * 这可以从Element这个类的源码和dex文件的内部结构看出。
 *
 * 原理:
 * apk在安装以后,会复制apk到data/app/packageName~1/base.apk
 * apk解压会有patch.dex   classes.dex   classes1.dex  classes2.dex   classes3.dex...
 * 他们都会在dexElement[]数组里面。
 * 
 * 安卓的类加载器在加载一个类时会先从自身DexPathList对象中的Element数组
 * 中获取(Element[] dexElements)到对应的类,之后再加载。
 * 采用的是数组遍历的方式,不过注意,遍历出来的是一个个的dex文件。
 * 在for循环中,首先遍历出来的是dex文件,然后再是从dex文件中获取class,
 * 所以,我们只要让修复好的class打包成一个dex文件,放于Element数组的第一个元素,
 * 这样就能保证获取到的class是最新修复好的class了(当然,有bug的class也是存在的,
 * 不过是放在了Element数组的最后一个元素中,所以没有机会被拿到而已)。
 */
image.png image.png

相关文章

网友评论

      本文标题:65.热修复原理

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