9012年都过去了,你确定还不学安卓的热修复?(手写AndFix)

简介: 在native层进行方法的替换,将错误的方法替换为正确的方法

背景介绍

热修复,乍一听,感觉好牛逼的样子,实际上并没有多么神秘,为什么这样说呢?且听我娓娓道来。。。

你发布了一款安卓应用,早上刚发版,结果发完之后发现有个bug没有修复,会直接导致整个应用崩溃,这时候你该怎么办呢?难道再马上重新打包发版吗?显然是不现实的,那么这时候热修复就来了,帮你打上一个补丁(没错,我认为热修复就像给衣服打补丁。。。),然后在你应用启动的时候直接进行修补,这样就可以不用发版了啊。

听上去感觉有点懵,怎么打补丁,应用怎么提前知道我哪里代码出问题了?什么是热修复?这都是啥?我是谁?我在哪???

aHR0cHM6Ly9kc3MyLmJkc3RhdGljLmNvbS83MGNGdm5TaF9RMVlueEdrcG9XSzFIRjZoaHkvaXQvdT0xNjA2ODAzNTQ3LDM4NDI2MjkzMDcmZm09MjYmZ3A9MC5qcGc.png

不着急,咱们慢慢来,先来看一个目前来说整个市场上的热修复方案的特性吧。

20200104221746436.png

预热

上面啰嗦了一大堆,其实最重要的就是上面这张图,这张图也比较老了,现在都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++的项目:

20200104230417276.png



直接选择这个:

20200104230456614.png



咱们先来模仿一个崩溃,直接抛出异常:

/**
 * @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需要配置全局变量:

20200104233408991.png

然后在path中也同样配置一下,就可以在cmd中直接进行使用了。打开cmd,命令是:

dx --dex --output 要打包的路径/名字.dex 源文件路径(即你通过build出的class文件)

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基本原理已经实现。“别光写不练啊,运行试试啊!”


好嘞,咱们来看一下运行效果吧:


20200105000002717.gif

文末

本来只是想简单总结一下,没想到越写越多,本来还打算写一下阿里的正宗的AndFix的使用流程,放到下一篇文章吧


 


目录
相关文章
|
6月前
|
移动开发 监控 安全
mPaaS常见问题之Android集成dexPatch热修复运行时候无法正常进行热更新如何解决
mPaaS(移动平台即服务,Mobile Platform as a Service)是阿里巴巴集团提供的一套移动开发解决方案,它包含了一系列移动开发、测试、监控和运营的工具和服务。以下是mPaaS常见问题的汇总,旨在帮助开发者和企业用户解决在使用mPaaS产品过程中遇到的各种挑战
106 0
|
安全 Java Shell
Android的热修复技术--阿里的hotfix试用
Android的热修复技术--阿里的hotfix试用
|
Java AndFix 开发工具
毕业5年了还不知道Android热修复?
随着移动端业务复杂程度的增加,传统的版本更新流程显然无法满足业务和开发者的需求, 热修复技术的推出在很大程度上改善了这一局面。国内大部分成熟的主流 App都拥有自己的热更新技术,像手淘、支付宝、微信、QQ、饿了么、美团等。
毕业5年了还不知道Android热修复?
|
jenkins Shell 持续交付
浅谈Android热修复的前因后果与实现原理。(下)
最近在集成热修复,正好要进行技术分享,所以就来好好梳理一下 热修复的前因后果。
164 0
|
存储 移动开发 缓存
浅谈Android热修复的前因后果与实现原理。(上)
最近在集成热修复,正好要进行技术分享,所以就来好好梳理一下 热修复的前因后果。
190 0
|
存储 缓存 Java
【组件健壮性】Android Java代码热修复的原理
总结Android Java代码三种热修复方式,包括自定义ClassLoader、插桩式、底层替换,并给出原理和实施流程。
|
Android开发 开发者
《深入探索Android热修复技术原理》电子版地址
热修复技术作为安全类技术的标志性衍生产物,其进入到应用化领域开始大行其道时,标志着平台发展开始迈入新阶段,这也意味着应用市场的繁荣程度、应用开发者的思维和研发模式也进入到游戏的下半程。
201 0
《深入探索Android热修复技术原理》电子版地址
|
安全 Java Go
Android三步集成阿里热修复——Sophix
Android三步集成阿里热修复——Sophix
783 0
Android三步集成阿里热修复——Sophix
|
Android开发
|
安全 算法 Java
Android 腾讯热修复 Tinker + Flutter
Android 腾讯热修复 Tinker + Flutter
1045 0
Android 腾讯热修复 Tinker + Flutter