Dex文件分析
- 1)编写代码生成dex
首先我们编写一个类Hello.java:
public class Hello{
public static void main(String[] args){
System.out.println("hello dex!");
}
}
然后进行编译:
javac -source 1.7 -target 1.7 Hello.java
最后通过dx工作将其转化为dex文件:
dx --dex --output=Hello.dex Hello.class
dx路径在android-sdk/build-tools/版本号/dx下,如果无法识别dx命令,记得将该路径放到path下,或者使用绝对路径。
- 分析dex文件
使用010Editer 打开dex文件
1.magic能够证明该文件是dex文件
2.checksum和signature主要用于校验文件的完整性
3.file_size为dex文件的大小
4.head_size为头文件的大小
5.endian_tag预设值为12345678,标识默认采用Little-Endian。
大端模式(Big-Endian):低地址存放高位。符号位的判定固定为第一个字节,容易判断正负。
小端模式(Little-Endian):低地址存放地位。强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样。
将0x12345678 存到0x4000 0000地址下
小端:hex: 87654321 (地址从左到右:低位-->高位 0x12345678从左到右:高位---> 地位)
大端:hex:12345678
start:存储的字节位置 size:存储的字节大小
int型:在地址中存储4个字节。
dex 文件格式分析
下面开始进入正题:
- 1.生成两个新旧dex
Hello.dex
public class Hello {
public static void main(String[] args) {
System.out.println("hello world");
}
}
这里只分析string_id_list,这里的list已经是根据ASCII表进行排序过的
World.dex
public class World{
public static void main(String[] args){
System.out.println("nani World");
}
}
- 打出差分包
public class DexDiffTest {
@Test
public void testDiff() throws IOException {
File oldFile = new File("Hello.dex");
File newFile = new File("World.dex");
File patchFile = new File("patch.dex");
//差分包的入口
DexPatchGenerator dexPatchGenerator
= new DexPatchGenerator(oldFile, newFile);
dexPatchGenerator.executeAndSaveTo(patchFile);
}
}
在tinker-build的tinker-patch-lib下,创建测试代码
最终生成patch.dex文件
- 3.生成代码的核心
public void execute() {
this.patchOperationList.clear();
// 1. 拿到oldDex和newDex的itemList
this.adjustedOldIndexedItemsWithOrigOrder = collectSectionItems(this.oldDex, true);
this.oldItemCount = this.adjustedOldIndexedItemsWithOrigOrder.length;
AbstractMap.SimpleEntry<Integer, T>[] adjustedOldIndexedItems = new AbstractMap.SimpleEntry[this.oldItemCount];
System.arraycopy(this.adjustedOldIndexedItemsWithOrigOrder, 0, adjustedOldIndexedItems, 0, this.oldItemCount);
Arrays.sort(adjustedOldIndexedItems, this.comparatorForItemDiff);
AbstractMap.SimpleEntry<Integer, T>[] adjustedNewIndexedItems = collectSectionItems(this.newDex, false);
this.newItemCount = adjustedNewIndexedItems.length;
Arrays.sort(adjustedNewIndexedItems, this.comparatorForItemDiff);
int oldCursor = 0;
int newCursor = 0;
// 2.遍历,对比,收集patch操作
while (oldCursor < this.oldItemCount || newCursor < this.newItemCount) {
if (oldCursor >= this.oldItemCount) {
// rest item are all newItem.
while (newCursor < this.newItemCount) {
// 对剩下的newItem做ADD操作
}
} else if (newCursor >= newItemCount) {
// rest item are all oldItem.
while (oldCursor < oldItemCount) {
// 对剩下的oldItem做DEL操作
}
} else {
AbstractMap.SimpleEntry<Integer, T> oldIndexedItem = adjustedOldIndexedItems[oldCursor];
AbstractMap.SimpleEntry<Integer, T> newIndexedItem = adjustedNewIndexedItems[newCursor];
int cmpRes = oldIndexedItem.getValue().compareTo(newIndexedItem.getValue());
if (cmpRes < 0) {
int deletedIndex = oldIndexedItem.getKey();
int deletedOffset = getItemOffsetOrIndex(deletedIndex, oldIndexedItem.getValue());
this.patchOperationList.add(new PatchOperation<T>(PatchOperation.OP_DEL, deletedIndex));
markDeletedIndexOrOffset(this.oldToPatchedIndexMap, deletedIndex, deletedOffset);
++oldCursor;
} else if (cmpRes > 0) {
this.patchOperationList.add(new PatchOperation<>(PatchOperation.OP_ADD,
newIndexedItem.getKey(), newIndexedItem.getValue()));
++newCursor;
} else {
int oldIndex = oldIndexedItem.getKey();
int newIndex = newIndexedItem.getKey();
int oldOffset = getItemOffsetOrIndex(oldIndexedItem.getKey(), oldIndexedItem.getValue());
int newOffset = getItemOffsetOrIndex(newIndexedItem.getKey(), newIndexedItem.getValue());
if (oldIndex != newIndex) {
this.oldIndexToNewIndexMap.put(oldIndex, newIndex);
}
if (oldOffset != newOffset) {
this.oldOffsetToNewOffsetMap.put(oldOffset, newOffset);
}
++oldCursor;
++newCursor;
}
}
}
// 未完
}
这里很熟悉的一个算法,针对两个有序的列表进行比对,这里使用的一个归并算法,没有分,只有治。compareTo字符串比对,因为之前的list已经是一个有序的了,所以可以直接比对执行。
- 4.对生成的patchOperationList进行整理,相同的id的del add 替换为replace,然后合并相同字段的del,add,replace
获取oldDex中StringData区域的Item,并进行排序
获取newDex中StringData区域的Item,并进行排序
然后对ITEM依次比较
<0
说明从老的dex中删除了该String,patchOperationList中添加Del操作
>0
说明添加了该String,patchOperationList添加add操作
=0
说明都有该String, 记录oldIndexToNewIndexMap,oldOffsetToNewOffsetMap
old item已到结尾
剩下的item说明都是新增项,patchOperationList添加add操作
new item已到结尾
剩下的item说明都是删除项,patchOperationList添加del操作
最后对对patchOperationList进行优化(
{OP_DEL idx} followed by {OP_ADD the_same_idx newItem} will be replaced by {OP_REPLACE idx newItem})
参考:
tinker dexdiff
Android 热修复 Tinker 源码分析之DexDiff / DexPatch
tinker 热更新分析