一、热修复:
之前在插件化中讲到了如何在家插件,其实热修复也是一样的,不过我们得要把加载的热修复模块放在dexElement数组的前面
1、获取当前应用的PathClassLoader
2、反射获取到DexPathList属性对象pathList
3、反射修改pathList的dexElement
1)把补丁包patch.dex转换为Element[](path)
2)获得pathList的dexElements属性(old)
3)path+old合并,并反射赋值给pathList的dexElements
Android N 混合编译:
ART是在Android KiKat(Android4.0)引入并在Lollipop(Android 5.0)中设为默认运行环境,可以看做Dalvik2.0。
ART模式在Android N(7.0)之前安装APK时会采用AOT(Ahead of time:提前编译、静态编译)预编译为机器码。
而在Android N 使用混合模式的运行时,应用在安装时不做编译,而是运行时解释字节码,同时在JIT编译了一写代码后将这些代码信息记录至Profile文件。等到设备空闲时使用AOT(All-of-the-time compilation:全时段编译)编译生成为app_image的base.art(类对象映射)文件,这个art文件会在apk启动时自动加载(相当于缓存)。根据类加载原理,类加载了就无法被代替,即无法修复。
简单点说,ART虚拟机会缓存上一次频繁使用的类,下次启动时会在类加载之前先加载缓存的类,这样假如我们的待加载的热修复的类和缓存的类一样就不会加载热修复的类了。
混合编译热修复解决方案:
运行时替换PathClassLoader
App image中的class是插入到PathClassloader中的ClassTable中。假设我们完全废弃掉PathClassLoader,而是采用新建ClassLoader来加载后续的所有类,这样就可以达cache无效化的效果了。
现在一般用的比较多的第三方热修复框架是:Tinker
二、字节码插桩
就是在Java字节码Class中某些位置插入或者修改一些代码
在ART虚拟机以下,就是Dalvik虚拟机会发生如下冲突:
1、CLASS_ISPREVERIFIED:
如果我们一个MainActivity类中只引用了:Utils类。当打包dex文件时,MainActivity与Utils都在classes.dex文件中,则加载MainActivity类时,被标记为CLASS_ISPREVERIFIED。
如果使用补丁包中的Utils类取代出bug的Utils,则会导致MainActivity与其引用的Utils不再同一个dex文件中,但是MainActivity已经被打伤标记此时冲突。导致校验失败!
2、阻止标记:
防止类被打上CLASS_ISPREVERIFIED标记,就是将我们需要引用的Utils类单独打包一个dex文件,然后通过字节码插桩插入一个Utils的引用到MainActivity中,这时我们编译的话会报NoClassDefFoundEoor错误,这时因为我们还没有把单独生成的dex文件通过加载器加载进去。
Android打包流程:
1)AAPT,打包资源文件,并生成R.Java和resource.arsc文件
2)AIDI,读取.aidl文件生成对应Java文件
3)JAVAC,将Java文件编译成.class文件
4)DX,将多个.class文件转换打包成.dex文件
ASM字节码插桩框架:
我们要在.class文件中插入代码的话,手动的去插入很麻烦也很容易出错,ASM框架就可以帮我们解决这个问题了。
ASM框架,按照Class文件的格式,解析、修改、生成Class,可以懂爱生成类或者增强现有类的功能,比如我们的GSON操作json的框架一样。
ASM的使用示例:
public static byte[] referHackWhenInit(InputStream inputStream) throws IOException {
ClassReader cr = new ClassReader(inputStream);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new ClassVisitor(Opcodes.ASM5, cw) {
@Override
public MethodVisitor visitMethod(int access, final String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
mv = new MethodVisitor(api, mv) {
@Override
public void visitInsn(int opcode) {
//在构造方法中插入AntilazyLoad引用
if ("<init>".equals(name) && opcode == Opcodes.RETURN) {
super.visitLdcInsn(Type.getType("Lcom/enjoy/patch/hack/AntilazyLoad;"));
}
super.visitInsn(opcode);
}
};
return mv;
}
};
cr.accept(cv, 0);
return cw.toByteArray();
}
插件开发:
其实我们的Android项目在gradle执行的过程中,就是通过我们的Android插件执行任务比如:编译Java文件、生成dex文件、打包等等。
插件的实现方式:
1)Build script脚本,把插件卸载build.gradle文件中,一般用于简单的逻辑,只在该build.gradle文件可见
2)buildSrc目录,将插件源代码放在buildSrc/src/main/groovy/中,用的是groovy语法写的,只对该项目中可见
3)独立项目,一个独立的Java项目/模块,可以将文件包发布到仓库(Jcenter),使其他项目方便引入
插件实现:
正如我们开发Android项目,创建Activity需要继承Android.app.Activity或其子类一样,插件需要实现org.gradle.api.Plugin<Project>接口
插件扩展:
引入某些插件可能会需要进程插件配置,就比如我们常见的:
android {
compileSdkVersion 28
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.enjoy.enjoyfix"
minSdkVersion 14
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk{
abiFilters 'armeabi-v7a'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
我们也可以对我们自己定义的插件实现扩展,使用project.getExtensions().create("xxx",JavaBean.class);
Task:
Task,即任务。Android工程引入的Android插件,创建了多个自定义任务,如:编译Java文件、生成dex文件、打包等等,每个任务执行一项工作。
假如我们想插入一个任务Task1在Task2之前,我们可以在Task.doFirst方法中执行Task1