Device supports x86,but APK only supports armeabi-v7a,armeabi,x86_64;

简介:做项目的时候经常会使用到so文件。例如使用高德地图,其SDK中就包含了armeabi、armeabi-v7a、arm64-v8a、x86等其他文件夹,里面通常放着同样名称、同样数量的so文件。实际使用过程中,关于这些so文件引发的问题确实不少,也不好解决。写下此文,希望以后遇到相关的问题,能有个大概的思路。

名词解析:

    NDK:Native Development Kit

    JNI:Java Native Interface

    ABI: Application Binary Interface 应用二进制接口

Android系统目前支持以下七种不同的CPU架构,每一种都关联着一个相应的ABI。应用程序二进制接口(Application Binary Interface)定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库

Android Studio使用so库

1、使用和eclipse一样在libs目录下新建armeabi目录的方式

需要在build.gradle中添加指定jni库目录的语句

sourceSets {

   main.jniLibs.srcDirs = ['libs'] //指定libs为jni的存放目录

}

2、使用AS默认的位置:src/main/jniLibs

直接在src/main/下新建jniLibs目录,将armeabi等目录放到该目录下即可

备注:AS可以直接右键新建同目录下的jniLibs目录,但该目录不是编译好的库文件目录,而是未编译的本地代码文件的目录(这里指的是与java同级的jni目录,放置cpp代码的)

android支持的cpu架构(目前是七种)

armeabi 第5代 ARM v5TE,使用软件浮点运算,兼容所有ARM设备,通用性强,速度慢
armeabi-v7a 第7代 ARM v7,使用硬件浮点运算,具有高级扩展功能
arm64-v8a 第8代,64位,包含AArch32、AArch64两个执行状态对应32、64bit
x86 intel 32位,一般用于平板
x86_64 intel 64位,一般用于平板
mips 少接触
mips64 少接触

安装时的兼容性检查:

   安装到系统中后,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文件,会报如下错误:

Installation failed with message INSTALL_FAILED_NO_MATCHING_ABIS.

例如:在x86模拟器上就必须有x86版本的so文件夹。不然无法安装成功。

运行时的兼容性检查:

1、检查目标目录下是否存在的so库文件

2、检查存在的so文件是否符合当前cpu架构。

   对于情况一,一般规避的做法是:保证jnilibs目录下x86、x84_64、armeabi、armeabi-v7a、arm64-v8a等目录下的文件名称数量是一致的。

   例如:项目中使用了A、B、C三个第三方库。其中A、B提供了armebi以及arm64-v8a版本的库文件,而C只提供了armebi、armebi-v7a版本的库文件。这时候只能够删除原有的arm64-v8a目录,保留armeabi目录,一般arm64的手机都能兼容使用armeabi版本的库。或者复制一份armeabi的so文件到缺少的目录中(推荐)。

生成so文件:

   NDK交叉编译时选定APP_ABI := armeabi x86 ...可以生成支持相应芯片的so文件。APP_ABI := all生成支持所有芯片指令集(目前七种)so文件。

Android加载so文件规则:

   当你只提供了armeabi目录时,armeabi-v7a、arm64-v8a架构的程序都会去armeabi里寻找,而当你同时也提供了armeabi-v7a、armeabi-v8a目录,而里面又不存在对应的so库时,系统就不会再去armeabi里面寻找了,直接找不到报错。其他平台也是如此。这里我踩了不少的坑,切记。

   一般来说,一些比较有名的第三方库都会提供armeabi、armeabi-v7a、x86这三种类型的so文件,同时拥有这三种版本的app可以在所有机型上运行。另外,越来越多的SDK会同时提供arm64-v8a版本。只包含armeabi的项目也可以在所有设备上运行。

现实案例:

   我的项目中使用了armeabi、arm64-v8a两种类型,而当我需要使用某语音第三方库的时候,发现只提供了armeabi、armeabi-v7a两种类型的so文件,而我的手机是arm64-v8a的。所以只会使用arm64-v8a里面的so文件,当使用到该语音库时找不到对应的so库,就会报错。理论上有以下两种解决方法:

一、删除所有arm64-v8a,只保留armeabi,全部使用兼容性最高的版本,但也运行速度最慢。

二、将该语音库的armeabi版本的so复制到arm64-v8a中。单一so文件使用armeabi兼容版本。

总结:

  1. 当你使用到so文件时,保证每个子文件夹中文件名称数量都是一致的。

  2. 对于只提供armeabi的第三方库,复制一份armeabi的so文件到缺失的其他目录中;或者只保留armeabi目录(不推荐)

处理.so文件时有一条简单却并不知名的重要法则。

你应该尽可能的提供专为每个ABI优化过的.so文件,但要么全部支持,要么都不支持:你不应该混合着使用。你应该为每个ABI目录提供对应的.so文件。

当一个应用安装在设备上,只有该设备支持的CPU架构对应的.so文件会被安装。在x86设备上,libs/x86目录中如果存在.so文件的话,会被安装,如果不存在,则会选择armeabi-v7a中的.so文件,如果也不存在,则选择armeabi目录中的.so文件(因为x86设备也支持armeabi-v7a和armeabi)。

其他地方也可能出错

当你引入一个.so文件时,不止影响到CPU架构。我从其他开发者那里可以看到一系列常见的错误,其中最多的是"UnsatisfiedLinkError","dlopen: failed"以及其他类型的crash或者低下的性能:

使用android-21平台版本编译的.so文件运行在android-15的设备上

使用NDK时,你可能会倾向于使用最新的编译平台,但事实上这是错误的,因为NDK平台不是后向兼容的,而是前向兼容的。推荐使用app的minSdkVersion对应的编译平台。

这也意味着当你引入一个预编译好的.so文件时,你需要检查它被编译所用的平台版本。

混合使用不同C++运行时编译的.so文件

.so文件可以依赖于不同的C++运行时,静态编译或者动态加载。混合使用不同版本的C++运行时可能导致很多奇怪的crash,是应该避免的。作为一个经验法则,当只有一个.so文件时,静态编译C++运行时是没问题的,否则当存在多个.so文件时,应该让所有的.so文件都动态链接相同的C++运行时。

这意味着当引入一个新的预编译.so文件,而且项目中还存在其他的.so文件时,我们需要首先确认新引入的.so文件使用的C++运行时是否和已经存在的.so文件一致。

没有为每个支持的CPU架构提供对应的.so文件

这一点在前文已经说到了,但你应该真的特别注意它,因为它可能发生在根本没有意识到的情况下。

例如:你的app支持armeabi-v7a和x86架构,然后使用Android Studio新增了一个函数库依赖,这个函数库包含.so文件并支持更多的CPU架构,例如新增android-gif-drawable函数库:

compile ‘pl.droidsonroids.gif:android-gif-drawable:1.1.+’

发布我们的app后,会发现它在某些设备上会发生Crash,例如Galaxy S6,最终可以发现只有64位目录下的.so文件被安装进手机。

解决方案:重新编译我们的.so文件使其支持缺失的ABIs,或者设置

ndk.abiFilters

显示指定支持的ABIs。

最后一点:如果你是一个SDK提供者,但提供的函数库不支持所有的ABIs,那你将会搞砸你的用户,因为他们能支持的ABIs必将只能少于你提供的。

将.so文件放在错误的地方

我们往往很容易对.so文件应该放在或者生成到哪里感到困惑,下面是一个总结:

  • Android Studio工程放在jniLibs/ABI目录中(当然也可以通过在build.gradle文件中的设置jniLibs.srcDir属性自己指定)
  • Eclipse工程放在libs/ABI目录中(这也是ndk-build命令默认生成.so文件的目录)
  • AAR压缩包中位于jni/ABI目录中(.so文件会自动包含到引用AAR压缩包的APK中)
  • 最终APK文件中的lib/ABI目录中
  • 通过PackageManager安装后,在小于Android 5.0的系统中,.so文件位于app的nativeLibraryPath目录中;在大于等于Android 5.0的系统中,.so文件位于app的nativeLibraryRootDir/CPU_ARCH目录中。

只提供armeabi架构的.so文件而忽略其他ABIs的

所有的x86/x86_64/armeabi-v7a/arm64-v8a设备都支持armeabi架构的.so文件,因此似乎移除其他ABIs的.so文件是一个减少APK大小的好技巧。但事实上并不是:这不只影响到函数库的性能和兼容性。

x86设备能够很好的运行ARM类型函数库,但并不保证100%不发生crash,特别是对旧设备。64位设备(arm64-v8a, x86_64, mips64)能够运行32位的函数库,但是以32位模式运行,在64位平台上运行32位版本的ART和Android组件,将丢失专为64位优化过的性能(ART,webview,media等等)。

以减少APK包大小为由是一个错误的借口,因为你也可以选择在应用市场上传指定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
        }
    }
 }
  • 为了减小 apk 体积,只保留 armeabi 和 armeabi-v7a 两个文件夹,并保证这两个文件夹中 .so 数量一致
  • 对只提供 armeabi 版本的第三方 .so,原样复制一份到 armeabi-v7a 文件夹

查看手机的CPU架构

               adb device  查看连接的设备,
    adb -s 192.168.56.101:5555 shell  进入手机的shell设置模式,
    cat /proc/cpuinfo  查看手机CPU信息

查看手机CPU信息的命令)

或者搜:本地库监视器Native Libs Monitor(其包名:com.xh.nativelibsmonitor.app

Android应用支持的ABI取决于APK中位于lib/ABI目录中的.so文件,其中ABI可能是上面说过的七种ABI中的一种。
  很多设备都支持多于一种的ABI。例如ARM64和x86设备也可以同时运行armeabi-v7a和armeabi的二进制包。但最好是针对特定平台提供相应平台的二进制包,这种情况下运行时就少了一个模拟层(例如x86设备上模拟arm的虚拟层),从而得到更好的性能(归功于最近的架构更新,例如硬件fpu,更多的寄存器,更好的向量化等)。
  我们可以通过Build.SUPPORTED_ABIS得到根据偏好排序的设备支持的ABI列表。但你不应该从你的应用程序中读取它,因为Android包管理器安装APK时,会自动选择APK包中为对应系统ABI预编译好的.so文件,如果在对应的lib/ABI目录中存在.so文件的话。

然而问题又来了:

最近项目中遇到了要使用opencv的情况,涉及到了abi兼容的选择。因为如果全部都适配的话,包很大,这样兼容那些用户数极少的cpu就很不划算,所以我只适配了armeabi-v7a这一个。但是今天在x64-v8a的模拟器上看的时候,提示我的library.so文件找不到,我记得这个应该是向下兼容的,但是出现这种情况很奇怪,于是我就在网上找了找答案。

解决方法:abiFilters
在app的gradle的defaultConfig里面加上这么一句

ndk {
    abiFilters  "armeabi-v7a"  // 指定要ndk需要兼容的架构(这样其他依赖包里mips,x86,armeabi,arm-v8之类的so会被过滤掉)
}

这句话的意思就是指定ndk需要兼容的架构,把除了v7a以外的兼容包都过滤掉,只剩下一个v7a的文件夹。用了这个方法之后,确实解决了问题。这就是解决方法。

具体分析
其实这个方法我开始是很奇怪的,我明明没有指定其他的兼容框架,为什么会需要一个过滤。我打来了apk的包,找到了里面的lib目录,发现里面有很多的兼容目录,然后看到里面目录里面的是一个fresco的.so文件。也就是说,fresco做了各个平台的兼容,所以它创建了各个兼容平台的目录。因为只要出现了这个目录,系统就只会在这个目录里找.so文件而不会遍历其他的目录,所以就出现了之前找不到.so文件的情况(因为其他目录没有我的.so文件)。

总结
为了决定最后适配的abi版本,我下载了排行前几名的app,然后打开之后发现,他们基本上只适配了一个armeabi,少数会再加上v7a。我了解到的情况是armeabi性能较差,但是兼容性最好,v7a对于浮点计算的cpu来说性能更好,不兼容不支持浮点运算的cpu。我想到的是目前的手机cpu绝大多数应该是支持浮点运算的,而且安卓从2.2开始就支持v7a,所以v7a的兼容性应该也不是问题。(不知道对不对,谁能明确一下的,恳请指正) 
无论如何,abiFilters还是应该添加的。
 

最后,建一个armeabi 架构的模拟器了。

发布了74 篇原创文章 · 获赞 36 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/kdsde/article/details/84644286
今日推荐