本章会写出可以让你直接运行的Dome(后面附带本章节的源码)
一步一步跟着来肯定会成功!!!
再此先祝你成功啦。
APP更新,请参考 APP版本更新 该章节
前言:热修复是什么?
在刚进入王者荣耀的时候有没有遇见过,检测更新....再下载更新包...再解压压缩包...最后就可以进入游戏了。
没错!王者荣耀在上线的时候难免会出现bug,王者荣耀这是在修复bug。要是王者荣耀出现bug让你去重新下载一个没有bug的版本(一万只羊驼路过....),所以让玩家在进入王者时检测是否加载了最新的补丁包,是明智的选择。
效果图:
一个有bug的 1.0图 修复bug后的 1.1图
本章节项目文件结构图:
实现起来也非常简单,但需要细心。接下来一起跟着我一步一步来实现吧。
第一步:编写我们带bug代码文件 BugClass.java
public class BugClass {
static String str1 = "bug已修复,优秀!"; //修复后的代码
static String str2 = "一个完美的bug"; //默认是带有bug的
public static String Bug(Context context){
Toast.makeText(context,str2,Toast.LENGTH_SHORT).show();
return str2;
}
}
第二步:复制 FixDexUtil.java 热修复工具类
public class FixDexUtil {
//这下面两个属性可自己修改
private final static String REPAIR_FILE_NAME = "BugClass"; //修复文件名
private final static String REPAIR_FILE_PATH = "1079"; //修复文件路径(默认初始路径为根目录)
private static final String DEX_SUFFIX = ".dex";
private static final String APK_SUFFIX = ".apk";
private static final String JAR_SUFFIX = ".jar";
private static final String ZIP_SUFFIX = ".zip";
private static final String DEX_DIR = "odex";
private static final String OPTIMIZE_DEX_DIR = "optimize_dex";
private static HashSet<File> loadedDex = new HashSet<>();
static {
loadedDex.clear();
}
/**
* 开启修复
*
* @param context
*/
public static void startRepair(final Context context) {
File externalStorageDirectory = Environment.getExternalStorageDirectory();
// 遍历所有的修复dex , 因为可能是多个dex修复包
File fileDir = externalStorageDirectory != null ?
new File(externalStorageDirectory, FixDexUtil.REPAIR_FILE_PATH) :
new File(context.getFilesDir(), FixDexUtil.DEX_DIR);// data/user/0/包名/files/odex(这个可以任意位置)
if (!fileDir.exists()) {//如果目录不存在就创建所有目录,这里需要添加权限
fileDir.mkdirs();
}
if (FixDexUtil.isGoingToFix(context)) {
FixDexUtil.loadFixedDex(context, Environment.getExternalStorageDirectory());
Log.i("GT_", "正在修复");
}
}
/**
* 加载补丁,使用默认目录:data/data/包名/files/odex
*
* @param context
*/
public static void loadFixedDex(Context context) {
loadFixedDex(context, null);
}
/**
* 加载补丁
*
* @param context 上下文
* @param patchFilesDir 补丁所在目录
*/
public static void loadFixedDex(Context context, File patchFilesDir) {
// dex合并之前的dex
doDexInject(context, loadedDex);
}
/**
* @author bthvi
* @time 2019/10/10 11:42
* @desc 验证是否需要热修复
*/
public static boolean isGoingToFix(@NonNull Context context) {
boolean canFix = false;
File externalStorageDirectory = Environment.getExternalStorageDirectory();
// 遍历所有的修复dex , 因为可能是多个dex修复包
File fileDir = externalStorageDirectory != null ?
new File(externalStorageDirectory, REPAIR_FILE_PATH) :
new File(context.getFilesDir(), DEX_DIR);// data/data/包名/files/odex(这个可以任意位置)
File[] listFiles = fileDir.listFiles();
if (listFiles != null) {
for (File file : listFiles) {
if ((file.getName().endsWith(DEX_SUFFIX)
|| file.getName().endsWith(APK_SUFFIX)
|| file.getName().endsWith(JAR_SUFFIX)
|| file.getName().endsWith(ZIP_SUFFIX))) {
loadedDex.add(file);// 存入集合
//有目标dex文件, 需要修复
canFix = true;
}
}
}
return canFix;
}
private static void doDexInject(Context appContext, HashSet<File> loadedDex) {
String optimizeDir = appContext.getFilesDir().getAbsolutePath() +
File.separator + OPTIMIZE_DEX_DIR;
// data/data/包名/files/optimize_dex(这个必须是自己程序下的目录)
File fopt = new File(optimizeDir);
if (!fopt.exists()) {
fopt.mkdirs();
}
try {
// 1.加载应用程序dex的Loader
PathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();
for (File dex : loadedDex) {
// 2.加载指定的修复的dex文件的Loader
DexClassLoader dexLoader = new DexClassLoader(
dex.getAbsolutePath(),// 修复好的dex(补丁)所在目录
fopt.getAbsolutePath(),// 存放dex的解压目录(用于jar、zip、apk格式的补丁)
null,// 加载dex时需要的库
pathLoader// 父类加载器
);
// 3.开始合并
// 合并的目标是Element[],重新赋值它的值即可
/**
* BaseDexClassLoader中有 变量: DexPathList pathList
* DexPathList中有 变量 Element[] dexElements
* 依次反射即可
*/
//3.1 准备好pathList的引用
Object dexPathList = getPathList(dexLoader);
Object pathPathList = getPathList(pathLoader);
//3.2 从pathList中反射出element集合
Object leftDexElements = getDexElements(dexPathList);
Object rightDexElements = getDexElements(pathPathList);
//3.3 合并两个dex数组
Object dexElements = combineArray(leftDexElements, rightDexElements);
// 重写给PathList里面的Element[] dexElements;赋值
Object pathList = getPathList(pathLoader);// 一定要重新获取,不要用pathPathList,会报错
setField(pathList, pathList.getClass(), "dexElements", dexElements);
}
Toast.makeText(appContext, "修复完成", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 反射给对象中的属性重新赋值
*/
private static void setField(Object obj, Class<?> cl, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Field declaredField = cl.getDeclaredField(field);
declaredField.setAccessible(true);
declaredField.set(obj, value);
}
/**
* 反射得到对象中的属性值
*/
private static Object getField(Object obj, Class<?> cl, String field) throws NoSuchFieldException, IllegalAccessException {
Field localField = cl.getDeclaredField(field);
localField.setAccessible(true);
return localField.get(obj);
}
/**
* 反射得到类加载器中的pathList对象
*/
private static Object getPathList(Object baseDexClassLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
}
/**
* 反射得到pathList中的dexElements
*/
private static Object getDexElements(Object pathList) throws NoSuchFieldException, IllegalAccessException {
return getField(pathList, pathList.getClass(), "dexElements");
}
/**
* 数组合并
*/
private static Object combineArray(Object arrayLhs, Object arrayRhs) {
Class<?> clazz = arrayLhs.getClass().getComponentType();
int i = Array.getLength(arrayLhs);// 得到左数组长度(补丁数组)
int j = Array.getLength(arrayRhs);// 得到原dex数组长度
int k = i + j;// 得到总数组长度(补丁数组+原dex数组)
Object result = Array.newInstance(clazz, k);// 创建一个类型为clazz,长度为k的新数组
System.arraycopy(arrayLhs, 0, result, 0, i);
System.arraycopy(arrayRhs, 0, result, i, j);
return result;
}
}
第三步:我们在 MyApp.java 中修复bug
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
FixDexUtil.startRepair( getApplicationContext());//开启热修复
}
}
第四步:在 AndroidManifest.xml (清单文件)中添加权限与指定MyApp的代码
权限:
<!-- 文件的读取和写入权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
MyApp的指定:
<application
android:name=".MyApp"
...
>
</application>
MyApp指定的参考图:
第五步:在显示 Activity 与 xml 的内容 MainActivity.java 与 activity_main.xml
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.tv);
tv.setText(BugClass.Bug(this));
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
好,现在咋们所有要写的代码已经写完了,就剩操作了。
操作1:安装带bug的app
此时直接运行项目,你的app会显示
一个有bug的 1.0图
操作2:做一个修复bug的 补丁包
现在你的app有bug,需要一个补丁去修复,那么我们现在就来教你做补丁包。
先在项目中修复好bug,再将当前已经修复好bug的 类 打包成 dex 文件
修复bug:
public class BugClass {
static String str1 = "bug已修复,优秀!";
static String str2 = "一个完美的bug";
public static String Bug(Context context){
Toast.makeText(context,str1,Toast.LENGTH_SHORT).show();
return str1;
}
}
将 类 打包成 dex 文件 其实是将 class文件转成 dex 文件,我们先获取 class 文件先
第一节:点击 build --> Rebuild Project
第二节:展开 Project 结构 单独复制出 java 的编译文件 class (BugClass.class)
第三节:在D盘新建文件夹 dex ,并将 BugClass.class 文件复制到该目录
操作3:将 class 文件转为 dex 文件(关键点!!!)
先找到你的 Adnroid SDK 目录 中 build-tools 目录里的 dx.bat 文件。
博主这里的地址为:D:\King\E\sdk\build-tools\24.0.3(每个人的路径应该都不同)
找到后就打开 cmd 开始来转换把。
cmd 关键代码:
dx --dex --no-strict --output=D:\dex\GT_BugClass1.dex D:\dex\BugClass1.class
来查看dex文件夹吧,此时我们就有 dex 补丁包了
将该补丁包 复制到手机的 补丁文件夹下再重启 app (app需要停止运行)bug就修复了。
咋们重启APP后效果就是这样了。
修复bug后的 1.1图
当然,除了这样获取dex进行热修复外,你还可以用这些方式获取:编译后会在这个目录出现这个apk文件,可以直接把这个文件放到 1079 目录也照样会解析成dex进行热修复,
还可以直接将后缀名改为 zip后,直接放1079目录也行,当然,zip里的文件也可以删除一些不需要的
我们也可以将zip里面的 dex 全部拿出来,放1079目录也可以。或者在zip里删除掉除dex外的所有文件,再将这个精简的 zip 放入1079文件,也一样
总结:本章节细节地方比较多,只要你一步一步跟着来,肯定会成功的。
本章节源码网址:GitHub - 1079374315/ThermalRemediation: APP 热修复 Dome
源码中我已将 dex 文件拷贝到项目中 具体地址请参考该图
APP更新,请参考 APP版本更新 该章节
点个关注点个赞呗(〃'▽'〃),关注博主最新发布库:GitHub - 1079374315/GT
美帝 框架,让创造变得如此简单!