AndFix原理淺析(一)之補丁生成原理

zyl409214686發表於2017-12-23

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)
複製程式碼

相關文章