这两天准备重构一个好几年的老项目,项目中使用了NDK,但是莫名的报ABI的错误,最终找出问题是项目中.so文件放的位置不对。ABI的概念之前也了解过,今天总结一下。
理解几个概念
- NDK
即Native Development Kit,原生开发工具集,因此又被Google称为“NDK”,Android开发中NDK允许用户使用类似C / C++之类的原生代码语言执行部分程序。好处是方便调用第三方C/C++开源库,也方便代码的移植。 - JNI
即Java Native Interface,从字面意思知道充当一个接口的角色,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。 - ABI
即Application Binary Interface,应用程序二进制接口。不同的Android 手机有不同的CPU,进而支持不同的指令集,CPU和指令集的每一种组合有它自己的ABI(应用二进制文件),属于“运行时,应用的机器码和系统的交互方式”。 - API
即Application Programming Interface,应用程序编程接口。面向的是与源代码层面或者库函数层面的交互方式,发生在编译期。
API和ABI的一个重要的区别就是前者是面向的是源代码,后者站在源代码的角度面向的是驱动不同的cpu架构或者不同的内存分配方式(系统不同或者语言不同有可能内存分配方式也不同)的二进制文件。所以说一套源码下来API相同ABI却很可能不同,后者兼容程度要更为严格。
[外链图片转存失败(img-lmcCAjVD-1563071218677)(https://note.youdao.com/yws/api/personal/file/A44E1CFB4B3249FB8A1C87CB2ECBBA14?method=download&shareKey=e1479723fb2736cb27374bab84dcd797)]
Android支持的CPU架构(ABI)
- armeabi
基于 ARM* v5TE 设备的库,使用软件浮点运算,兼容所有ARM设备,通用性强,速度慢。 - armeabi-v7a
基于 ARM* v7 设备的库,使用硬件浮点运算,具有高级扩展功能(2010年)。 - arm64-v8
面向第8代、64位ARM处理器的库。 - x86
面向32位intel处理器(2011年)。(台式机和平板用的较多) - x86_64
面向64位intel处理器(2014年)。 - mips
面向mips架构的处理器。(早期索尼的游戏机,相比intel,其指令系统计算结构更精简) - mips64
面向64位mips架构的处理器。
AndroidStudio中使用.so库
两种形式配置:
1.和eclipse中工程项目一样,在libs目录下新建armeabi等目录的方式,这种方式不是Androidstudio默认支持的,因此要在gradle中添加指定加载.so库的目录:
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
2.采用AndroidStudio默认的形式,直接在src/main/下新建jniLibs目录,将armeabi等目录放到该目录下即可。另外同级目录下仍可新建jni目录NDK开发用于放置cpp或c文件,即jni和jniLibs可同级存在。
安装时兼容性检查
安装到系统中后,so文件会被提取在:data/app/com.xxxxxxxx.app-x/lib/目录下(5.0版本)、/data/app-lib/目录下(4.2版本),其中armeabi和armeabi-v7a会生成arm目录,arm64-v8a会生成arm64目录。
安装app的时候,如果app使用了so文件,而不存在适合本机cpu架构的so文件,则会报错。例如:在x86模拟器上就必须有x86版本的so文件夹。不然无法安装成功。
运行时兼容性检查
1.检查目标目录下是否存在的so库文件。有些时候由于项目中不同ABI文件夹下的.so文件个数不一致,加载后造成某些CPU架构无法正常在提取的目录下读取到相应的.so文件。
2.检查存在的so文件是否符合当前cpu架构。某些ABI文件夹的.so不符合。
加载.so文件规则
当项目中只提供了armeabi目录时,armeabi-v7a、arm64-v8a架构的程序会去armeabi里寻找,当项目中同时也提供了armeabi-v7a、armeabi-v8a目录,系统就不会再去armeabi里面寻找了,如果在这两个目录中找不到则直接报错。
常见问题
- ABI目录中虽然放置的.so文件一样,但某些目录下放置却不是最优的.so文件,或者从低版本架构拷贝的。
- 使用Android高版本平台版本编译的.so文件运行在低版本的设备上。
- 没有为每个支持的CPU架构提供对应的.so文件。
- 将.so文件放在错误的地方。
- 只提供armeabi架构的.so文件而忽略其他ABIs的。
以上这些问题要么导致报错,要么导致性能低下,即使能运行但在某些老旧的设备上也可能发生错误。
建议
这里根据上边的问题在开发的过程中提几条建议:
- 如果应用程序想安装范围广一些,那么尽可能包含所有的ABI,当然这会导致安装包很大。
- 如果考虑到应用的安装范围,比如针对更多的是手机,则可适配armeabi、armeabi-v7、arm64-v8。
- 如果考虑到安装包的大小,而不太注重CPU性能,可能面对的都是市面上的新机,可仅仅适配armeabi。
- 如果针对多个ABI,则每个ABI目录下的.so文件都是一样的,最好都是针对该ABI的。
- Native Libs Monitor这个应用可以帮助我们理解手机上安装的APK用到了哪些.so文件,以及.so文件来源于哪些函数库或者框架。
- 某些时候引用多个第三方库,可能libs文件夹和jniLibs文件夹都放的有,此时会造成冲突,所以尽可能的都放在一个地方。
上边选择性的适配ABI可在Gradle中配置:
ndk {
//选择要添加的对应cpu类型的.so库。
abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a'
// 还可以添加 'x86', 'x86_64', 'mips', 'mips64'
}
注意:ndk.abiFilters指定的abi平台的库文件夹及库文件,要么都存在,要么都不存在。
Tap:
-
踩过的坑:之前有个项目仅支持armeabi和armeabi-v7a的cpu架构,但是在调试过程中总是报错:
Caused by: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/idonic.mobileapp.xxx-1/base.apk"],nativeLibraryDirectories=[/data/app/idonic.mobileapp.xxx-1/lib/arm, /data/app/idonic.mobileapp.xxx-1/base.apk!/lib/armeabi-v7a, /vendor/lib, /system/lib]]] couldn't find "libxxx.so"
自己项目中也只有armeabi库文件夹,根据上边的规则,即使没有armeabi-v7a文件夹运行时也最终会到armeabi文件夹中寻找,不应该啊,后来冥思苦想只有一种可能,就是项目构建后肯定有armeabi-v7a文件夹的存在,立刻解压缩apk果真是这个问题,原来项目中依赖有腾讯的Bugly,构建打包后会释放出armeabi-v7a的文件夹,但是里面只有libBugly.so文件。
解决这个问题有三点注意:
- 不要只看项目表面的结构,隐藏的依赖有可能是最大的元凶。
- 一定要设置abi过滤器。
- 把对应的abi库文件补全。
-
有些时候我们运行安装在模拟器上的时候报“Failure [INSTALL_FAILED_NO_MATCHING_ABIS: Failed to extract native libraries, res=-113]”
具体错误原因:由于使用了native libraries 。该native libraries 不支持当前的cpu的体系结构。
INSTALL_FAILED_NO_MATCHING_ABIS is when you are trying to install an app that has native libraries and it doesn’t have a native library for your cpu architecture. For example if you compiled an app for armv7 and are trying to install it on an emulator that uses the Intel architecture instead it will not work.
简单点说,虽然工程支持运行在某abi架构(例如模拟器x86架构)机器上,但是工程所包含的库却没有该abi架构所支持的库,导致无法安装运行,这在上面阐述很清楚。
解决这个问题有两种方式:
a.将x86的库文件夹及文件补全。
b.移除ndk.abiFilters,采用abi分包,构建apk时单独分离出支持x86架构的apk。splits { abi { enable true reset() include 'x86' universalApk true } }
-
提到上边构建apk分包,有多种分包形式,可以按照abi支持进行分,也可以按照屏幕密度支持进行分:
splits { density { enable true reset() include "mdpi", "hdpi" } abi { enable true reset() include "x86", "x86_64" } }
点击菜单Build > Build APK,然后在工程中build > outputs > apk下可获取到多种形式的apk。具体详细使用可参考官方文档:https://developer.android.com/studio/build/configure-apk-splits