Android ABI 想到的

1,567 阅读5分钟
原文链接: www.jianshu.com

缘起

最近在项目中用到了realm,引起项目编译找不到so库,原来是realm的多版本so库所导致的。所以,解铃还须系铃人,咱们就从ABI入手解决这个问题。

ABI是什么

应用程序二进制接口ABI(Application Binary Interface)定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库。在Android 系统上,每一个CPU架构对应一个ABI:armeabi,armeabi-v7a,x86,mips,arm64- v8a,mips64,x86_64。分别对应着下面7种CPU架构ARMv5,ARMv7 (从2010年起),x86 (从2011年起),MIPS (从2012年起),ARMv8,MIPS64和x86_64 (从2014年起),每一种都关联着一个相应的ABI。

.so文件

.so文件是unix的动态连接库,是二进制文件,在Android中调用动态库文件(*.so)都是通过jni的方式,这里我们要说说NDK,Android NDK是一个工具集,它让我们能够在Android应用程序中使用由本地代码(native code)编写的代码模块,NDK 提供的编译系统让我们可以高效的编译源码,不需要去处理工具链、平台等细节。我们只需要创建很短的编译文件,描述哪些源文件要编译以及哪些应用程序会使用 它们,编译系统就会编译源代码并且将编译生成的共享库(shared libraries)直接放到我们的应用程序中。如果项目中使用到了NDK,它将会生成.so文件,因此显然你已经在关注它了。如果只是使用Java语言进行编码,你可能在想不需要关注.so文 件了吧,因为Java是跨平台的。但事实上,即使你在项目中只是使用Java语言,很多情况下,你可能并没有意识到项目中依赖的函数库或者引擎库里面已经 嵌入了.so文件,并依赖于不同的ABI,Android应用支持的ABI取决于APK中位于lib/ABI目录中的.so文件。Native Libs Monitor这个应用可以帮助我们理解手机上安装的APK用到了哪些.so文件,以及.so文件来源于哪些函数库或者框架。

当然,我们也可以自己对app反编译来获取这些信息,不过相对麻烦一些,其实我们只要把APK文件解压了以后就可以看到项目用到了哪些.so文件了。如下图


1.png

ABI和CPU的关系

很多设备都支持多于一种的ABI。例如ARM64和x86设备也可以同时运行armeabi-v7a和armeabi的二进制包。但最好是针对特 定平台提供相应平台的二进制包,这种情况下运行时就少了一个模拟层(例如x86设备上模拟arm的虚拟层),从而得到更好的性能(归功于最近的架构更新, 例如硬件fpu,更多的寄存器,更好的向量化等)。

我们可以通过Build.SUPPORTED_ABIS得到根据偏好排序的设备支持的ABI列表。但你不应该从你的应用程序中读取它,因为 Android包管理器安装APK时,会自动选择APK包中为对应系统ABI预编译好的.so文件,如果在对应的lib/ABI目录中存在.so文件的 话。
对应关系如下图:


2.png

配置.so文件

使用 app源根文件夹下build.gradle文件的配置:

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.dygsm.dywifi"
        minSdkVersion 15
        targetSdkVersion 22
        versionCode 1
        versionName "04.30.00.01"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        // 不声明ndk标签,项目默认会创建一个libapp.so的文件
        ndk {
            abiFilters "armeabi", "armeabi-v7a", "x86"
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

此外,要在 gradle.properties 文件中添加android.useDeprecatedNdk=true,重新编译工程即可。
  不同CPU架构的Android手机加载时会在libs下找自己对应的目录,从对应的目录下寻找需要的.so文件;如果没有对应的目录,就会去armeabi下去寻找,如果已经有对应的目录,但是如果没有找到对应的.so文件,也不会去armeabi下去寻找了。 所以,这里需要注意工程配置哪几个so文件目录,需要加载对应的so文件,不然会报错。

  如何适配各个目录,例如有一些第三方的类库只提供了armeabi下的.so文件,而工程配置不止armeabi一个目录,这就需要将armeabi下的.so文件复制到其他对应的目录下。果第三方提供了不同平台的.so文件,则复制不同平台的.so文件到项目中对应的文件夹下即可。

  so文件也会影响编译出的apk大小(将apk解压出来,lib目录下就为so文件目录),所以只配置armeabi一个目录,既能适配各CPU架构的手机,也能精简apk大小(微信就是只有armeabi一个目录)。

使用原则

你应该尽可能的提供专为每个ABI优化过的.so文件,你不应该混合着使用(不能就装对不同cpu架构的so文件,放在同一个ABI目录下)。你应该为每个ABI目录提供对应的.so文件。
当只有一个.so文件时,静态编译C++运行时是没问题的,
当存在多个.so文件时,应该让所有的.so文件都动态链接相同的C++运行时。
这意味着当引入一个新的预编译.so文件,而且项目中还存在其他的.so文件时,我们需要首先确认新引入的.so文件使用的C++运行时是否和已经存在的.so文件一致。

缘灭

缘起的时候出现的编译错误解决方法如下:

  1. 不使用realm。
  2. 在每个ABI文件下面复制相同的.so库。
  3. 适配的使用库运行和支持运行。