安卓代码修复框架Patch
只支持程序重启修复,不支持热修复
markdown代码原理
-
1.主要通过反正把原来dexElements数组替换成我们新的dexElements数组,我们新的dexElements数组主要包含了我们修复的代码类,安卓加载dex里面的类有个特点就是优先被加载到虚拟机的类,如果后面出现相同签名的类文件将不进行处理,相当于先到先得。
-
2.于是我们利用了它的这个特点只要实现我们修改好的类比我们出现bug的类先给虚拟机加载就可以了。
-
3.但是对于多dex还有一个特点就是android5.0开始开启了ISPRIVILEAGE校验,啥意思,就是如果一个dex里的类没有依赖于其他dex文件的类,那就会打上这个标签,一旦打上这个标签,其他dex文件是不能访问的,那对于我们代码修复就有问题了,如果我们要修复的类是没有依赖于其他类的,但是我们修复的时候把他打到第一个dex文件中,那就会出现问题,为了避免这个问题,我们最好把所有的类都去掉这个标签。怎么去掉,很简单,让所有的类都存在同一个类的应用,然后无论有多少个dex都必定引用我们第一个dex的这个类,这样就能有效的去除这个标签的验证。
-
4.如何给每个类添加一个类的应用,我们选择用gradle插件,在dex任务开始之前,在dex任务获取所有可用依赖任务之后,进行代码插入,
为什么要在这个时候呢,因为这个过程中class文件会被转化成dex文件,这是class最后存在的时机,也相当于最后形态,不会出现任何修改class的行为了,所以在这里对class进行处理是最好的 -
5.具体就是用ASM对每个类的构造函数注入一个我们Hack类的应用,类似于
Class hack=Hack.class;
这样的格式,我们可以利用Android studio插件show bytecode outline显示出ASM代码,具体代码为mv.visitLdcInsn(Type.getType("Lcom/example/administrator/patchdemo/Hack;"));
这样就能在每个类的构造函数注入我们第一个dex文件的一个Hack类的引用 -
6.然后我们会为每个需要打包的类计算它的hash值,因为每个类的hash值都是唯一的,那么我们每次修改了一个类,对比上一个hash文件,就能知道哪些类发生了修改,那我们就知道要把哪些文件打入补丁包里面,这个原理,在美团的robust热修复框架也有对应的应用
代码实现
1.自定义生成补丁的gradle 插件
自定义gradle插件,网上有很多文章,我这边用的是独立的库方式。
具体步骤
1.新建一个module我这边选择的是 javalibrary
image.png
然后生成这样的目录
我们在
java
同级目录新建一个目录叫做 groovy
image.png
然后我们还需要做的是新建一个resources目录,同样在
java
同级目录image.png
然后我们开始我们插件开发
- 1.新建一个groovy文件
当然我们可以增加我们的包目录路径,类似我这边的com.bigman.tech
,然后我们新建一个类叫做PatchPlugin
image.png
当然这个文件是以.groovy
结尾的
然后我们开始开发我们的插件
class PatchPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
}
}
代码实现如上
- 2.下一步我们要声明一下我们的插件名称
我们找到刚刚新建的resources
目录,我们继续新建两级目录,和一个文件
image.png
这两级目录名称固定不能变,然后是我们的properties
文件,文件名就是我们以后用到app里面的名字,比如我们这里到时候会这么用apply plugin: 'com.bigman.tech'
,这里的名字决定了我们使用时候的名字
然后我们看一下这个文件里面有啥
implementation-class=com.bigman.tech.PatchPlugin
主要就是我们刚新建的groovy文件里面类的全路径名称,这里千万不能写错
- 3.第三步我们要配置一个发布插件的脚本
找到我们刚新建的java library
里面的build.gradle
我们改成这样
//应用groovy插件
apply plugin: 'groovy'
//要发布插件,必须应用这个maven插件,当然还有其他第三方插件
apply plugin: 'maven'
repositories {
jcenter()
mavenCentral()
}
dependencies {
compile gradleApi()
compile 'com.android.tools.build:gradle:1.5.0'
compile 'commons-io:commons-io:2.4'
compile 'commons-codec:commons-codec:1.11'
compile 'org.ow2.asm:asm-all:5.0.3'
}
sourceCompatibility = "7"
targetCompatibility = "7"
group="com.bigman"
archivesBaseName='patch-gradle'
version ='1.0.0'
uploadArchives {
repositories {
mavenDeployer {
repository(url: uri('../repo'))
}
}
}
主要是这三个配置
group="com.bigman"
archivesBaseName='patch-gradle'
version ='1.0.0'
平时我们应用依赖的时候就是通过group+archivesBaseName+version来进行寻找的
看一下我们怎么使用这个插件
首先我们找到项目根目录下面的build.gradle
文件,然后我们在dependencies
加入一个classpath
buildscript {
repositories {
google()
jcenter()
maven { url uri('repo') }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.bigman:patch-gradle:1.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
值得注意的还有一个地方,就是我们的repositories
我们还加了一个maven { url uri('repo') }
这个是让我们脚本去我们本地的repo
目录去需要我们的依赖库,比如我们这个插件
但这个具体是怎么配置到本地maven库目录的,就是我们上面插件build.gradle
文件里面有一个uploadArchives
任务,这是maven插件提供的一个上传库的任务
uploadArchives {
repositories {
mavenDeployer {
repository(url: uri('../repo'))
}
}
}
我们配置了让库上传到上级目录的一个repo
目录下面,然后我们找到我们AS编辑器右边有一个gradle图标打开找到我们的 插件模块,比如我这边的plugin
模块里面有一个upload
任务
双击之后我们就可以在项目目录下面找到一个文件夹,里面就是我们刚上传的插件库
image.png
现在算是初步完成了自定义插件,我们可以试试有没有用
- 4.测试 可以先在我们项目根目录依赖我们的插件,就像上面说的那样,然后找到我们的主module的
build.gradle
应用一下我们的插件就行,apply plugin: 'com.bigman.tech'
,然后构建一下你的app项目,结果发现没啥反应,其实没啥反应是最好的反应。如果报错了,我们还要去调试,调试插件我的另一篇文章里有说,可以具体看看。
我们还是让他有点反应,怎么弄,很简单,打印一句话就行
我们改一下代码
class PatchPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println ("我是插件")
}
}
然后我们重新点击一下AS右侧的上传任务,重新上传一下,然后我们在这里找到app的assemble任务,双击运行看一下有没有打印日志就行,试试看
image.png
这里上传插件成功
ze'h
这里是插件应用成功,至此我们开发自定义插件的第一步已经成功迈出
网友评论