注册
so

Android so文件的加载原理




  1. 先说说so的编译类型
    Android只支持3种cpu架构分为:arm,mips,x86,目前用的最多的是arm体系cpu,x86和mips体系的很少用到了。
    arm体系中,又分32位和64位:

    armeabi/armeabi-v7a:这个架构是arm类型的,主要用于Android 4.0之后的,cpu是32位的,其中armeabi是相当老旧的一个版本, 缺少对浮点数的硬件支持,基本已经淘汰,可以不用考虑了。

    arm64-v8a:这个架构是arm类型的,主要是用于Android 5.0之后,cpu是64位的。平时项目中引入第三方的so文件时,第三方会根据cpu的架构编译成不同类型的so文件,项目引入这些so文件时,会将这些文件分别放入jniLibs目录下的arm64-v8a,armeabi-v7a等这些目录下,其实对于arm体系的so文件,没这个必要,因为arm体系是向下兼容的,比如32位的so文件是可以在64位的系统上运行的。Android上每启动一个app都会创建一个虚拟机,Android 64位的系统加载32位的so文件时,会创建一个64位的虚拟机的同时,还会创建一个32位的虚拟机,这样就能兼容32位的app应用了。鉴于兼容的原理,在app中,可以只保留armeabi-v7a版本的so文件就足够了。64位的操作系统会在32位的虚拟机上加载这个它。这样就极大的精简了app打包后的体积。虽然这样可以精简apk的体积,但是,在64位平台上运行32位版本的ART和Android组件,将丢失专为64位优化过的性能(ART,webview,media等等)所以,更好的方法是,为相应的abi打对应的apk包,这样就可以为不同abi版本生成不同的apk包。具体在build.gradle中的配置如下:



android {

...

splits {
abi {
enable true
reset()
include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' //select ABIs to build APKs for
universalApk true //generate an additional APK that contains all the ABIs
}
}

// map for the version code
project.ext.versionCodes = ['armeabi': 1, 'armeabi-v7a': 2, 'arm64-v8a': 3, 'mips': 5, 'mips64': 6, 'x86': 8, 'x86_64': 9]

android.applicationVariants.all { variant ->
// assign different version code for each output
variant.outputs.each { output ->
output.versionCodeOverride =
project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) * 1000000 + android.defaultConfig.versionCode
}
}
}


  1. so的加载流程
    可以通过以下命令来查看手机的cpu型号(以OPPO R7手机为例),在AS中的Terminal窗口中,输入如下命令

  C:\Users\xg\Desktop\AndroidSkill>adb shell
shell@hwmt7:/ $ getprop ro.product.cpu.abilist
arm64-v8a,armeabi-v7a,armeabi

手机支持的种类存在一个abiList 的集合中,有个前后顺序,比如我的手机,支持三种类型, abiList 的集合中就有三个元素,第一个元素是arm64-v8a ,第二个元素是armeabi-v7a,第三个元素是armeabi 。按照这个先后顺序,我们遍历jniLib 目录,如果这个目录下有arm64-v8a子目录并且里面有so文件,那么接下来将加载arm64-v8a下的所有so文件,就不再去看其他子目录(比如armeabi-v7a)了,以此类推。在我的手机上,如果arm64-v8a 下有a.so,armeabi-v7a下有a.so和b.so那么我的手机只会加载arm64-v8a下的a.so,而永远不会加载到b.so,这时候就会抛出找不到b.so的异常,这是由Android 中的so加载算法导致的。因此,为了节省apk的体积,我们只能保存一份so文件,那就是armeabi-v7a下的so文件。32位的arm手机,肯定能加载到armeabi-v7a下的so文件。64位的arm手机,想要加载32位的so文件,千万不要在arm64 -v8a目录下放置任何so文件。把so文件都放在armeabi-v7a目录下就可以加载到了。


下面举个例子来说明上面so的加载过程:
32位的arm手机,如果项目的jniLibs目录下存在如下的so文件,
jniLibs/arm64-v8a/libmsc.so
jniLibs/armeabi-v7a/libmsc.so
当要加载msc这个so文件时,就会直接到areabi-v7a目录下找。找到就加载, 找不到就报 couldn’t find “libmsc.so”
如果armeabi-v7a这个目录都不存在时,也报 couldn’t find “libmsc.so”


64位的arm手机,如果项目的jniLibs目录下存在如下的so文件,
jniLibs/arm64-v8a/libmsc.so
jniLibs/armeabi-v7a/libmsc.so
当要加载msc.so文件时,就先到arm64-v8a目录下找,找到后,就不会去其他目录下找了。
如果arm64-v8a目录下未找到,则到armeabi-v7a目录下找,找到就使用,找不到就去其他目录找,依次类推,如果都找到不到就报 couldn’t find “libmsc.so”。
这个查找过程可以看下图:
在这里插入图片描述



  1. so的加载方式
    方式一:System.loadLibrary方法,加载jniLibs目录下的so文件。例如,jniLibs目录下的arm64-v8a目录下有一个libHello.so文件,那么加载这个so文件是:

     System.loadLibray("Hello");//注意,没有lib前缀

方式二:使用System.load方法,加载任意路径下的so文件,需要传入一个参数,这个参数就是so文件所在的完整路径。这两种方式最终都是调用的底层的dlopen方法加载so文件。但是方式二,由于可以传入so的路径,这样就可以实现动态加载so文件。so的插件化,就是使用的这种方式。动态加载so文件时,有时会出现 dlopen failed:libXXX.so is 32-bit instead of 64 bit 的异常。出现这个异常的原因是,手机的操作系统是64位的,这样加载这个32位的so文件时,会默认使用64位的虚拟机去加载,这样就报了这个异常。解决这个问题的方式,可以先在jniLibs目录下armeabi-v7a目录下,放入一个很简单的32位的libStub.so文件,在动态加载插件的so文件时,先去加载这个jniLibs/armeabi-v7a目录下的libStub.so文件,这样就会创建一个32位的虚拟机,当加载插件的32位的so文件时,就会使用这个32位的虚拟机来加载插件的so文件,这样也就不会报错了。


注意,每个abi目录下的so文件数量要相同,因为,如果,在arm64-v8a目录下,存在a.so文件,在armeabi-v7a目录下,存在a.so和b.so文件,如果是在64位的arm系统的手机上加载a.so和b.so文件,由于先找a.so文件会先到arm64-v8a目录下找,找到后,后续的其他so文件就会都在这个目录下找了,有arm64-v8a目录下没有b.so文件,这样就会报couldn’t find "b.so"文件异常。所以,要保持每个abi目录下的so文件个数一致。


关于加载插件中的so文件,是通过先创建加载插件的DexClassLoader,将插件中的so文件的路径传递给DecClassLoader的构造函数的第三个参数,这样,后续使用这个DexClassLoader去加载插件中的类或方法,插件中这些类或者方法中去加载插件的so文件。


————————————————
版权声明:本文为CSDN博主「hujin2017」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hujin2017/article/details/102804883

0 个评论

要回复文章请先登录注册