Bug
需要及时修复的时候,如果按照传统的方式,这就需要去解决 Bug
、测试打包重新发布,而用户也需要重新安装你发布的新版本才能解决这个 Bug
,使用这个时候可以使用热修复去进行及时修复,而且不需要发布新的版本,只需要发布补丁包,在客户不知不觉间修复掉 Bug
。对比 | Bugly | Sophix |
---|---|---|
前世今生 | 腾讯在 Tinker 基础上开发的商业级框架 Bugly | 阿里的 AndFix 基础上开发的商业级框架 Sophix |
及时生效 | 否,仅支持冷启动修复 | 是,支持实时修复和冷启动修复 |
方法替换 | 是 | 是 |
类替换 | 是 | 是 |
类结构修改 | 是 | 否 |
资源替换/更新 | 替换 | 更新 |
so 替换/更新 | 替换 | 更新 |
支持 gradle | 支持 | 不支持 |
支持 ART | 支持 | 支持 |
支持 Android 7.0 | 支持 | 支持 |
地址 | Tinker-GitHub 、Bugly 接入文档 | Sophix 接入文档 |
Instant Run
是 Android studio 2.0
以后新增的一个运行机制,能够显著减少开发人员第二次及以后的构建和部署时间。App
,这显然会很耗时,而 Instant Run
的构建和部署都是基于更改的部分的,且无需重新安装 App
。Instant Run
部署有三种方式:App
,甚至不需要重启当前的 Activity
。修改一个现有方法的代码时会采用该方式。App
不需要重启,但是 Activity
需要重启。修改或删除一个现有的资源文件可采用该方式。App
需要重启,但是不需要重新安装。采用该方式的情况很多,例如添加、删除和修改一个字段和方法,添加一个类等。ClassLoader
没有必要再次加载。Android
平台上虚拟机运行的是Dex
字节码,一种对 class
文件优化的产物,Java
源文件可以通过 javac xxx.java
编译生成一个 .class
文件,而 Android
是把所有 Class
文件进行合并和优化,然后生成一个最终的 class.dex
,目的是把不同 class
文件重复的东西只需保留一份。如果我们的 Android
应用不进行分 dex
处理,这样一个应用的 apk
只会有一个 dex
文件。Android
中常用的有两种类加载器,DexClassLoader
和 PathClassLoader
,它们都继承于 BaseDexClassLoader
。区别在于调用父类构造器时,DexClassLoader
多传了一个 optimizedDirectory
参数,这个目录必须是内部存储路径,用来缓存系统创建的 Dex
文件。而 PathClassLoader
该参数为 null
,只能加载内部存储目录的 Dex
文件。所以我们可以用 DexClassLoader
去加载外部的 apk
。apk
做 diff
操作得到 补丁.dex
文件,再将 补丁.dex
和 bug.dex
做合并操作得到 已修复.dex
文件,再利用 DexClassLoader
类加载器,根据 DexPathList
的 findClass()
方法,采取运行时反射注入的形式,将 已修复.dex
插入到 dexElements
数组的第 0
个位置,从而实现代码的修复。(代表:Tinker
)// /libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
public Class<?> findClass(String name, List<Throwable> suppressed) {for (Element element : dexElements) {Class<?> clazz = element.findClass(name, definingContext, suppressed);if (clazz != null) {return clazz;}}if (dexElementsSuppressedExceptions != null) {suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));}return null;
}
Native
层修改原有类,限制为不能增减原有类的方法和字段。ART
虚拟机方法的 ArtMethod
结构体,该结构体中包含了 Java
方法的所有信息,包括执行入口、访问权限、所属类和代码执行地址等。像 Sophix
采用的就是替换掉整个 ArtMethod
结构体,从而实现代码的修复。ActivityThread
中所有 LoadApk
中的 resDir
的值替换成新合成的资源文件路径(获取 Resources
时,会以 LoadApk
中的 resDir
作为 key
去 ResourcesManager
中获取);AssetManager
,并把资源补丁 apk
加载进新的 AssetManager
中;ResourcesManager
中所有 Resources
对象中 AssetManager
替换成我们新建的 AssetManager
,那么所有的 Resources
对象获取到的都是新合成的资源文件。Android
现在常见且使用的 cpu
架构 armeabi-v7a(32位)
和 arm64-v8a(64位)
。Android
加载 so
库的两种方式:System.load(String pathName)
:传进去的参数:so
库在磁盘中的完整路径, 加载一个自定义外部 so
库文件 。System.loadLibrary(String libName)
:传进去的参数:so
库名称, 表示的 so
库文件,位于 apk
压缩文件中的 libs
目录,最后复制到 apk
安装目录下。so
修复就是利用 System.loadLibrary(xxx)
方法,思路如下:cpu
架构对应 so
文件到指定目录;copy so
文件到可动态加载的文件目录下(/data/data/packageName
);gradle
,指定 cpu
架构;load
加载,利用 System.loadLibrary(xxx)
方法。System#loadLibrary(libxxx.so)
如何加载 so
库的呢?PathClassLoader
类加载器,根据 DexPathList
的 findLibrary()
方法,采取运行时反射注入的形式,在 sdk < 23
时,将我们的补丁 so
库路径插入到 nativeLibraryDirectories
数组的第 0
个位置,在 sdk >= 23
时,将我们的补丁 so
库路径插入到 nativeLibraryPathElements
数组的第 0
个位置,从而达到修复的目的。// /libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
// sdk < 23 时对应的代码:
private final File[] nativeLibraryDirectories;
public String findLibrary(String libraryName) {String fileName = System.mapLibraryName(libraryName);for (File directory : nativeLibraryDirectories) {String path = new File(directory, fileName).getPath();if (IoUtils.canOpenReadOnly(path)) {return path;}}return null;
}
// sdk >= 23 时对应的代码:
NativeLibraryElement[] nativeLibraryPathElements;
public String findLibrary(String libraryName) {String fileName = System.mapLibraryName(libraryName);for (NativeLibraryElement element : nativeLibraryPathElements) {String path = element.findNativeLibrary(fileName);if (path != null) {return path;}}return null;
}
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态