Android5.0以下手机上关于类冲突的总结

问题描述

我们使用分层的方式接入一些第三方库,上层只调用下层的接口,完全不清楚下层的具体细节,当方法数超出65535时,由于是我们是先接入接口服务层SDK生成APK后再使用一个工具加入第三方库的具体实现,默认将第三方库的所有实现放到了第二个Dex中,这时发现当第一个Dex和第二个Dex中包含了相同的Jar(比如support-v4,gson等),在Android5.0上在使用相关功能时会崩溃,抛出的异常是:

Android5.0以下手机上关于类冲突的总结_第1张图片

主要是一个类检查的错误。

问题分析

问题分析: 分层机制使得应用在接入SDK接口服务层时无法接触到第三方SDK具体实现的Jar,这使得应用在无法发觉的情况下会添加一些和第三方SDK冲突的Jar,这里的冲突指的是和第三方SDK包含有相同package的class的Jar,但是两边的Jar的版本可能不同,也就是class中的内容可能不同。

目前打包工具对于应用APK中的dex加上SDK具体实现方法数超过65535的做法是,直接将第三方SDK的所有jar编译为classes2.dex,然后使用google的aapt工具添加到应用APK中去,应用的Applicaiton被修改或者继承自MultidexApplication,在应用启动时安装classes2.dex。这样APK中存在两个dex文件,而且两个dex文件很容易出现冲突的类。

这种情况在Android5.0以上的ART模式中不会有问题,原因是ART会在安装时将多个dex合并生成ODEX文件,当某个加载出来的类调用另外一个类时会在本dex中加载调用,如果本dex中没有再到另外一个dex种寻找该类,而且引用类和要装载的类即使不来自于同一个dex也不会报错。

但是这种情况在Android5.0以下的Dalvik虚拟机上就会出现了问题,原因是虚拟机加载类时有个
dvmResolveClass方法,这个方法对Class进行了校验。判断这个要Resolve的class是否和其引用来自一个dex。如果不是,返回false后Dalvik就会跑出异常并崩溃。

之前出现该问题时的临时解决方法是:将第三方SDK中和应用冲突的jar编译成smali文件,在反编译应用APK后,强制将第三方SDK的smali覆盖到应用的smali中,也就是第三方SDK的代码覆盖掉了应用的代码。当时出包后暂时没有测试出其它问题。但是该解决方案存在两个问题:
1. 当应用的Jar和第三方SDK的Jar版本差异较大时,可能会导致应用使用该功能时崩溃。
2. 如果应用主dex的方法数较多,当覆盖后会造成主dex的方法数依然超过65535,无法编译出APK。

比较完善的解决方案

目前对于运行时加载dex是有很多的成熟的解决方案的,比如阿里的百川HotFix,通过研究HotFix的原理(http://blog.csdn.net/xwl198937/article/details/49801975),提出如下解决方案:
1. 通过开源的Hook框架(目前暂定是Cydia Hook),应用启动时检测到系统是5.0以下时,引用Cydia Hook技术来hook Native dalvik中dvmResolveClass这个方法,直接返回true,也就是跳过的原始的检测。
2. 在SDK接口服务层封装第三方SDK时,将第三方SDK需要在Applicaiton中初始化并加载的jar通过Ant脚本放到资源包的mainsmali文件夹中,该文件夹会在打包工具中复制到反编译后的APK的smali中。

这样做的风险:

主要风险
1, 跳过了Dalvik的中dvmResolveClass的检查后,在Android 5.0以下系统的表现是对于冲突的类只加载第二个Dex中的类,这时如果两个Dex中的相同package中的类差异很大,比如方法数不同,方法实现不同等,应用可能会使用该类时直接Crash或者行为异常。
2. Cydia Hook的性能,稳定性问题,但是据我所知,目前有些公司已经用它来实现apk加壳和脱壳防止反编译技术方案。具体可以参考:
http://www.cydiasubstrate.com/
而且Cydia Hook的so只有134kb,多于APK大小不会造成太大影响,提供了源代码,可以进行更加深入的研究。
3. 需要通过工作经验的方式在封装第三方SDK时手动添加必须放在mainsmali文件夹的jar。但是脚本已经优化的比较简单,可以通过正则匹配的方式查找Jar,即第三方SDK在封装时只需要修改一次,之后升级时就不需要再次修改了,除非第三方SDK进行了非常大的改动。

这样做的优势:

这样只将必须在Application中加载的类放在应用的主dex中,减少了主dex方法数超出65535的可能,而且只在系统是5.0以下时启动Hook方案,对于5.0以上原生支持multidex的系统不造成任何影响。

具体实现

Cydia Hook技术来hook Native dalvik中dvmResolveClass这个方法,直接返回true,也就是跳过的原始的检测。然后使用Android提供的support-mulitdex安装Dex文件,具体实现在:
https://github.com/blueiceheaven/ndk-patch.git

在应用的Application中的attachBaseContext回调中调用Cydia Hook,:

public class MyApplication extends Application {
    public MyApplication() {
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        PatchDex.install(this);
        HookBridge.initJNIEnv();
        PatchDex.addAllDexFile(base, HookManager.getInstance().getPatchDir(base).getAbsolutePath(),
                HookManager.getInstance().getPatchOptDir(base).getAbsolutePath(), false);
    }
}

你可能感兴趣的:(android基础知识,java)