AndFix主要是兩個部分,一個是patch檔案的生成,第二個是打補丁的過程。上一篇AndFix 實戰以及遇到的坑已經進行了相關過程的描述。本篇進行原理分析,為了能夠思路清晰一些,分為兩篇來分別分析這兩個部分。打補丁,首先要有補丁檔案,本篇首先來分析補丁檔案的生成原理。
AndFix patch檔案的生成是由阿里提供的apkpatch工具生成的。下載之後,我們發現apkpatch-1.0.3.jar是一個jar格式的檔案,阿里並沒有對它進行開源。這裡我們可以使用JD-GUI來檢視它的原始碼。
首先我們在com.euler.patch
包下找到程式入口Main.class
的main()
方法。前面都是接收命令列引數等邏輯,直接看最後三行。
public static void main(String[] args) {
//上面主要是接收命令列引數等邏輯,不是重點,此處省略
......
ApkPatch apkPatch = new ApkPatch(from, to, name, out, keystore,
password, alias, entry);
apkPatch.doPatch();
}
複製程式碼
找到com.euler.patch
包下ApkPatch.class
的doPatch()
方法。
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)
複製程式碼