热更新技术
我这里使用没有使用阿里巴巴的黑科技,腾讯的之类的。
首先看效果图
- 出现错误界面
- 正常界面 弹出toast。
- 详细看demo中的代码
- 首先是Test类。很简单。就是个测试,故意出错报个错误
public class Test {
public static void show(Context context) {
int i = 10;
//这里过一会 会把j的值改成1。这样子就正确
int j = 0;
Toast.makeText(context, "shit:" + i / j, Toast.LENGTH_SHORT).show();
}
}
Activity的代码,具体作用会在下面写出来
public class FixDesActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fix); }
public void inject(View view) {
// 无bug的classes2.dex文件存放地址
String sourceFile = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
+ "classes2.dex";
// 系统的私有目录
String targetFile = this.getDir("odex", Context.MODE_PRIVATE).getAbsolutePath() + File.separator
+ "classes2.dex";
try {
// 复制文件到私有目录
FileUtils.copyFile(sourceFile, targetFile);
// 加载.dex文件
FixDexUtils.loadFixDex(this.getApplication());
} catch (Exception e) {
e.printStackTrace();
}
}
public void test(View view) {
Test.show(this);
}
}
- FixDexUtils类的代码
public class FixDexUtils {
private static HashSet<File> loadedDex = new HashSet<File>();
static {
loadedDex.clear();
}
public static void loadFixDex(Context context) {
// 获取到系统的odex 目录
File fileDir = context.getDir("odex", Context.MODE_PRIVATE);
File[] listFiles = fileDir.listFiles();
for (File file : listFiles) {
if (file.getName().endsWith(".dex")) {
//存储该目录下的.dex结尾的文件(补丁)
loadedDex.add(file);
}
}
doDexInject(context, fileDir);
}
/**
* 合并之前的apk
*
* @param context
* @param fileDir
*/
private static void doDexInject(Context context, File fileDir) {
// .dex 的加载需要一个临时目录
String optimizeDir = fileDir.getAbsolutePath() + File.separator + "opt_dex";
File fopt = new File(optimizeDir);
if (!fopt.exists()) {
fopt.mkdirs();
}
//加载应用程序的dex
// 根据.dex 文件创建对应的DexClassLoader 类
for (File file : loadedDex) {
DexClassLoader classLoader = new DexClassLoader(file.getAbsolutePath()
, fopt.getAbsolutePath(), null, context.getClassLoader());
inject(classLoader, context);
}
}
private static void inject(DexClassLoader classLoader, Context context) {
// 获取到系统的DexClassLoader 类
PathClassLoader pathLoader = (PathClassLoader) context.getClassLoader();
try {
// 分别获取到补丁的dexElements和系统的dexElements
Object dexElements = combineArray(getDexElements(getPathList(classLoader)),
getDexElements(getPathList(pathLoader)));
// 获取到系统的pathList 对象
Object pathList = getPathList(pathLoader);
// 设置系统的dexElements 的值
setField(pathList, pathList.getClass(), "dexElements", dexElements);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 通过反射设置字段值
*/
private static void setField(Object obj, Class<?> cl, String field, Object value)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field localField = cl.getDeclaredField(field);
localField.setAccessible(true);
localField.set(obj, value);
}
/**
* 通过反射获取 BaseDexClassLoader中的PathList对象
*/
private static Object getPathList(Object baseDexClassLoader)
throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
}
/**
* 通过反射获取指定字段的值
*/
private static Object getField(Object obj, Class<?> cl, String field)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field localField = cl.getDeclaredField(field);
localField.setAccessible(true);
return localField.get(obj);
}
/**
* 通过反射获取DexPathList中dexElements
*/
private static Object getDexElements(Object paramObject)
throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
return getField(paramObject, paramObject.getClass(), "dexElements");
}
/**
* 合并两个数组
*
* @param arrayLhs
* @param arrayRhs
* @return
*/
private static Object combineArray(Object arrayLhs, Object arrayRhs) {
Class<?> localClass = arrayLhs.getClass().getComponentType();
int i = Array.getLength(arrayLhs);
int j = i + Array.getLength(arrayRhs);
Object result = Array.newInstance(localClass, j);
for (int k = 0; k < j; ++k) {
if (k < i) {
Array.set(result, k, Array.get(arrayLhs, k));
} else {
Array.set(result, k, Array.get(arrayRhs, k - i));
}
}
return result;
}
}
原理
这里先说下 热更新吧
通常的技术有
阿里巴巴的黑科技
底层c入手。杀到进程加载的类二进制数据,把内存里面的二进制数据修改。Dexposed、andfix这些。
腾讯的开源tinker
基于android和java的类加载机制入手
ClassLoader—-> .class文件
######这里就得知道android是如果加载classes.dex文件的
dalvik虚拟机:
PathClassLoader
用来加载应用程序的dex
DexClassLoader可以用来加载指定的某些dex文件
android studio中使用的 instant run 使用的就是。
####接下来就明白了。当用户手机上的版本出现问题时。程序员在修改好之后。拿到没问题的版本的 class打包成class.dex文件,发送到用户客户端。把classes2.dex加载dexElements(DexPathList中)。就达到目的了
- 上面的也就是使用步骤了.
错误客户端在客户手机上面。
修改成功后,把class2.dex放到服务器,客户端下载该文件。
客户端下载完成后,执行方法实现热更新。
对应到我的demo中就是把
- test方法中的j=0, 出现除0异常
- 下载这一步直接手动放到sd卡的根目录了。这里其实应该放到包目录下。也无所谓了。然后进入app,点击inject。实现替换。
- 再点击test。正常谈toast。