首先看效果图
出现错误界面
正常界面 弹出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。