Android热修正原理与实战
热修正
什么是热修正
在咱们应用上线后出现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的类,到达热修正的目的