通过这篇文章给大家介绍下我理解的热更新的逻辑,需要先了解一些关系到的知识
本篇文章主要是 类加载 和 Instant Run 两种方式进行的热更新
需要先了解Android 类加载,可以看这篇 https://blog.csdn.net/hjiangshujing/article/details/104249956
此处用到的是Android 中的 DexClassLoader 类加载器
以下做简单的介绍
class 加载过程从ClassLoader 的loadClass方法开始
ClassLoader 的加载方法为loadClass
可以通过 Android 中的类加载 文章中的最后的图来说
public Class> findClass(String name, List suppressed) {
for (Element element : dexElements) {//1
Class> clazz = element.findClass(name, definingContext, suppressed);//2
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
/**
* 修复方法
*/
private static void dexFix(ClassLoader classLoader, File optimizedDirectory, File... dexFiles) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessError, IllegalAccessException {
StringBuilder sb = new StringBuilder();
for (File file : dexFiles) {
sb.append(file.getAbsolutePath()).append(":");
}
//1.使用DexClassLoader 加载所有外部dex文件
DexClassLoader dexClassLoader = new DexClassLoader(sb.deleteCharAt(sb.length() - 1).toString(),
optimizedDirectory.getAbsolutePath(), null, ClassLoader.getSystemClassLoader());
//2.获取系统 dexElements
Object pathElements = getClassLoaderElements(classLoader);
//3.获取外部dex 的dexElements
Object dexElements = getClassLoaderElements(dexClassLoader);
Field pathListField = ReflectUtil.findFiled(classLoader.getClass(), "pathList");
Object pathList = pathListField.get(classLoader);
Field dexElementsFiled = ReflectUtil.findFiled(pathList.getClass(), "dexElements");
//4.将系统与外部dexElements合并
Object arrayAppend = arrayAppend(dexElements, pathElements);
//5.修改系统 dexElements
dexElementsFiled.set(pathList, arrayAppend);
}
/**
* 将所有Array类型的数据按顺序合并成一个Array数据
*/
public static Object arrayAppend(Object... elements) {
int length = 0;
for (Object element : elements) {
length += Array.getLength(element);
}
Object array = Array.newInstance(elements[0].getClass().getComponentType(), length);
for (int i = 0, j = 0, k = 0, elementLength = Array.getLength(elements[k]); i < length; i++) {
Array.set(array, i, Array.get(elements[k], i - j));
if (i - j == elementLength - 1) {
j += elementLength;
k++;
if (k < elements.length) {
elementLength = Array.getLength(elements[k]);
}
}
}
return array;
}
/**
* 获取ClassLoader 中 dexElements 成员变量
*/
private static Object getClassLoaderElements(ClassLoader classLoader) throws NoSuchMethodException, IllegalAccessException {
Field pathListField = ReflectUtil.findFiled(classLoader.getClass(), "pathList");
Object pathList = pathListField.get(classLoader);
Field dexElementsFiled = ReflectUtil.findFiled(pathList.getClass(), "dexElements");
return dexElementsFiled.get(pathList);
}
Instant Run 是Android Studio 2.0 以后新增的一个运行机制,能够显著减少开发人员第二次以及以后的构建和部署时间
Instant Run原理就是:Instant Run 在第一次构建 APK 时,使用 ASM 在每一个方法中注入了判断代码
ASM :是一个 Java 字节码操控框架,它能够动态生成类或者增强现有类的功能。 ASM 可以直接产生 clsss文件,也可以在类被加载到虚拟机之前动态改变类的行为。
此类是对ChangeQuickRedirect修复类做了一层包装,最终还是调用的ChangeQuickRedirect实现类中的方法
每个补丁类必须实现的接口(ChangeQuickRedirect),内部有两个方法
原方法
public long getIndex() {
return 100;
}
将方法中注入判断代码后
public static ChangeQuickRedirect changeQuickRedirect;
public long getIndex() {
if(changeQuickRedirect != null) {
//PatchProxy中封装了获取当前className和methodName的逻辑,并在其内部最终调用了changeQuickRedirect的对应函数
if(PatchProxy.isSupport(new Object[0], this, changeQuickRedirect, false)) {
return ((Long)PatchProxy.accessDispatch(new Object[0], this, changeQuickRedirect, false)).longValue();
}
}
return 100L;
}
public class PatchExecutor extends Thread {
protected Context context;
protected PatchManipulate patchManipulate;
protected RobustCallBack robustCallBack;
public PatchExecutor(Context context, PatchManipulate patchManipulate, RobustCallBack robustCallBack) {
this.context = context.getApplicationContext();
this.patchManipulate = patchManipulate;
this.robustCallBack = robustCallBack;
}
@Override
public void run() {
try {
//拉取补丁列表
List patches = fetchPatchList();
//应用补丁列表
applyPatchList(patches);
} catch (Throwable t) {
Log.e("robust", "PatchExecutor run", t);
robustCallBack.exceptionNotify(t, "class:PatchExecutor,method:run,line:36");
}
}
/**
* 拉取补丁列表
*/
protected List fetchPatchList() {
return patchManipulate.fetchPatchList(context);
}
/**
* 应用补丁列表
*/
protected void applyPatchList(List patches) {
if (null == patches || patches.isEmpty()) {
return;
}
Log.d("robust", " patchManipulate list size is " + patches.size());
for (Patch p : patches) {
if (p.isAppliedSuccess()) {
Log.d("robust", "p.isAppliedSuccess() skip " + p.getLocalPath());
continue;
}
if (patchManipulate.ensurePatchExist(p)) {
boolean currentPatchResult = false;
try {
currentPatchResult = patch(context, p);
} catch (Throwable t) {
robustCallBack.exceptionNotify(t, "class:PatchExecutor method:applyPatchList line:69");
}
if (currentPatchResult) {
//设置patch 状态为成功
p.setAppliedSuccess(true);
//统计PATCH成功率 PATCH成功
robustCallBack.onPatchApplied(true, p);
} else {
//统计PATCH成功率 PATCH失败
robustCallBack.onPatchApplied(false, p);
}
Log.d("robust", "patch LocalPath:" + p.getLocalPath() + ",apply result " + currentPatchResult);
}
}
}
protected boolean patch(Context context, Patch patch) {
if (!patchManipulate.verifyPatch(context, patch)) {
robustCallBack.logNotify("verifyPatch failure, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:107");
return false;
}
ClassLoader classLoader = null;
try {
File dexOutputDir = getPatchCacheDirPath(context, patch.getName() + patch.getMd5());
classLoader = new DexClassLoader(patch.getTempPath(), dexOutputDir.getAbsolutePath(),
null, PatchExecutor.class.getClassLoader());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
if (null == classLoader) {
return false;
}
Class patchClass, sourceClass;
Class patchesInfoClass;
PatchesInfo patchesInfo = null;
try {
Log.d("robust", "patch patch_info_name:" + patch.getPatchesInfoImplClassFullName());
patchesInfoClass = classLoader.loadClass(patch.getPatchesInfoImplClassFullName());
patchesInfo = (PatchesInfo) patchesInfoClass.newInstance();
} catch (Throwable t) {
Log.e("robust", "patch failed 188 ", t);
}
if (patchesInfo == null) {
robustCallBack.logNotify("patchesInfo is null, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:114");
return false;
}
//classes need to patch 1.获取补丁包中所有待修复类信息
List patchedClasses = patchesInfo.getPatchedClassesInfo();
if (null == patchedClasses || patchedClasses.isEmpty()) {
// robustCallBack.logNotify("patchedClasses is null or empty, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:122");
//手写的补丁有时候会返回一个空list
return true;
}
boolean isClassNotFoundException = false;
for (PatchedClassInfo patchedClassInfo : patchedClasses) {
String patchedClassName = patchedClassInfo.patchedClassName;
String patchClassName = patchedClassInfo.patchClassName;
if (TextUtils.isEmpty(patchedClassName) || TextUtils.isEmpty(patchClassName)) {
robustCallBack.logNotify("patchedClasses or patchClassName is empty, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:131");
continue;
}
Log.d("robust", "current path:" + patchedClassName);
try {
try {
//2.加载要被修复的类
sourceClass = classLoader.loadClass(patchedClassName.trim());
} catch (ClassNotFoundException e) {
isClassNotFoundException = true;
// robustCallBack.exceptionNotify(e, "class:PatchExecutor method:patch line:258");
continue;
}
Field[] fields = sourceClass.getDeclaredFields();
Log.d("robust", "oldClass :" + sourceClass + " fields " + fields.length);
Field changeQuickRedirectField = null;
for (Field field : fields) {
if (TextUtils.equals(field.getType().getCanonicalName(), ChangeQuickRedirect.class.getCanonicalName()) && TextUtils.equals(field.getDeclaringClass().getCanonicalName(), sourceClass.getCanonicalName())) {
//3.找到要被修复类的注入的静态变量
changeQuickRedirectField = field;
break;
}
}
if (changeQuickRedirectField == null) {
robustCallBack.logNotify("changeQuickRedirectField is null, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:147");
Log.d("robust", "current path:" + patchedClassName + " something wrong !! can not find:ChangeQuickRedirect in" + patchClassName);
continue;
}
Log.d("robust", "current path:" + patchedClassName + " find:ChangeQuickRedirect " + patchClassName);
try {
//4.加载要修复类对应的patch中的补丁类对象
patchClass = classLoader.loadClass(patchClassName);
Object patchObject = patchClass.newInstance();
changeQuickRedirectField.setAccessible(true);
changeQuickRedirectField.set(null, patchObject);//5.将静态变量的值设置为补丁包中的补丁类
//patchObject为补丁类对象
Log.d("robust", "changeQuickRedirectField set success " + patchClassName);
} catch (Throwable t) {
Log.e("robust", "patch failed! ");
robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:163");
}
} catch (Throwable t) {
Log.e("robust", "patch failed! ");
// robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:169");
}
}
Log.d("robust", "patch finished ");
if (isClassNotFoundException) {
return false;
}
return true;
}
private static final String ROBUST_PATCH_CACHE_DIR = "patch_cache";
/*
* @param c
* @return 返回缓存补丁路径,一般是内部存储,补丁目录
*/
private static File getPatchCacheDirPath(Context c, String key) {
File patchTempDir = c.getDir(ROBUST_PATCH_CACHE_DIR + key, Context.MODE_PRIVATE);
if (!patchTempDir.exists()) {
patchTempDir.mkdir();
}
return patchTempDir;
}
}