一、制作补丁
1.先将没有bug的apk重命名为zip
2..dex这个文件就是我们的补丁通常我们会把这个补丁放到服务器,当项目出现bug那么app就会走动从服务器获取补丁,最后完成自我修复,让用户感觉体验很流畅。
1.release签名打包作为发布版本,每次release打包都会重新生成hash.txt和mapping.txt(开启混淆的情况下才有mapping)
2.每次debug运行的时候(直接运行项目或者buildapk),都会通过校验hash.txt和mapping.txt生成已签名补丁包。
直接将补丁包放到sdcard中即可完成热修复
3.加载补丁的时候需要进行签名校验,防止恶意代码注入
二、具体的代码实现
现在有很多热修复框架的原理也是这样的,比如 Tinker、Bugly 等。
首先我们写一个会出异常的方法.
public class Test {
int a=10;
int b=0;
//点击第一个按钮调用这个方法,很明显出现了bug
public void showToast(Context context){
Toast.makeText(context, "输出:"+(a/b), Toast.LENGTH_SHORT).show();
}
}
之后我们写一个修复的类
原理就是处理了ClassLoader.pathList.dexElements[]
/**
* 修复bug的类
* 创建一个加载的dex文件的File对象的集合
*/
public class HotTestManager {
private static HashSet<File> loadedDex=new HashSet<>();
static {
//调用之前 先请空这个集合
loadedDex.clear();
}
//首先 要获取到系统的dexElement
//其次 获取到补丁的dexElement
//然后 进行dexElement的合并
//最后 把合并后的dexElement赋值给系统的dexElement的变量
public static void loadDex(Context context){
if (context == null){
return;
}
File fileDir = context.getDir("odex", Context.MODE_PRIVATE);
File[] files = fileDir.listFiles();
for ( File file:files) {
if (file.getName().startsWith("classes") || file.getName().endsWith(".dex")){
loadedDex.add(file);
}
//创建一个目录 用来 装载解压的文件
String optimizeDir= fileDir.getAbsolutePath()+File.separator+"opt_dex";
File fpot =new File(optimizeDir);
if (!fpot.exists()){
fpot.mkdir();
}
//遍历所有补丁dex文件的File对象
for (File dex : loadedDex) {
//---------------------获取系统的
try {
PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
Class<?> baseDexClassLoader = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = baseDexClassLoader.getDeclaredField("pathList");
pathListField.setAccessible(true);
Object pathListValue = pathListField.get(pathClassLoader);
Class<?> systemPathListClass = pathListValue.getClass();
Field dexElementsField = systemPathListClass.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
Object systemElement = dexElementsField.get(pathListValue);
DexClassLoader dexClassLoader = new DexClassLoader(dex.getAbsolutePath()
, fpot.getAbsolutePath()
, null
, context.getClassLoader());
Field myPathListField = baseDexClassLoader.getDeclaredField("pathList");
myPathListField.setAccessible(true);
Object myPathListValue = myPathListField.get(dexClassLoader);
Class<?> myPathListClass = myPathListValue.getClass();
Field myDexElementsField = myPathListClass.getDeclaredField("dexElements");
myDexElementsField.setAccessible(true);
Object myElement = myDexElementsField.get(myPathListValue);
int systemLength = Array.getLength(systemElement);
int myLength = Array.getLength(myElement);
int newElementLength =systemLength+myLength;
Class<?> componentType = systemElement.getClass().getComponentType();
Object newElement = Array.newInstance(componentType, newElementLength);
for (int i = 0; i <newElementLength ; i++) {
if (i<myLength){
Array.set(newElement,i,Array.get(myElement,i));
}else {
Array.set(newElement,i,Array.get(systemElement,i-myLength));
}
}
//将合并好的数组 赋值给dexElement的成员变量
dexElementsField.set(pathListValue,newElement);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
最后我们把自己的补丁读取进来,我们的修复类就实现了这一步,将异常类替换掉,让程序读取我们的补丁不去读取异常的类然后我们读取补丁,把这个补丁存到我们的app里面。
//修复bug的
public void hotTest(View view) {
//先将补丁复制到指定目录下
File odex = this.getDir("odex", Context.MODE_PRIVATE);
//创建补丁的名字
String name="out.dex";
//创建
String filePath=new File(odex,name).getAbsolutePath();
File file =new File(filePath);
if (file.exists()){
file.delete();
}
InputStream is=null;
FileOutputStream os=null;
try {
is=new FileInputStream(new File(Environment.getExternalStorageDirectory(),name));
os=new FileOutputStream(filePath);
int len =0;
byte[] bytes=new byte[1024];
while ((len=is.read(bytes))!=-1) {
os.write(bytes,0,len);
}
File f=new File(filePath);
HotTestManager.loadDex(this);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}