本帖最后由 小篱 于 2015-10-30 11:46 编辑

  背景

  当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就会忙得焦头烂额:重新打包App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。有时候仅仅是为了修改了一行代码,也要付出巨大的成本进行换包和重新发布。

  这时候就提出一个问题:有没有办法以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装?

  解决方案

  该方案基于的是android dex分包方案的,关于dex分包方案,网上有几篇解释了,所以这里就不再赘述,具体可以看这里https://m.oschina.net/blog/308583

  简单的概括一下,就是把多个dex文件塞入到app的classloader之中,但是android dex拆包方案中的类是没有重复的,如果classes.dex和classes1.dex中有重复的类,当用到这个重复的类的时候,系统会选择哪个类进行加载呢?

  让我们来看看类加载的代码:

 

 

  1. public Class findClass(String name, List<Throwable> suppressed) {
  2.  
  3. for (Element element : dexElements) {//每个Element就是一个dex文件
  4.  
  5. DexFile dex = element.dexFile;
  6.  
  7. if (dex != null) {
  8.  
  9. Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
  10.  
  11. if (clazz != null) {
  12.  
  13. return clazz;
  14.  
  15. }
  16.  
  17. }
  18.  
  19. }
  20.  
  21. if (dexElementsSuppressedExceptions != null) {
  22.  
  23. suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
  24.  
  25. }
  26.  
  27. return null;
  28.  
  29. }

  从代码上来看,如果两个相关联的类在不同的dex中就会报错,但是拆分dex没有报错这是为什么,原来这个校验的前提是:

113332sx7boubujdduddm2.png
  这段代码是dex转化成odex(dexopt)的代码中的一段,我们知道当一个apk在安装的时候,apk中的classes.dex会被虚拟机(dexopt)优化成odex文件,然后才会拿去执行。

  虚拟机在启动的时候,会有许多的启动参数,其中一项就是verify选项,当verify选项被打开的时候,上面doVerify变量为true,那么就会执行dvmVerifyClass进行类的校验,如果dvmVerifyClass校验类成功,那么这个类会被打上CLASS_ISPREVERIFIED的标志,那么具体的校验过程是什么样子的呢?

  此代码在DexVerify.cpp中,如下:

113333fmcmlw36vlz6mlfv.png
  1. 验证clazz->directMethods方法,directMethods包含了以下方法:

   1. static方法

  ?2. private方法

  ?3. 构造函数

  2.?clazz->virtualMethods

  1. 虚函数=override方

  概括一下就是如果以上方法中直接引用到的类(第一层级关系,不会进行递归搜索)和clazz都在同一个dex中的话,那么这个类就会被打上CLASS_ISPREVERIFIED标志

113340zhraxa85aau8kxhq.png
  所以为了实现补丁方案,所以必须从这些方法中入手,防止类被打上CLASS_ISPREVERIFIED标志。

  最终空间的方案是往所有类的构造函数里面插入了一段代码,代码如下:

 

 

  1. if (ClassVerifier.PREVENT_VERIFY) {
    • System.out.println(AntilazyLoad.class);
      • }

  其中AntilazyLoad类会被打包成单独的hack.dex,这样当安装apk的时候,classes.dex内的类都会引用一个在不相同dex中的AntilazyLoad类,这样就防止了类被打上CLASS_ISPREVERIFIED的标志了。只要没被打上这个标志的类都可以进行打补丁操作。

  然后在应用启动的时候加载进来.AntilazyLoad类所在的dex包必须被先加载进来,不然AntilazyLoad类会被标记为不存在,即使后续加载了hack.dex包,那么他也是不存在的,这样屏幕就会出现茫茫多的类AntilazyLoad找不到的log。

  所以Application作为应用的入口不能插入这段代码。(因为载入hack.dex的代码是在Application中onCreate中执行的,如果在Application的构造函数里面插入了这段代码,那么就是在hack.dex加载之前就使用该类,该类一次找不到,会被永远的打上找不到的标志)

  其中:

 

 

  1. class ClassVerifier {
    • public static boolean PREVENT_VERIFY = false;//false防止代码被执行,提高性能
      • }
复制代码
  之所以选择构造函数是因为他不增加方法数,一个类即使没有显式的构造函数,也会有一个隐式的默认构造函数。

  空间使用的是在字节码插入代码,而不是源代码插入,使用的是javaassist库来进行字节码插入的。

  隐患:

  虚拟机在安装期间为类打上CLASS_ISPREVERIFIED标志是为了提高性能的,我们强制防止类被打上标志是否会影响性能?这里我们会做一下更加详细的性能测试。

  但是在大项目中拆分dex的问题已经比较严重,很多类都没有被打上这个标志。

  如何打包补丁包:

  1.空间在正式版本发布的时候,会生成一份缓存文件,里面记录了所有class文件的md5.还有一份mapping混淆文件。

  2.在后续的版本中使用-applymapping选项,应用正式版本的mapping文件,然后计算编译完成后的class文件的md5和正式版本进行比较,把不相同的class文件打包成补丁包。

  备注:该方案现在也应用到我们的编译过程当中,编译不需要重新打包dex,只需要把修改过的类的class文件打包成patch dex,然后放到sdcard下,那么就会让改变的代码生效。

腾讯Bugly 投稿
锐亚教育

锐亚教育,游戏开发论坛|游戏制作人|游戏策划|游戏开发|独立游戏|游戏产业|游戏研发|游戏运营| unity|unity3d|unity3d官网|unity3d 教程|金融帝国3|8k8k8k|mcafee8.5i|游戏蛮牛|蛮牛 unity|蛮牛