定义差分插件
- 1.在根目录下建议buildSrc目录,并新建build.gradle文件,sync,会自动生成.gradle目录和build目录,build.gradle文件内容如下,主意 apply plugin会识别buildSrc中的脚本,并生成对应的插件,比如apply plugin : "groovy",我们新建的文件BsDiffPlugin.groovy才能生效
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.2'
}
}
//识别groovy 插件
apply plugin : "groovy"
apply plugin: "java"
repositories {
mavenCentral()
}
dependencies {
}
- 2.新建groovy目录,定义BsDiffPlugin.groovy
plugin与task成对出现,plugin负责构建出task和对task进行参数的初始化,task负责真正的任务执行。
class BsDiffPlugin implements Plugin<Project>{
@Override
void apply(Project project) {
project.afterEvaluate {
//只是定义任务,不会执行,执行还需要手动 ./gradlew bsDiff
project.task(type:BsDiffTask , "bsDiffWrapper"){
inputs.files "${project.rootDir.absolutePath}/temp/testold.txt","${project.rootDir.absolutePath}/temp/testnew.txt"
outputs.files "${project.rootDir.absolutePath}/temp/testdiff.patch"
}
}
}
}
当我们在app下的build.gralde中增加
apply plugin: BsDiffPlugin
在gradle的配置阶段,BsDiffPlugin的apply方法会被执行,当其中定义的任务BsDiffTask并不会执行,它仅仅是被初始化出来了
project.task(type:BsDiffTask , "bsDiffWrapper") 第二个参数会修改任务的名称
- 3.执行任务
执行任务有两种方式,可以通过
./gradlew tasks
列出任务列表,然后执行
./gradlew bsDiffWrapper
或者勾住某个默认构建任务执行
project.getTasksByName("assembleDebug",false).getAt(0).finalizedBy 'bsDiffWrapper'
project.getTasksByName("bsDiffWrapper",false).getAt(0).finalizedBy 'bsPatchWrapper'
给插件定义id
-
1.在buildSrc中的src/main下新建目录resources/META_INF/gradle-plugins
-
2.新建两个properties文件,名字就是我们要依赖的id com.hch.plugin.bsDiff.properties com.hch.plugin.bsPatch.properties
-
3.在文件中增加一行表示插件的实现类路径
implementation-class=com.hch.plugin.BsPatchPlugin
- 4.在需要依赖的gradle中采用id依赖,如app下的build.gradle,主意plugins必须在文件的最开始,前面不能加其它的脚本
plugins{
id 'com.hch.plugin.bsDiff'
id 'com.hch.plugin.bsPatch'
id 'com.android.application'
}
dex 文件格式分析
格式查看工具 010Editor
方法数不能超过65535的原因
在dex中对方法指向的类描述id长度为ushort,长度为65535,因此超过65535个类会提示 too much classes.
bsDiff/dexDiff比对算法的缺陷与改进
查看tinker比对算法的方法,依赖tinker的patch插件,拷贝出来实现类
compileOnly "com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1"
dex的结构
dex 是结构化的数据,包含header , map , data section 。
header中包含:
- magic string[dex.035.],
- checksum 校验数据
- 其它一些区段的类型和长度描述,如string,id,prototype,method,class等的初始位置,长度。其中string,id,prototype,method,class每个结构又是一个结构化数据
maplist 相比于 header更加全面的描述了dex的整个分块信息
datasection 实际的各个secion的内容信息
dex 的算法实现
因此对比算法的基本流程可以确定为:
- dex file 到dex内存对象的读取转化
- 对dex中15种数据结构的结构化对比,大致实现流程为,读取每个结构化列表的item并排序,产生oldList和newList,循环list的cursor 分别根据当前的cursor,获取oldItem和newItem,对其value对对比:
-
- 如果<0 ,则认为该old Item被删除了,记录为PatchOperation.OP_DEL,并记录该oldItem index到PatchOperation对象,加入到patchOperationList中。
-
- 如果>0,则认为该newItem是新增的,记录为PatchOperation.OP_ADD,并记录该newItem index和value到PatchOperation对象,加入到patchOperationList中。
-
- 如果=0,不会生成PatchOperation。
- 得到对比结果 map ,oldDex中要删除的item,newDex中要新增的item,以及要替换的item
// 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 {
}
}
dex diff的缺陷
双重循环遍历这里,可以使用数组+链表来存放 根据item的hashcode生成key值(int),通过比对key值是否相等,以o(1)的复杂度得到对比结果