从ARM暂停与华为合作谈起 Android中SO兼容的那些事

因为包含来自美国的技术,ARM(英国)已经要求员工“停止所有与华为及其子公司正在生效的合约、支持及未决约定”

从最近的新闻来看,美国的贸易禁令使得华为公司腹背受敌,ARM彻底暂停与华为合作已成定局,最新款的芯片技术肯定是用不上了,不过华为已经获得了ARMv8的永久授权。但是ARM是什么样的存在? 为什么对华为有这么大的影响力呢,事实上 ARM处理器 已经一统移动端了。今天就来谈谈 ARM 与 Android SO 的那些事。

背景:

公司某项目新上功能部分页面使用 weex 来做的,测试过程中发现 weex 页面在某些手机上出现异常,经过排查定位到是 SO 库的问题,下面是这个问题的解决过程的一个记录,希望能对遇到类似问题的同学提供一些帮助。

什么问题?

公司某项目 V1.2.0版本最初上线时,使用的 weex 是官方的 SDK 的接入方式, 接下来 App版本需要把官方的 SDK 替换成我们自己私服上的 SDK(因为私服上有我们针对自己项目需要,自定义了许多控件和公共组件) ,替换后,出现 weex 页面加载异常的问题(白屏)

报错信息如下:

so1.jpeg

我们注意到,上面有一行错误是 invokeInitFramework java.lang.UnsatisfiedLinkError: 见过该错误的开发者都知道,这个是 JNI 相关的错误信息,根据错误信息能看到是 weex 相关的 JNI 调用出了问题,由JNI 又想到了 SO 库文件, 那就引出今天的话题。

什么是 SO 文件及SO 应用?

SO(shared object,共享库)是机器可以直接运行的二进制代码

  • SO 机制让开发者最大化利用已有的 C 和 C++ 代码,达到复用的效果,利用软件世界积累了几十年的优秀代码;

  • SO 是二进制,没有解释编译的开销,用SO实现的功能比纯java实现的功能要快;

  • SO 内存分配不受 Dalivik/ART 的单个应用限制,减少 OOM;

  • 相对于java代码,二进制代码的反编译难度更大,一些核心代码可以考虑放在 SO 中。

在Android 中 提到 SO 就不能不提 ABI

ABI

应用程序二进制接口(Application Binary Interface)

定义了其所对应的CPU架构能够执行的二进制文件(特别是.so文件)的格式规范。在 Android 系统上,不同 Android 手机使用不同的 CPU,因此支持不同的指令集。CPU 与指令集的每种组合都有其相应的应用二进制界面(或 ABI)。

ABI 可以非常精确地定义应用的机器代码在运行时如何与系统交互。 您必须为应用要使用的每个 CPU 架构指定 ABI:armeabi,armeabi-v7a,arm64-v8a,x86,x86_64,mips,mips64;

目前 Android 共支持七种不同类型的 CPU 架构,分别是:ARMv5,ARMv7 (从2010年起),x86 (从2011年起),MIPS (从2012年起),ARMv8,MIPS64和x86_64 (从2014年起)

so11.png

SO(CPU)的兼容

每一个 CPU 架构对应一个 ABI,一个 CPU 属于某一种架构,多核 CPU 属于相同架构才能一起工作,很多设备仅支持一种 CPU 架构。

如果你要完美兼容所有类型的机型,理论上是要在的 libs 目录下放置各个架构平台的 SO 文件。

但项目体积也会变得非常庞大。是否一定需要带入这么多SO文件去兼容呢?答案是否定的。

根据目前Android共支持七种不同类型的CPU架构,其兼容特点可总结如下:

armeabi设备只兼容armeabi;

armeabi-v7a设备兼容armeabi-v7a、armeabi;

arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi;

X86设备兼容X86、armeabi;

X86_64设备兼容X86_64、X86、armeabi;

mips64设备兼容mips64、mips;

mips只兼容mips;

armeabi 的 SO 文件基本上可以说是兼容目前市面上的大部分手机,它能运行在除了mips和mips64的设备上,但在非 armeabi 设备上运行性能会有所损耗;

64位的CPU架构总能向下兼容其对应的32位指令集,如:x86_64兼容X86,arm64-v8a兼容armeabi-v7a,mips64兼容mips;

总结成一句话就是, 新的CPU 架构总能向下兼容;

所以知道这些信息后,查看公司其他项目怎么用的, 发现其他项目, 在 build.gradle 中关于 ndk.abiFilters 都是这样写的:

so2.png

而这个项目的写法是这样的:

so3.png

区别就是这个 ndk.abiFilters , 既然这样写,必定有它的原因, 那就继续找,为什么要这么写,咨询相关的同事,了解到相关路径下的文件是 视频聊天、语音相关的 SO 库。

那能不能改成只支持 armeabi 或者 他们其中的部分呢。那就模拟这些情况,然后分别打出了这么多包(包的命名有点随意,这不是重点),分别在不同的手机上验证是否能正常加载 weex 。

so4.png

so5.png

经测试发现 armeabi 的和 armeabi +x86 的都可以正常加载 weex 页面,感觉问题这么简单就解决了,但是!!!

想到去掉这么多东西 会不会影响其他正常功能呢, 那就验证下吧,试了下视频相关功能,果不其然的 app 崩溃了。

说明上面的方向肯定是有问题,使用工具 Native libs Monitor 查看下Apk 中具体的SO 文件

首先查看下那些我们常用的App 是怎么做的,微信、QQ、支付宝等等

其他公司如何适配的

  • 微信(只适配armeabi,有少量 v7a);

  • qq(只适配armeabi,文件夹下有少量x86);

  • 百度地图(只适配armeabi);

  • 大众点评(只适配armeabi);

  • google 家 (基本都是 arm64);

  • 支付宝 (基本都是 armeabi 的,2个x86的);

而且 公司别的项目App 中 用到的 face++ 的人脸识别,身份证识别 也是只提供了armeabi 的SO 文件

下图是微信的Apk 解压出的情况, 微信的lib下虽然只有armeabi-v7a一个目录,但目录内的文件仍放着v7a和 armeabi架构的SO文件,用于处理兼容带来的某些性能运算问题。

so12.png

看完大公司的适配情况,然后看下我们自己 公司这几个项目,看看到底有什么区别, 还有上面打出的不同的包,相应的 SO 引入情况。

经过对比发现, 指定不同的 abi 会 在apk 中 打入相应 的文件(前提是 你有这些文件),这就是一个过滤器,只在包中引入指定的cpu 架构的 SO 文件。

so6.png

既然重点是 weex 加载的出了问题,然后就重点查看 weex 相关的 SO 引入情况, 既然官方的可以正常使用, 我们私服上却出了问题,然后对比使用官方的 sdk 集成方式,和我们私服上的集成方式的 SO 库有什么不同;

如图是集成weex 官方sdk 的 APK
so7.png

而集成私服上的 sdk 后,只在 armv5中有 libweexjsc.so 而 armv7 中是没有这个问题的。 区别找到了,那就去私服的项目中找原因
so8.png

并没有 armeabi-v7a 的文件,相应的 lib下也没有相应的文件夹和文件,按照一样的写法,在这加入 armeabi-v7a 相应的路径,相应的 lib 下,也新建了相应的文件夹,放入相应的文件。重新编译

so9.png

再次尝试, 查看 lib 在 arm v7 和 v8a 的手机上,查看 lib 情况,发现, 在都能看到相应的 SO 文件了,

多部手机尝试, 视频聊天、语音、 weex 都正常、至此 问题得到解决。

在排查问题的过程中发现,其实这个项目 本来的 build.gradle 文件中的 arm64-v8a 写错了,以前写成了 armeabi-v8a 其实这样是无法使用到 arm64-v8a下的 so 的。

但是会向下兼容, 使用了 armeabi-v7a 中的文件,也没有报错,其实也是因祸得福,如果写正确的话,weex 在 arm64-v8a 也会加载失败,因为 官方也没有提供这样的一个文件夹和文件。

但是我们要怎么配置呢?

从目前移动端CPU市场的份额数据看,ARM架构几乎垄断,所以,除非你的用户很特殊,否则几乎可以不考虑单独编译带入X86、X86_64、mips、mips64架构SO文件。除去这四个架构之后,还要带入armeabi、armeabi-v7a、arm64-v8a 这三个不同类型,这对于一个拥有大量SO文件的应用来说,安装包的体积将会增大不少。

针对不同平台,如何去适配,如何抉择

目前主流的Android设备主要是 armeabi-v7a ARMv8 架构的,程序在运行的时候去加载不同平台对应的so,这是较为完美的一种解决方案,但是有时候为了减少包体积的大小,

不会同时设置 armeabi, armeabi-v7a 和 x86。根据不同的情况,可以进行不同的适配,

1.只适配 armeabi-v7a,因为目前主流机型是 ARMv7,并且 ARMv8 设备也向下兼容了armeabi-v7a, Facebook、WhatsApp、王者荣耀等就是只适配了armeabi-v7a。(Google play store下载 Native libs Monitor 进行查看)。

2.只适配 armeabi,因为 ARMv7 、ARMv8 还是 x86 都兼容 armeabi,但是性能都会有些损耗,例如ARMv7 支持硬件浮点运算等没法体现,x86 支持 armeabi 同样具有相应的损耗。

3.同时适配 armeabi-v7a 和 armeabi,既能够支持所有 ARM 架构,同时又能具有 ARMv7 支持硬件浮点运算等特性,例如Line等应用。

4.同时适配 x86 和 armeabi,既能支持所有 ARM 架构,又能支持x86架构,唯一的缺点就是没有了ARMv7 支持硬件浮点运算等一系列特性,例如QQ。

5.同时适配 armeabi, armeabi-v7a 和 x86,在性能方面来说是较为完美的方案,只是APK的大小也会随之变大。

6.还有其他的一些方案,例如微信只适配了armeabi,但是对于某些需要利用 ARMv7 支持硬件浮点运算等一系列特性的操作,在armeabi目录下存在v7对应的so文件,通过代码判断加载不同的so文件。即达到了减少APK大小的目的,又能达到适配ARMv7等架构以便使用其架构的一些新特性的目的。

就目前市场份额而言,绝大部分的设备都已经是armeabi-v7a、arm64-v8a,可以考虑只保留armeabi-v7a架构的SO文件,这样能获得更好的性能效果。所以我们的 这个项目选择采用第3种方案;
so10.png

现在把这个项目中的 ndk.abiFilters 配置成上图所示,几乎覆盖所有机型,weex 能够正常加载,暂时没有发现影响其他功能,这个问题也算是得到解决。在不影响太多性能的情况下,也可以明显减少包的体积。

总结下常见的 引入.so文件的错误

1.使用android高版本平台版本编译的.so文件运行在android低版本的设备上

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

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

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

arm64-v8a是可以向下兼容的,但前提是你的项目里面没有arm64-v8a的文件夹,如果你有两个文件夹armeabi和arm64-v8a,两个文件夹,armeabi里面有a.so 和 b.so,arm64-v8a里面只有a.so,

那么arm64-v8a的手机在用到b的时候发现有arm64-v8a的文件夹,发现里面没有b.so,就报错了,

所以这个时候删掉arm64-v8a文件夹,这个时候手机发现没有适配arm64-v8a,就会直接去找armeabi的so库,或者把 arm64-v8a文件夹 的 b.so补齐。

参考文章:

https://zhuanlan.zhihu.com/p/21302804

https://www.zhihu.com/question/36893314/answer/78467097

https://blog.csdn.net/zophar_development/article/details/84329054

https://www.cnblogs.com/janehlp/p/7473240.html

https://www.jianshu.com/p/cb05698a1968

https://www.jianshu.com/p/cb15ba69fa89

请联系我!