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)