1.编译class文件

先看下自己的javac版本

如果是用javac1.6以上的版本编译的class文件会报错(原因未知)。

所以需要在app的gradle下android{}中添加命令行。

compileOptions {sourceCompatibility JavaVersion.VERSION_1_6targetCompatibility JavaVersion.VERSION_1_6
}

意思是用1.6版本的javac进行编译。

因为1.8才支持Lambda表达式,所以需要改回来。

点Make Project

将整个包路径还有修复好的class文件复制下来。

2.class转dex

我们把自己需要修复的java文件通过AS编译成class文件之后

再用sdk目录下的dx.bat工具将class文件转成dex文件。

打开cmd,如果你设置了环境变量,可以直接在c盘调用语句,不然你就需要把路径切换到跟dx.bat一样。

比如我上面就是cd C:\Users\*****\AppData\Local\Android\Sdk\build-tools\30.0.1

把你的包放到cmd所对应的路径下,如果配置了环境变量就可以直接把包放到桌面。

回车后

dx --dex –output=com\example\hotfixdemo\classes2.dex com\example\hotfixdemo\MainActivity.class

该命令前面对应的是生成的dex文件放置路径+文件名,后面的就是class文件所对应的路径。

如果你放在桌面,默认的cmd路径是没有附带Desktop的,需要再后面的路径上加上Desktop\。

3.编写热修复工具类

package com.example.hotfixdemo;import android.content.Context;import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.HashSet;import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;public class FixDexUtils {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 HashSet<File> loadedDex = new HashSet<File>();/*** 加载补丁,使用默认目录: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) {if (context == null) {return;}// 遍历所有的修复dexFile fileDir = patchFilesDir != null ? patchFilesDir : new File(context.getExternalCacheDir().getAbsolutePath());// data/data/包名/cache(这个可以任意位置)File[] listFiles = fileDir.listFiles();for (File file : listFiles) {if (file.getName().startsWith("classes") &&(file.getName().endsWith(DEX_SUFFIX)|| file.getName().endsWith(APK_SUFFIX)|| file.getName().endsWith(JAR_SUFFIX)|| file.getName().endsWith(ZIP_SUFFIX))) {loadedDex.add(file);// 存入集合}}// dex合并之前的dexdoDexInject(context);}private static void doDexInject(Context appContext) {String optimizeDir = appContext.getFilesDir().getAbsolutePath();// data/data/包名/files (这个必须是自己程序下的目录)File fopt = new File(optimizeDir);if (!fopt.exists()) {fopt.mkdirs();}try {// 1.加载应用程序的dexPathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();for (File dex : FixDexUtils.loadedDex) {// 2.加载指定的修复的dex文件DexClassLoader dexLoader = new DexClassLoader(dex.getAbsolutePath(),// 修复好的dex(补丁)所在目录fopt.getAbsolutePath(),// 存放dex的解压目录(用于jar、zip、apk格式的补丁)null,// 加载dex时需要的库pathLoader// 父类加载器);// 3.合并Object dexPathList = getPathList(dexLoader);Object pathPathList = getPathList(pathLoader);Object leftDexElements = getDexElements(dexPathList);Object rightDexElements = getDexElements(pathPathList);// 合并完成Object dexElements = combineArray(leftDexElements, rightDexElements);// 重写给PathList里面的Element[] dexElements;赋值Object pathList = getPathList(pathLoader);// 一定要重新获取,不要用pathPathList,会报错setField(pathList, pathList.getClass(), dexElements);}} catch (Exception e) {e.printStackTrace();}}/*** 反射给对象中的属性重新赋值*/private static void setField(Object obj, Class<?> cl, Object value) throws NoSuchFieldException, IllegalAccessException {Field declaredField = cl.getDeclaredField("dexElements");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 left, Object right) {Class<?> componentType = left.getClass().getComponentType();int i = Array.getLength(left);// 得到左数组长度(补丁数组)int j = Array.getLength(right);// 得到原dex数组长度int k = i + j;// 得到总数组长度(补丁数组+原dex数组)Object result = Array.newInstance(componentType, k);// 创建一个类型为componentType,长度为k的新数组System.arraycopy(left, 0, result, 0, i);System.arraycopy(right, 0, result, i, j);return result;}
}

4.测试

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);TextView tv1 = findViewById(R.id.tv1);TextView tv2 = findViewById(R.id.tv2);tv1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {FixDexUtils.loadFixedDex(MainActivity.this);}});tv2.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(MainActivity.this, Text.getString(),Toast.LENGTH_SHORT).show();}});}
}

直接看代码注释,这里我们把修复好的文件放在了外部存储的缓存文件夹中,我们可以调试手机直接把文件放进去

加载到我们的dex文件后他会存放到我们指定的另一个内部存储文件夹中。

我们主要修复的是Test类。

修复前:

public class Text {public static String getString(){return "出错了!";}
}

修复后:

public class Text {public static String getString(){return "修复了";}
}

这里需要注意你所修复的类被加载的时机,如果你这里修复的是MainActivity,该activity已经被加载了你再去修复是没有用的,因为该类已经被加载成一个对象存在内存中。这里我们是点了tv2才会去加载Test类。

类被加载时机:

  1. 定义了main的类,启动main方法时该类会被加载
  2. 创建类或子类的实例,即new对象的时候
  3. 访问类的静态方法
  4. 访问类的静态变量
  5. 反射 Class.forName()

如果我们先点击tv1再点tv2此时就会显示修复了,如果先点tv2则一直显示是出错了。

Android—热修复实践相关推荐

  1. 微信 Tinker 负责人张绍文关于 Android 热修复直播分享记录

    2019独角兽企业重金招聘Python工程师标准>>> 微信 Tinker 负责人张绍文关于 Android 热修复直播分享记录 来源:微信技术团队的公众号WeMobileDev 热 ...

  2. Android热修复-Tinker简析

    一.简介 日常工作工作中难免会遇到项目上线后出现bug问题,如果紧急发版往往由于渠道审核时间问题,导致bug修复不及时,影响用户体验.这时我们需要引入热修复,免去发版审核烦恼. 热更新优势: 让应用能 ...

  3. 热修复系列之一----Android 热修复原理篇及几大方案比较

    热修复说白了就是"即时无感打补丁",比如你们公司上线一个app,用户反应有重大bug,需要紧急修复.2015年以来,Android开发领域里对热修复技术的讨论和分享越来越多,同时也 ...

  4. Android 热修复原理篇及几大方案比较

    热修复说白了就是"即时无感打补丁",比如你们公司上线一个app,用户反应有重大bug,需要紧急修复.2015年以来,Android开发领域里对热修复技术的讨论和分享越来越多,同时也 ...

  5. Android 热修复 Tinker Gradle Plugin解析

    本文已在我的公众号hongyangAndroid原创首发. 转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/72667669 本文 ...

  6. Android 热修复技术浅析

    转载请注明本文出自 clevergump 的博客:http://blog.csdn.net/clevergump/article/details/54782635, 谢谢! 一. 热修复技术简介 什么 ...

  7. Android热修复之 阿里开源的热补丁

    1.概述   上一期讲到Android热修复之 - 收集崩溃信息上传至服务器,我们获取到用户手中上线的崩溃信息上传到服务器后该怎么办?如果直接发布版本要用户去下载肯定不乐意.这一期我们来看一下怎么去打 ...

  8. 【Android 热修复】热修复原理 ( 多 Dex 打包机制 | 多 Dex 支持 | Dex 分包设置 | 开发和产品风格设置 | 源码资源 )

    文章目录 一.Dex 打包设置 1.多 Dex 支持 2.Dex 分包设置 3.开发和产品风格设置 ( 非必须 ) 二.完整 build.gradle 配置 1.build.gradle 配置 2.d ...

  9. 【Android 热修复】热修复原理 ( 合并两个 Element[] dexElements | 自定义 Application 加载 Dex 设置 | 源码资源 )

    文章目录 一.合并两个 Element[] dexElements 二. 完整修复包加载工具类 三. 源码资源 一.合并两个 Element[] dexElements 在 [Android 热修复] ...

最新文章

  1. 简单的dns解析过程
  2. flume bucketpath的bug一例
  3. ​Unity 游戏开发技巧集锦之制作一个望远镜与查看器摄像机
  4. 基于OpenStreetMap计算驾车距离(Java)
  5. 期末考试前的预习,科目:化工设备与反应器(3)
  6. 多层架构模型中的BLL 与 Model的解释
  7. VS2015 中使用 MVC4
  8. 怎么看曲线有没有斜渐近线_?成考结束后,怎么看你有没有被录取?
  9. ubuntu snappy 记事
  10. ps4html5播放器,PS4终获得全新媒体播放器 允许从PC或U盘串流媒体
  11. 双参数cfar c语言代码,一种多目标环境下的SAR图像双参数CFAR检测方法与流程
  12. Java实现 蓝桥杯 算法提高 学霸的迷宫
  13. 关于迭代速度很快的解决方案
  14. 和包贷是什么?究竟如何
  15. Zoom Out and Observe:News Environment Perception for Fake News Detection
  16. 谷歌浏览器显示oracle,css让table不显示边框的代码在火狐和谷歌浏览器中无效
  17. 一个屌丝程序猿的人生(一)
  18. 怎样安装注册金蝶软件
  19. 基于asp.net315家教信息管理系统
  20. 数据分析中的参考系:5大生命周期理论

热门文章

  1. php zhegnze_php 正则表达式
  2. python指针引用的区别_C++基础:指针和引用的区别
  3. 函数return,有些地方你可能还没掌握
  4. 48岁的C语言,你知道它背后的历史吗?
  5. 电机驱动TB6612FNG全网断货,可替代方案来了,文末送模块!
  6. java 防止反射_Java设计模式(一):单例模式,防止反射和反序列化漏洞
  7. 怎么让程序后台运行_CPU中的程序是怎么运行起来的?
  8. node mysql 连接池创建_Node.js使用MySQL连接池的方法实例
  9. @onetoone中被控表不能做自我删除吗?_儿童生活自律表,孩子总是不能坚持,那是因为父母犯了这4个错误...
  10. colmak键盘_Colemak键盘布局学习