目前国内Android热修复技术已经发展的可以说百花齐放了,从实现方式来大致分类,可以分为:
-
Native层实现
-
Java层实现
之前也有人简单分析过阿里开源的Andfix
实现原理(基于Native层),这里就不再多说了感兴趣的可以搜索看下。
本文简单分析一下Java层实现热修复逻辑,简单实现代码热修复Demo,以Tinker为例(当然Tinker
是支持代码修复,资源修复,so修复
的,感兴趣的小伙伴自行移步官网~)
先梳理下思路:
=====================================================================
就是java类通过javac编译成.class文件,再由dx.bat编译成.dex文件的过程,不做赘述,简单画张图~
=========================================================================
Android中的java.lang.ClassLoader这个类也不同于Java中的java.lang.ClassLoader。 Android中的ClassLoader类型也可分为系统ClassLoader和自定义ClassLoader。其中系统ClassLoader包括3种分别是:
-
BootClassLoader
,Android系统启动时会使用BootClassLoader来预加载常用类,与Java中的Bootstrap ClassLoader不同的是,它并不是由C/C++代码实现,而是由Java实现的。BootClassLoader是ClassLoader的一个内部类。 -
PathClassLoader
,全名是dalvik/system.PathClassLoader,可以加载已经安装的Apk,也就是/data/app/package 下的apk文件,也可以加载/vendor/lib, /system/lib下的nativeLibrary。 -
DexClassLoader
,全名是dalvik/system.DexClassLoader,可以加载一个未安装的apk文件。 PathClassLoader和DexClasLoader都是继承自 dalviksystem.BaseDexClassLoader,它们的类加载逻辑全部写在BaseDexClassLoader中。 下图展示了Android中的ClassLoader中的继承体系,其中SecureClassLoader和UrlClassLoader是在Java中的类加载器,在Android中是没法办使用的。
====================================================================
通过源码得知,.dex
文件是通过BaseDexClassLoader类(ClassLoader的子类)
进行加载的,这个类里面有个成员变量DexPathList对象
,而这个对象中有个有个数组存放的是DexElement
对象,也就是从文件中加载的.dex
文件,切入点就在这里
对于项目来说,一般项目会分包(方法数大于64k,及大于65535个时候,google提供的分包策略),如果采用Java代码实现热修复,分包是肯定要做的,因为要保证主包没有bug,分包简单说就是打包的apk一般有多个.dex
文件
如: classes.dex,classes2.dex 等等…
那么比如我们的classes2.dex
中某个类的某个方法出现了异常,我们就可以创建一个修复包(修复后的classes2.dex
文件),然后通过自定义的类加载器将修复后的classes2.dex
文件copy到私有目录,再插队到系统ClassLoader
的dexPathList对象
的dexElement
的数组中,让系统优先加载修复后的classes2.dex
文件,以做到热修复的目的,这种实现方式必须要执行修复逻辑后,重启app才能实现效果~
了解了这些信息大致思路就有了,我们需要修复后的.dex文件加载解析,然后插队旧的安装包装的.dex文件,做到插队的操作,相当于欺骗了Android系统,大致如下:
================================================================
思路大概是,我们需要一个修复bug的.dex文件,插队到BaseDexClassLoader类
下的DexPathList对象
的DexElement
数组中,并且排序到最前面,让系统加载到我们修复后的.dex文件不会再加载有bug的dex文件,完成插队(插装),这里会有个类加载机制的知识,本文不做详细介绍,后面会单独写一篇总结~ 大致实现步骤如下:
==================================================================
1、基础配置-主包配置
配置分包,配置分包的目的主要是打包做出来的apk会有多个.dex文件,实际项目应用中要保证主包不要有bug,demo中加载.dex文件的时候也排除了主包文件classes.dex
,大致如下: 创建BaseApplication
,BaseActivity
,MainActivity
放置在主包,其中MainActivity
主要是为了分包占位,只做了点击跳转分包中SecondActivity
的逻辑 app目录下的build.gradle
开启分包支持,在android
→defaultConfig
下增加配置,其中multiDex-config.txt
是配置保留在主包内类文件
//开启分包
multiDexEnabled true
//分包的配置,将配置文件中的放置在主包
multiDexKeepFile file(“multiDex-config.txt”)
添加分包依赖:
//multidex分包依赖
implementation ‘com.android.support:multidex:1.0.3’
Application开启分包:
public class BaseApplication extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//安装分包配置
MultiDex.install(this);
}
}
2、分包配置
分包就创建了一个SecondActivity类
做模拟异常和修复异常的入口,和一个Calculate
模拟异常,做了10/0
的操作,修复后为10/1
注:获取修复后的
classes2.dex
文件可以通过直接buildapk直接解压获取,或者用build-tools下的dx.bat
执行命令获取
简单贴下SecondActivity
,点击fix
按钮后的代码:
private void update() {
//将下载的修复包,复制到私有目录,解压从.dex文件中取到对应的.class文件
//从sd卡取修复包
File sourceFile = new File(Environment.getExternalStorageDirectory(), Constants.DEX_NAME);
//目标文件
File targetFile = new File(getDir(Constants.DEX_DIR, Context.MODE_PRIVATE).getAbsolutePath() + File.separator + Constants.DEX_NAME);
if (targetFile.exists()) {
targetFile.delete();
Log.e(“update”,“删除原有dex文件(已使用的)”);
}
//将SD卡中的修复包copy到私有目录
FileUtils.copyFile(sourceFile,targetFile);
Log.e(“update”,“copy完成”);
FixDexUtils.loadDexFile(this);
}
3、FixModule
新建一个Module处理热修复的相关逻辑
只有五个文件,核心文件代码在FixDexUtils
,其它是工具类,还有个定义了几个常量 看下FixDexUtils
中的代码
public class FixDexUtils {
//修复文件可能有多个
private static HashSet loadedDex = new HashSet<>();
//不建议这么写,demo演示用
static {
loadedDex.clear();
}
public static void loadDexFile(Context context) {
//获取私有目录
File fileDir = context.getDir(Constants.DEX_DIR, Context.MODE_PRIVATE);
//遍历筛选私有目录中的.dex文件
File[] listFiles = fileDir.listFiles();
for (int i = 0; i < listFiles.length; i++) {
//文件名以.dex结尾,且不是主包.dex文件
if (listFiles[i].getName().endsWith(Constants.DEX_SUFFIX) && !“classes.dex”.equalsIgnoreCase(listFiles[i].getName())) {
loadedDex.add(listFiles[i]);
}
}
//创建自定义的类加载器
createDexClassLoader(context ,fileDir);
}
最后
这里我特地整理了一份《Android开发核心知识点笔记》,里面就包含了自定义View相关的内容
除了这份笔记,还给大家分享 Android学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这几块的内容。非常适合近期有面试和想在技术道路上继续精进的朋友。
分享上面这些资源,希望可以帮助到大家提升进阶,如果你觉得还算有用的话,不妨把它们推荐给你的朋友~
喜欢本文的话,给我点个小赞、评论区留言或者转发支持一下呗~
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
d学习PDF+架构视频+面试文档+源码笔记**,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这几块的内容。非常适合近期有面试和想在技术道路上继续精进的朋友。
[外链图片转存中…(img-5KDnHe28-1714547811993)]
分享上面这些资源,希望可以帮助到大家提升进阶,如果你觉得还算有用的话,不妨把它们推荐给你的朋友~
喜欢本文的话,给我点个小赞、评论区留言或者转发支持一下呗~
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!