ClassLoader实现热修复的示例
2023-02-18 16:34:02 时间
转载请以链接形式标明出处: 本文出自:103style的博客
效果图
实现思路
主要实现思路主要是:
- 先编写一个有 bug 的程序, 运行安装到手机。
- 修正bug之后,重新
rebuild
, 然后找到app - build - intermediates - dex - debug - mergeProjectDexDebug - out - classes.dex
移动到 修复包 下载的目录 , 这里放在assets
目录下,并重命名classes.dex
为classes2.dex
。
- 然后点击程序上的
Move Dex
, 将修正bug之后的dex包 移动到android/data/packagename/
目录下,在这里目录才有加载dex权限
。 - 然后重启程序,在继承自
MultiDexApplication
的Application
中加载对应的dex
文件,获取对应的dexElements
,然后合并到应用的dexElements
之前。
相关代码
MyApplication
public class MyApplication extends MultiDexApplication {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
new FixDemo().loadFixedDex(base);
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
private TextView bugTv;
private int i = 10;
private int a = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bugTv = findViewById(R.id.bug);
bugTv.setText("Bug: " + i + " / " + a);
findViewById(R.id.bug).setOnClickListener(v -> bugMethod());
findViewById(R.id.move).setOnClickListener(v -> moveDex("classes2.dex"));
}
private void bugMethod() {
Toast.makeText(this, "res = " + i / a, Toast.LENGTH_SHORT).show();
}
private void moveDex(String name) {
//目录 data/data/packageName/odex
File fileDir = getDir(FixDemo.DEX_DIR, Context.MODE_PRIVATE);
AssetManager am = getResources().getAssets();
try {
InputStream is = am.open(name);
String filePath = fileDir.getAbsolutePath() + File.separator + name;
File file = new File(filePath);
if (file.exists()) {
file.delete();
}
FileOutputStream os = new FileOutputStream(filePath);
int len;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
os.close();
is.close();
//粘贴完文件
File f = new File(filePath);
if (f.exists()) {
//文件从sk卡赋值到应用运行目录下,成功则toast提示
Toast.makeText(this, "dex移动成功,请重启应用", Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
FixDemo
public class FixDemo {
public static String DEX_DIR = "odex";
private File odexDir;
/**
* 开始替换dex
*/
public void loadFixedDex(Context context) {
if (null == context) {
return;
}
//遍历所有的修复的dex
odexDir = context.getDir(DEX_DIR, Context.MODE_PRIVATE);
File[] listFiles = odexDir.listFiles();
if (listFiles == null) {
return;
}
HashSet<File> loadedDex = new HashSet<>();
for (File file : listFiles) {
if (file.getName().startsWith("classes") || file.getName().endsWith(".dex")) {
//先将补丁文件放到一个集合里,然后再进行合并
loadedDex.add(file);
}
}
//dex合并
doDexInject(context, loadedDex);
}
/**
* dex 合并
*/
private void doDexInject(Context appContext, HashSet<File> loadedDex) {
try {
String filesDirPath = odexDir.getAbsolutePath() + File.separator + "opt_dex";
File fopt = new File(filesDirPath);
if (!fopt.exists()) {
fopt.mkdir();
}
//1.加载应用程序的dex
BaseDexClassLoader pathLoader = (BaseDexClassLoader) appContext.getClassLoader();
for (File dex : loadedDex) {
//2.加载指定的修复的dex文件
DexClassLoader classLoader = new DexClassLoader(
dex.getAbsolutePath(),
fopt.getAbsolutePath(),
null,
pathLoader);
//3.合并
Object dexObj = getPathList(classLoader);
Object pathObj = getPathList(pathLoader);
Object mDexElementsList = getDexElements(dexObj);
Object pathDexElementsList = getDexElements(pathObj);
//将两个list合并为一个
Object dexElements = combineArray(mDexElementsList, pathDexElementsList);
//重写给PathList里面的Element[] dexElements赋值
Object pathList = getPathList(pathLoader);
ReflectUtils.setClassFiled(pathList, pathList.getClass(), "dexElements", dexElements);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private Object getPathList(ClassLoader classLoader) {
try {
return ReflectUtils.getDeclaredField(classLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
private Object getDexElements(Object obj) {
return ReflectUtils.getClassField(obj, "dexElements");
}
/**
* 合并数组
*/
private Object combineArray(Object arrayLbs, Object arrayRhs) {
Class localClass = arrayLbs.getClass().getComponentType();
int i = Array.getLength(arrayLbs);
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(arrayLbs, k));
} else {
Array.set(result, k, Array.get(arrayRhs, k - i));
}
}
return result;
}
}
以上
相关文章
- 在线地图持续进化,BAT技术“鲜”发制人
- 阴阳五行在数据分析中的致命问题2011.11.5
- 知乎15篇书籍推荐帖子中的书名号统计2022.11.7
- 万维钢精英日课4统计学新书《让世界讲得通》2022.11.11
- 双十一天猫分类销售额简单看看2022.11.14
- 《数据与城市》走在理想的边缘2022.11.14
- #每周一本书计划#11月第3周《清单革命》
- @@知乎提问数据分析推荐书籍的统计分析2022.11.21
- 今年冬天是暖冬还是寒冬?拉尼娜2022.11.22
- 继续看今年的天气2022.11.22
- 领略设计模式的魅力,谈谈组合模式搭配访问者模式
- 玩转JVM中的对象及引用:从创建到引用到分配和优化策略
- k50的67w超快充只是前5分钟2022.11.26
- 二手图书多抓鱼的价格和推荐2022.11.29
- 拒绝躺平,如何使用AOP的环绕通知实现分布式锁
- 初探持续监测技术
- 本次降温有多冷2022.12.2
- #每周一本书#《事实》
- 好强的chatGPT,“我失业了”2022.12.8
- 中国香港第五波疫情数据分析2022.12.9