背景介绍
热修复,乍一听,感觉好牛逼的样子,实际上并没有多么神秘,为什么这样说呢?且听我娓娓道来。。。
你发布了一款安卓应用,早上刚发版,结果发完之后发现有个bug没有修复,会直接导致整个应用崩溃,这时候你该怎么办呢?难道再马上重新打包发版吗?显然是不现实的,那么这时候热修复就来了,帮你打上一个补丁(没错,我认为热修复就像给衣服打补丁。。。),然后在你应用启动的时候直接进行修补,这样就可以不用发版了啊。
听上去感觉有点懵,怎么打补丁,应用怎么提前知道我哪里代码出问题了?什么是热修复?这都是啥?我是谁?我在哪???
不着急,咱们慢慢来,先来看一个目前来说整个市场上的热修复方案的特性吧。
预热
上面啰嗦了一大堆,其实最重要的就是上面这张图,这张图也比较老了,现在都Android 10 了。。。我还弄的7的图。。。将就看吧,意思能表达清楚就行。
目前的热修复大致分为两个方案:一种是native层的,代表的是阿里的AndFix(停更好几年)和Sophix(不开源),另外一种就是java层的,代表的是腾讯的Tinker(开源)。今天准备模仿的是阿里的AndFix。
既然要模仿AndFix,那么就来说一下AndFix的优势吧:首先它打出的修复包要比Tinker打出的小很多(精确到方法),其次它的性能消耗代价要小,最重要的是:它及时生效,无需退出应用重新进入即可修复。
我们都知道:Java方法的执行一定有相应的入口(包括普通执行,亦或通过反射执行)。那么可以思考一下AndFix是怎样工作的?安卓中Java文件编译成class后会打成dex包,方法即存在于dex包中。dex包是在虚拟机中执行的,虚拟机是c/c++编写的,虚拟机在执行方法时在安卓源码中存在着成员变量表和方法表,而方法表中存在着一个结构体,我们的方法都是由这个结构体来保存执行的,这个结构体就是ArtMethod。那么我们需要做的就是:在native层进行方法的替换,将错误的方法替换为正确的方法即可。
当然,虚拟机在安卓4.4以下和5.0以上有了翻天覆地的变化,在4.4及以前,虚拟机为Davik,它采用的是JIT(即时编译);5.0以上虚拟机为Art,采用的是AOT(预编译)。两者区别就是Art安装应用时慢,加载快,Davik安装应用快,加载慢。(细心的肯定发现了安卓4.4及以前的安卓版本安装应用要比现在快很多)。但是今天不考虑Davik,因为现在的手机基本没有4.4及以下的版本了,就不做适配了。这里还要说的是,AndFix热修复基于的是安卓源码中的结构体(art_method.h),所以说国内某些厂商对安卓系统进行魔改了,有可能修复失败;还有就是每一个版本的安卓系统中的源码都不同,需要适配来进行解决,否则会修复失败。
开始编码
我也没想到我能写出上面那么多字,好了,终于到了编码的时候了。来新建一个c++的项目:
直接选择这个:
咱们先来模仿一个崩溃,直接抛出异常:
-
/**
-
* @ProjectName: Andfix
-
* @Package: com.zj.andfix
-
* @Author: jiang zhu
-
* @Date: 2020/1/2 21:25
-
*/
-
public
class Caclutor {
-
-
public void test(Context context){
-
throw
new RuntimeException(
"报错了");
-
}
-
-
}
在MainActivity中进行调用,模仿现实中的崩溃:
-
public void test(View view) {
-
Caclutor caclutor =
new Caclutor();
-
caclutor.test(
this);
-
}
再来模仿写一个解决完bug的类:
-
/**
-
* @ProjectName: Andfix
-
* @Package: com.zj.andfix
-
* @Author: jiang zhu
-
* @Date: 2020/1/2 21:25
-
*/
-
public
class Caclutor {
-
-
public void test(Context context){
-
//throw new RuntimeException("报错了");
-
Toast.makeText(context,
"修复成功了", Toast.LENGTH_SHORT).show();
-
}
-
-
}
接下来要写一个注解,我们要获取到是哪个类和哪个方法出了问题:
-
/**
-
* @ProjectName: Andfix
-
* @Package: com.zj.andfix
-
* @Author: jiang zhu
-
* @Date: 2020/1/2 21:18
-
*/
-
@Retention(RetentionPolicy.RUNTIME)
-
@Target(ElementType.METHOD)
-
public
@interface Replace {
-
-
//类的全限定名
-
String path();
-
-
//方法名
-
String method();
-
-
}
写好注解之后在修复类中加上注解:
-
@Replace(path =
"com.zj.andfix.Caclutor",method =
"test")
-
public void test(Context context){
-
//throw new RuntimeException("报错了");
-
Toast.makeText(context,
"修复成功了", Toast.LENGTH_SHORT).show();
-
}
接下来就到了最重要的一步,打出修复包,咱们先把错误的代码打一个apk包(release),然后再把修复好的代码打一个aok包。咱们需要打的是一个dex文件,需要使用到安卓sdk中的工具,进入你的sdk/build-tools/版本/dx.bat,这个dx.bat就是咱们需要使用的工具。想要全局使用dx.bat需要配置全局变量:
然后在path中也同样配置一下,就可以在cmd中直接进行使用了。打开cmd,命令是:
dx --dex --output 要打包的路径/名字.dex 源文件路径(即你通过build出的class文件)
执行完命令之后生成了修复包,咱们把这个修复包直接放入测试机的根目录,真实开发中肯定放在私密目录。
最最重要的来了
咱们需要一个工具类来加载咱们的修复包,需要用到上下文,所以可以直接传入:
-
/**
-
* @ProjectName: Andfix
-
* @Package: com.zj.andfix
-
* @Author: jiang zhu
-
* @Date: 2020/1/2 21:39
-
*/
-
public
class DexManager {
-
private Context context;
-
static {
-
System.loadLibrary(
"native-lib");
-
}
-
public void setContext(Context context) {
-
-
this.context = context;
-
}
-
-
}
别忘了加载native-lib。接下来需要一个方法来加载我们的修复包:
-
public void load(File file) {
-
try {
-
DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),
-
new File(context.getCacheDir(),
"opt").getAbsolutePath(),
-
Context.MODE_PRIVATE);
-
Enumeration<String> entry= dexFile.entries();
-
while (entry.hasMoreElements()) {
-
// 全类名
-
String className = entry.nextElement();
-
Class realClazz=dexFile.loadClass(className, context.getClassLoader());
-
if (realClazz !=
null) {
-
fixClass(realClazz);
-
}
-
// Class.forName(className);
-
-
}
-
}
catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
下面简单说一下上面方法的意思:先通过传进来的File文件获取到一个DexFile文件,然后遍历里面所有的类,获取到修复包中类的全限定名,通过loadClass获取到修复类,如果类不为空,则进行修复,下面是fixClass方法的代码:
-
private void fixClass(Class realClazz) {
-
//加载方法 Method
-
Method[] methods = realClazz.getMethods();
-
for (Method rightMethod : methods) {
-
Replace replace = rightMethod.getAnnotation(Replace.class);
-
-
if (replace ==
null) {
-
continue;
-
}
-
-
String clazzName = replace.path();
-
String methodName = replace.method();
-
-
try {
-
Class wrongClazz=Class.forName(clazzName);
-
//Method right wrong
-
Method wrongMethod=wrongClazz.getDeclaredMethod(methodName, rightMethod.getParameterTypes());
-
replace(wrongMethod, rightMethod);
-
-
}
catch (Exception e) {
-
e.printStackTrace();
-
}
-
-
}
-
}
上面的代码首先获取到类中所有的方法,然后进行遍历,获取方法上咱们定义的注解,如果有自定义注解的画,获取类的全限定名和方法名,获取到正确的方法和错误的方法。接下来就交给了replace方法:
public native void replace(Method wrongMethod, Method rightMethod);
replace方法是一个native方法,需要写c++来实现了,到这里咱们需要引入安卓源码中的ArtMethod.h头文件了(上面讲到过,注意,只需引入结构体的代码,其他删掉即可,全部引用的话代码太多,一层套一层,会把源码都搬过来的。。。),下面是ArtMethod.h头文件的代码,大家可以直接进行复制,或者去最新的安卓源码中去复制:
-
#include <stdint.h>
-
-
namespace art{
-
namespace mirror{
-
class Object{
-
uint32_t klass_;
-
uint32_t monitor_;
-
-
};
-
class ArtMethod:
public Object{
-
public:
-
uint32_t access_flags_;
-
uint32_t dex_code_item_offset_;
-
uint32_t dex_method_index_;
-
uint32_t method_index_;
-
uint32_t dex_cache_resolved_methods_;
-
uint32_t dex_cache_resolved_types_;
-
uint32_t declaring_class_;
-
};
-
}
-
}
万事俱备,之前东风,最后需要的就是在c++中进行方法的替换了:
-
extern
"C"
-
JNIEXPORT void JNICALL
-
Java_com_zj_andfix_DexManager_replace
(JNIEnv *env, jobject thiz, jobject wrongMethod,
-
jobject rightMethod) {
-
art::mirror::ArtMethod *wrong=
reinterpret_cast<art::mirror::ArtMethod *>(env->FromReflectedMethod(wrongMethod));
-
art::mirror::ArtMethod *right=
reinterpret_cast<art::mirror::ArtMethod *>(env->FromReflectedMethod(rightMethod));
-
-
// wrong=right;
-
wrong->declaring_class_ = right->declaring_class_;
-
wrong->dex_cache_resolved_methods_ = right->dex_cache_resolved_methods_;
-
wrong->access_flags_ = right->access_flags_;
-
wrong->dex_cache_resolved_types_ = right->dex_cache_resolved_types_;
-
wrong->dex_code_item_offset_ = right->dex_code_item_offset_;
-
wrong->dex_method_index_ = right->dex_method_index_;
-
wrong->method_index_ = right->method_index_;
-
}
至此,AndFix基本原理已经实现。“别光写不练啊,运行试试啊!”
好嘞,咱们来看一下运行效果吧:
文末
本来只是想简单总结一下,没想到越写越多,本来还打算写一下阿里的正宗的AndFix的使用流程,放到下一篇文章吧,之后再写写腾讯的Tinker。周六的晚上写到了周日,也是没谁了,好了,准备洗漱,睡觉。晚安了陌生人。
转载:https://blog.csdn.net/haojiagou/article/details/103838377