作者:独孤狼
什么是热修复
在我们应用上线后出现bug需要及时修复时,不用再发新的安装包,只需要发布补丁包,在客户无感知下修复掉bug
怎么进行热修复
服务端:补丁包管理
用户端:执行热修复
开发端:生成补丁包
热修复要解决的问题
客户端
- 补丁包是什么?
- 如何生成补丁包?
- 开启混淆后呢?
- 对比改动自动生成补丁包(gradle)?
服务端
- 什么时候执行热修复?
- 怎么执行热修复(使用补丁包)?
- Android版本兼容问题?
热修复解决方案
热补丁方案有很多,其中比较出名的有腾讯Tinker、阿里的AndFix、美团的Robust以及QZone的超级补丁方案。
AndFix
在native动态替换java层的方法,通过native层hook java层的代码
Robust
Android热更新方案Robust - 美团技术团队 (meituan.com)
public long getIndex() {
// 有BUG的代码片段
return 100;
}
public static ChangeQuickRedirect changeQuickRedirect;
public long getIndex() {
// 经过插桩后实际执行的代码
if(changeQuickRedirect != null) {
return 修复的实现;
}
return 100L;
}
Tinker
Tinker通过计算对比指定的Base Apk中的dex与修改后的Apk中的dex的区别,补丁包中的内容即为两者差分的描述,运行时将Base Apk中的dex与补丁包进行合成,重启后加载全新的合成后的dex文件
ClassLoader
双亲委派机制
含义
某个类加载器在加载类时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务或者没有父类加载器时,才自己去加载。
作用
1、避免重复加载,当父加载器已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
2、安全性考虑,防止核心API库被随意篡改。
类查找流程
类加载实现热修复
流程
1、获取程序的PathClassLoader对象
2、反射获得PathClassLoader父类BaseDexClassLoader的pathList对象
3、反射获取pathList的dexElements对象 (oldElement)
4、把补丁包变成Element数组:patchElement(反射执行makePathElements)
5、合并patchElement+oldElement = newElement (Array.newInstance)
6、反射把oldElement赋值成newElement
代码实现
HotFix.installPatch(this, new File("/sdcard/patch.jar"));
EnjoyFix.java类
package com.example.simpledemo;
import android.app.Application;
import android.content.Context;
import android.os.Build;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class HotFix {
private static final String TAG = "HotFix";
private static File initPatch(Context context) {
File patchFile = new File(context.getExternalFilesDir(""), "patch.dex");
FileOutputStream fos = null;
InputStream is = null;
try {
fos = new FileOutputStream(patchFile);
is = context.getAssets().open("patch.dex");
int len;
byte[] buffer = new byte[2048];
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return hackFile;
}
public static void installPatch(Application application, File patch) {
File patchDex = initHack(application);
List<File> patchs = new ArrayList<>();
patchs.add(patchDex);
if (patch.exists()) {
patchs.add(patch);
}
//1、获取程序的PathClassLoader对象
ClassLoader classLoader = application.getClassLoader();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
try {
ClassLoaderInjector.inject(application, classLoader, patchs);
} catch (Throwable throwable) {
}
return;
}
//2、反射获得PathClassLoader父类BaseDexClassLoader的pathList对象
try {
Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
Object pathList = pathListField.get(classLoader);
//3、反射获取pathList的dexElements对象 (oldElement)
Field dexElementsField = ShareReflectUtil.findField(pathList, "dexElements");
Object[] oldElements = (Object[]) dexElementsField.get(pathList);
//4、把补丁包变成Element数组:patchElement(反射执行makePathElements)
Object[] patchElements = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Method makePathElements = ShareReflectUtil.findMethod(pathList, "makePathElements", List.class, File.class, List.class);
ArrayList<IOException> ioExceptions = new ArrayList<>();
patchElements = (Object[]) makePathElements.invoke(pathList, patchs, application.getCacheDir(), ioExceptions);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Method makePathElements = ShareReflectUtil.findMethod(pathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class);
ArrayList<IOException> ioExceptions = new ArrayList<>();
patchElements = (Object[]) makePathElements.invoke(pathList, patchs, application.getCacheDir(), ioExceptions);
}
//5、合并patchElement+oldElement = newElement (Array.newInstance)创建一个新数组,大小 oldElements+patchElements
Object[] newElements = (Object[]) Array.newInstance(oldElements.getClass().getComponentType(), oldElements.length + patchElements.length);
System.arraycopy(patchElements, 0, newElements, 0, patchElements.length);
System.arraycopy(oldElements, 0, newElements, patchElements.length, oldElements.length);
//6、反射把oldElement赋值成newElement
dexElementsField.set(pathList, newElements);
} catch (Exception e) {
e.printStackTrace();
}
}
}
热修复原理
依据双亲委托机制的原理,在执行热修复代码后,会先去加载patch.dex中的类,从而避免再加载有bug的类,达到热修复的目的
为了帮助到大家快速熟悉APP架构,热修复这一块的内容,下面我将自己的心得体会整理在此了,大家可以参考这学习:https://qr21.cn/CaZQLo?BIZ=ECOMMERCE