Android 热修复技术

热修复技术一般是对线上bug的紧急处理,不需要二次安装应用,在用户无感知的情况下就可以修复已知的bug。而插件化技术是把需要实现的模块和功能独立提取出来,减少宿主的规模,需要相应的功能时再去加载相应模块。热修复要完成或满足以下三个方面的需求,才能解决行业痛点。

  1. 代码热修复技术
  2. 资源热修复技术
  3. so库热修复技术

1. 代码修复技术

1.1 类加载机制

当我们调用DexClassLoader调用loadDex()的时候,如果不存在odex则会执行dexopt()方法,会先对DexFile每个文件的class文件进行校验和优化,代码如下:

static void verifyAndOptimizeClass (DexFile* pDexFile, ClassObject* clazz, const DexClassDef*                pClassDex, bool doVerity, bool doOpt){ 
    const char* classDescriptor;
    bool verified = false;
    classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdex);
    
    if(doVerify){
        if(dvmVertifyClass(clazz)){ //执行类的Verify
            //类被打上 CLASS_ISPREVERIFIED标志
            ((DexClassDef*)pClassDef)->accessFlags |= CLASS_ISPREVERIFIED;
            verified = true;
        }
    }
    
    if(doOpt){
        bool needVerify = (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED || gDvm.dexOptMode ==                                     OPTIMIZE_MODE_FULL);
        if(!verified && needVerify){
            //......
        } else {
            dvmOptimizeClass(clazz, false); //执行类的optimize
            //类被打上 CLASS_ISOPTIMIZED标志
            ((DexClassDef*)pClassDef)->accessFlags |= CLASS_ISOPTIMIZED; 
        }
    }

}
  • dvmVerifyClass: 类校验,类校验的目的简单来说是为了防止类被篡改校验类的合法性。此时会对类的每个方法进行校验,如果类的所有方法中直接引用到的类和当前类都在同一个dex中的话,dvmVerifyClass就返回true。
  • dvmOptimizeClass:类优化,简单的来说这个过程会把部分指令优化成虚拟机内部指令,然后会把这些指令存入类的vtable表中,需要的话,直接取出来用,会提升执行效率。

如果单纯的将加载好的dex文件放入baseloader的dexelements数组的最前面,则会出现报错,原因是:

ClassObject* dvmResolveClass(const ClassObject* referrer, u4 classIdex, bool fromUnverifiedConstant){
    ......
    //如果类被打上了CLASS_ISPREVERIFIED标志
    if(!fromUnverifiedConstant && IS_CLASS_FLAG_SET(referrer, CLASS_ISPREVERIFIED)){
        if(referrer->pDvmDex != resClassCheck->pDvmDex && resClassCheck->classLoader != null){
            dvmThrowIllegalAccessError("Class ref in pre-verified class resolved to unexpected implementation"); //抛异常
            return NULL;
        }
    }
    ......
}

这就是类加载机制的问题所在,很多大厂都是为了解决这个问题而提出自家的方案。而dalvik虚拟机和art虚拟机的类加载机制又有所不同,dalvik只会从dex elements里面加载一个主dex,其余的在使用到了在加载。而art虚拟机本身就有对dex包有合成的功能,会一起把dex放到压缩文件中,然后依次加载。下面来看看不同厂家对合成dex的解决方案。

1.2 合成方案

首先是QQ空间的方案,他们团队在解决以上问题,用了一个独立的dex包,让其他不需要被标记Vertify标记都引入hack.class来规避以上问题,这样导致的问题是会影响到dalvik虚拟机的加载性能,每次运行这个类的时候,才会校验和优化,art虚拟机在替换的时候,需要把它引用的类以及父类也放在patch.dex包里面,导致合成包很大。

腾讯的tinker方案,主要就是将他们的补丁dex合成到有问题的dex中,然后整体合成一个dex,替换原先的dex elements数组中去,这种方案就不会导致art环境下 合成包很大问题,但是在合成新的dex文件会消耗很多heap内存,可能会出现oom的情况。

阿里的sophix方案,是移除dex文件中对内部类的定义,而类的方法实体以及其他存在dex文件中的信息不移除,这么做的考虑是为了寻找方法的时候,指针发生偏移。这样会就防止这个类被打上vertify的标记,在art虚拟机环境下也有很好的表现。

Qfix方案,他们会在校验dex包提前加载好所需的dex包,然后规避这个缺陷的产生,但是会导致dexopt方法执行不是按正常的逻辑执行的,会导致对class文件优化的时候会写死字段和方法的地址,在多态的情况下会导致调用的是另外一个方法的情况,这种情况就是方法的偏移量发生改变。

1.3 Application的处理

在Android应用启动的时候,Application是最先被加载的,在多dex情况下,主要有两种方法解决问题:

  • 将Application用到所有的非系统类都和Application位于同一个dex包,这样就可以保证pre-verfied标志被打上,避免进入dvmOptResolveClass,在补丁加载完成之后,我们在清除pre-verified标志,使得接下来使用其他类不会报错。
  • Application可以采用反射的方式来访问这个单独类,这样就可以把Application和其他类隔离开了。

第一种方案,我们可以在执行dexopt的结束后把Application类的vertify标志清除掉,然后加载完补丁dex之后,将Application的vertify标志恢复,在attachBaseContext时候,替换dex elements,这样Application初始化的时候就不会报错了。因为如果在Application初始化的时候,发现类vertify标志没有被打上,就会重新执行该类所有引用到的class dvmOptResolveClass方法,等运行到补丁class的时候,又会报错。第二种方案,也是Tinker解决问题的方式,一开始就dexopt TinkerApplication,运行之后然后再通过反射调用Application。

1.4 art环境下合成dex包存在的问题

如果dex包足够大的话,art虚拟机loadDex的时候会将patch.dex和原dex包进行合成一个完整的dex包,这个过程非常的耗时,如果在应用启动的时候,odex文件没有生成的话,会在主线程中去合成,所以不能在应用启动的时候合成。在应用启动的时候,看有没有odex,如果有的话,通过反射注入,如果没有则使用子线程去loadDex,重启后再生效。合成失败要将odex文件给删除掉,同时通过md5对odex包进行校验,看是否被篡改,如果不匹配,重新生成一遍odex。

2. 资源热修复技术

2.1 Instant Run 方式实现资源修复

  • 通过构造一个新的AssertManager,并通过反射调用addAssertPath,把这个完整的新资源包加入到AssertManager中,这样就得到一个含有所有新资源的AssertManager。
  • 找到所有之前引用到原有AssertManager的地方,通过反射,把引用处替换为AssertManager。

这种方式必须要将整个AssertManager全部替换掉原来系统生成的AssertManager,因为不管在Android L之前版本还是之后版本,直接通过addAssertPath是无法生效的。

2.2 sophix方案

由于AssertManager实际处理资源的逻辑都在native层,Java层只是一个引用的地方,完全可以调用native层AssertManager的析构函数,然后重新初始化,将所需要添加到 resource path 添加进去,这样native层就会处理这些资源文件,并不需要向Instant Run那样做很多反射的工作。

sophix构造了package id 为0x66开始的补丁包,这样会导致之前的资源id会发生偏移,有以下三种情况:

  • 新增资源导致id偏移,将资源id改回原来的那个resource id。
  • 内容发生改变的资源,需要改代码将资源id改为新的那个资源id,0x66开头的那个。
  • 删除的资源,不要使用它即可。

3. so库热更新技术

3.1 so库冷部署实现方案

冷部署有两种方案可以实现,一种是通过接口替换的方式,通过加载指定目录下的so去实现;另外一种是类似于类修复反射注入方式,只要把我们的补丁so库的路径插入到PathClassLoader.nativeLibraryDirectories数组的最前面就能够达到加载补丁so库的目的。

3.2 so库热部署实现方案

JNI的注册方式有两种,一种是动态注册另外一种是静态注册,在动态注册的情况下通过加载so的方式,art虚拟机可以完成实时修复,但是dalvik虚拟机则还是返回之前so库的句柄,这种只能通过修改so的名字来规避。

so库的热部署方案对静态注册的方案有一定的局限性,因为虽然可以通过调用以下接口:

static void patchNativeMethod(JNIEnv* env, jclass clz){
     env->UnregisterNatives(clz);
}

不管是静态注册还是动态注册的native方法之前是否执行过加载补丁so的时候都会重新去做映射。但是我们无法知道到底哪个native方法做了修改,而且就算做了修改,我们调用上面的方法,重新load补丁so库也有可能修复,也有可能不会被修复。因为,如果补丁so库在gDvm.nativeLibs的位置在原so库的下面,则不会被修复。而且还有个问题,就是受到dex的影响,如果so对应的方法在dex包中没有的话,会抛 NoSuchMethod的异常。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容