动态加载so
为什么使用so
- so机制让开发者最大化利用已有的C和C++代码,达到重用的效果;
- so是二进制,没有解释编译的开消,用so实现的功能比纯java实现的功能要快;
- so内存分配不受Dalivik/ART的单个应用限制,减少OOM;
- 相对于java代码,二进制代码的反编译难度更大,一些核心代码可以考虑放在so中。
查看android系统支持的ABI
Android可以在运行期间确定当前系统所支持的ABI,这是由系统编译时的具体参数指定的:
- primary ABI(主ABI):对应当前系统中使用的机器码类型
- secondary ABI(副ABI):表示当前系统支持的其他ABI类型
因此很多手机不止支持一个ABI。
通过adb命令查看
/system/build.prop中指定了支持的ABI类型,在adb中,可使用如下命令查看:
adb shell getprop | grep abilist
通过API获取
使用Build.SUPPORTED_ABIS可以获取当前设备支持的ABI列表
String supportedAbis = Build.SUPPORTED_ABIS;
apk如何找到so
扫描整个apk文件,先找主abi的so文件,即找路径格式为:
lib/<primary-abi>/lib<name>.so
如果没有找到,则找合适的次级abi的so文件
lib/<secondary-abi>/lib<name>.so
即安装应用时,系统会根据当前CPU架构选择最优ABI适配,如果找到了合适的so文件,包管理器会将该ABI文件夹下所有so库全部拷贝至应用的data目录下:
data/data/<package_name>/lib/
注意:apk安装过程对so选择是基于整个ABI文件夹的,而非以单个so文件为粒度,也就是说把lib/armeabi 、lib/armeabi-v7a、lib/x86等等文件夹的其中一个文件夹内所有.so复制到应用的data目录下。
找不到so时,会报java.lang.UnsatisfiedLinkError的错误。
so加载方式
System.loadLibrary
静态加载so库,只需要传入so在Android.mk中定义的LOCAL_MODULE的值即可。
系统会调用System.mapLibraryName把这个libName转化成对应平台的so的全称并去尝试寻找这个so加载。
System.load
动态加载so库,需要给出完整的绝对路径。so必须拷贝到so拷贝至/data/data/<package-name>/
下。因为外部存储路径是一种不可执行的存储媒介。
load只能加载2种路径下的so:
- /system/lib
- 应用程序安装包的路径
loadLibrary和load最终都会调用nativeLoad(name, loader, ldLibraryPath)方法,只是因为loadLibrary的参数传入的仅仅是so的文件名,所以,loadLibrary需要首先找到这个文件的路径,然后加载这个so文件。 而load传入的参数是一个文件路径,所以它不需要去寻找这个文件路径,而是直接通过这个路径来加载so文件。
一般的优化方案
- 按照ABI分别单独打包APK(如Google Play)
- 只提供armabi的so
进阶版方案
首先是性能问题:使用兼容模式去运行arm架构的so,会丢失专门为当前ABI优化过的性能;其次还有兼容性问题,虽然x86设备能兼容arm类型的函数库,但是并不意味着100%的兼容,某些情况下还是会发生crash,所以x86的arm兼容只是一个折中方案,为了最好的利用x86自身的性能和避免兼容性问题,我们最好的做法仍是专为x86提供对应的so。 针对这些问题,我们可以采用一个相对更好的方案:让所有so都来自于网路,应用下载服务器上的so库后,利用System.load方法动态加载当前设备对应的so。
注意项
如果我们的应用选择了支持多个ABI,要十分注意:对于每个ABI下的so,但要么全部支持,要么都不支持。不应该混合着使用,而应该为每个ABI目录提供对应的.so文件。
因为是按照ABI来将so拷贝到私有目录的,如果部分有,则会在找这个ABI下的没有的so库,则直接报错。
一种可行的处理方案是:取你所有的so库所支持的ABI的交集,移除其他。