AndFix原则浅析(一)之补丁生成原理

2017/05/17 热修复技术

  AndFix主要是两个部分,一个是patch文件的生成,第二个是打补丁的过程。上一篇AndFix 实战以及遇到的坑已经进行了相关过程的描述。本篇进行原理分析,为了能够思路清晰一些,分为两篇来分别分析这两个部分。打补丁,首先要有补丁文件,本篇首先来分析补丁文件的生成原理。   AndFix patch文件的生成是由阿里提供的apkpatch工具生成的。下载之后,我们发现apkpatch-1.0.3.jar是一个jar格式的文件,阿里并没有对它进行开源。这里我们可以使用JD-GUI来查看它的源码。   首先我们在com.euler.patch包下找到程序入口Main.classmain()方法。前面都是接收命令行参数等逻辑,直接看最后三行。

public static void main(String[] args) {
    //上面主要是接收命令行参数等逻辑,不是重点,此处省略
    ......
    ApkPatch apkPatch = new ApkPatch(from, to, name, out, keystore, 
        password, alias, entry);
      apkPatch.doPatch();  
}

找到com.euler.patch包下ApkPatch.classdoPatch()方法。

public void doPatch() {
    try {
      //创建smali文件夹
      File smaliDir = new File(this.out, "smali");
      if (!smaliDir.exists())
        smaliDir.mkdir();
      try
      {
        //清空smali目录
        FileUtils.cleanDirectory(smaliDir);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
      //new diff.dex文件
      File dexFile = new File(this.out, "diff.dex");
      if ((dexFile.exists()) && (!dexFile.delete())) {
        throw new RuntimeException("diff.dex can't be removed.");
      }
      //new diff.apatch文件
      File outFile = new File(this.out, "diff.apatch");
      if ((outFile.exists()) && (!outFile.delete())) {
        throw new RuntimeException("diff.apatch can't be removed.");
      }
      //比较from新apk和to旧apk差异,写入DiffInfo对象
      DiffInfo info = new DexDiffer().diff(this.from, this.to);
      //将info对象写入smali文件,然后将smali文件打包成dex文件
      this.classes = buildCode(smaliDir, dexFile, info);
      //将生成的dex写入jar包、并根据输入的签名信息进行签名生成diff.apatch。
      build(outFile, dexFile);
      //将对dex 进行md5后生成的字符串对diff.apatch文件重命名
      release(this.out, dexFile, outFile);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

整体流程基本上就是这样。主要是两点: 1、收集新apk与旧apk文件的差异存入diff.dex中。 2、根据差异文件以及签名文件等生成apatch文件

下面重点对新旧apk差异的收集进行分析 com.euler.patch.diff包下DexDiffer类的diff()方法

public DiffInfo diff(File newFile, File oldFile)
    throws IOException
  {
    //加载新apk文件的dex文件
    DexBackedDexFile newDexFile = DexFileFactory.loadDexFile(newFile, 19, 
      true);
    //加载旧apk文件的dex文件
    DexBackedDexFile oldDexFile = DexFileFactory.loadDexFile(oldFile, 19, 
      true);
    //获取DiffInfo对象
    DiffInfo info = DiffInfo.getInstance();
    //标识旧类是否包含新类
    boolean contains = false;
    //遍历新类 
    for (DexBackedClassDef newClazz : newDexFile.getClasses()) {
      Set oldclasses = oldDexFile
        .getClasses();
      //遍历旧类
      for (DexBackedClassDef oldClazz : oldclasses) {
        //新类与旧类类型相同
        if (newClazz.equals(oldClazz)) {
         //比较变量
          compareField(newClazz, oldClazz, info);
         //比较方法
          compareMethod(newClazz, oldClazz, info);
         //标识符为true(旧类包含新类)
          contains = true;
          break;
        }
      }
      //如果包含跳出、开始下一次循环
      if (contains)
        continue;
      //如果不包含将新增类添加到info中
      info.addAddedClasses(newClazz);
    }
    //返回 包含diff信息的info对象
    return info;
  }

这里很好理解,主要就是遍历新旧两个apk 所有的类进行比较,来找到他们之间差异信息。 最后来看com.euler.patch.diff包下的DiffInfo

  public void addAddedFields(DexBackedField field) {
    this.addedFields.add(field);
    throw new RuntimeException("can,t add new Field:" + 
      field.getName() + "(" + field.getType() + "), " + "in class :" + 
      field.getDefiningClass());
  }
  public void addModifiedFields(DexBackedField field) {
    this.modifiedFields.add(field);
    throw new RuntimeException("can,t modified Field:" + 
      field.getName() + "(" + field.getType() + "), " + "in class :" + 
      field.getDefiningClass());
  }

由此而知、AndFix是不支持增加和修改成员变量的。 可以尝试新增成员变量,执行命令报错如下:

java.lang.RuntimeException: can,t add new Field:mTestAddField(Z), in class :Lfamilylibrarymanager/zhao/com/familylibrarymanager/MainActivity;
	at com.euler.patch.diff.DiffInfo.addAddedFields(DiffInfo.java:77)
	at com.euler.patch.diff.DexDiffer.compareField(DexDiffer.java:132)
	at com.euler.patch.diff.DexDiffer.compareField(DexDiffer.java:101)
	at com.euler.patch.diff.DexDiffer.compareField(DexDiffer.java:95)
	at com.euler.patch.diff.DexDiffer.diff(DexDiffer.java:32)
	at com.euler.patch.ApkPatch.doPatch(ApkPatch.java:68)
	at com.euler.patch.Main.main(Main.java:97)

Search

    Table of Contents