Android 应用开发中不可避免的会引入第三方的代码。如果是开源项目风险相对可控,如果引入商用的 SDK 那就要谨慎了,难免会有这样或那样的问题。比如我们今天要说的这一个。

对集成过第三方 SDK 的同学,上图中的目录结构应该不陌生。正常情况下我们只需要将不同版本的 so 文件分别放置。但如果我们要集成的这个第三方 SDK 偏偏没有 arm-v7a 的版本呢?是删除 armeabi-v7a 目录只保留 armeabi ?还是说两个目录下 .so 文件数不同也没有关系?系统会加载哪个 .so 呢?

如果只对结论感兴趣可以直接跳到最后

为了方便说明我们先引入 FAT Binary 的概念。我们知道不同的 CPU 支持的指令集也不一样,那么如果我们需要让 App 尽可能不同的 CPU 上都可以正常运行该怎么做呢?简单,只需要将不同版本的 Binary 放在一个文件里,运行时按需取用就可以了。这就是 FAT Binary 的典型实现。Android 实现 FAT 的方式有些不同,就是上边提到的将 .so 文件放置在相应文件夹中。在 Android 系统中 ndk 默认会生成如下 7 种 so。

在 apk 文件中带这么多版本的 .so 是一种很不经济的做法:

  • mips / mips64: 极少用于手机可以忽略
  • x86 / x86_64: x86 架构的手机都会包含由 Intel 提供的称为 Houdini 的指令集动态转码工具,实现 对 arm .so 的兼容,再考虑 x86 1% 以下的市场占有率,x86 相关的两个 .so 也是可以忽略的
  • armeabi: ARM v5 这是相当老旧的一个版本,缺少对浮点数计算的硬件支持,在需要大量计算时有性能瓶颈
  • armeabi-v7a: ARM v7 目前主流版本
  • arm64-v8a: 64位支持

这样我们就可以明确 mips, mips64, x86, x86_64 这 4 个 .so 我们是不需要的。

我们回到开头提到的问题:

假定我们现在的情况是这样的(b.so 就是那个只有 armeabi 版本的第三方 so):

如果这样放置的话,在 ARM / ARM v7 两种设备上运行 apk 时会分别执行哪个 so 呢?

答案是:不确定……

这么坑爹的答案是怎么来的呢?

由于 Android 上 FAT binrary 的设计如此阳春,在 apk 安装时就需要根据 CPU 情况执行对应版本 so 的拷贝。对上边的情况最合理的一种做法应该是使用 armeabi-v7a/a.so 和 armeabi/b.so 这两个文件。Google 最初也是这么想的,然后就引入了 Bug…

Native library copy issue when install apk with different abi native libraries on device

上图是到 Android 4.4 还在使用的 so 文件拷贝逻辑,看起来没有问题?

坑爹是 Android 在安装 apk 文件时没有保证 zip entry 的扫描顺序,所以同样的文件放置会带来两种不同的安装结果:

看的有点头晕?简而言之,如果按我们上面的放置方式,安装后系统可能只拷贝了 armeabi-v7a/a.so。如果执行到 b.so 的逻辑,程序显然会 crash

这边还有个小插曲,这个 bug 的发现者在提交时其实已经给出了完善的解决方案,但在经历了快有小一年的 code review 后 Android 官方表示:我们自己另起炉灶修好了=_=。

这个问题确实在 Android 5.0 已经 “修复” 了。“修复” 方式简单粗暴,不再以文件为粒度匹配 abi,直接拷贝整个文件夹=_=。所以如果按我们之前的放置方法,在 Android 5.0+ 如果执行到 b.so 也是一定会 crash 的。

上面提到,只保留 armeabi 文件夹从性能角度是不明智的。正确的做法是将 armeabi/b.so 复制一份到 armeabi-v7a/b.so. 这是由于 ARM v7 是前向兼容 ARM v5 的。

  • 为了减小 apk 体积,只保留 armeabi 和 armeabi-v7a 两个文件夹,并保证这两个文件夹中 so 数量一致
  • 对只提供 armeabi 版本的第三方 so,原样复制一份到 armeabi-v7a 文件夹

原文地址:https://zhuanlan.zhihu.com/p/21359984

Android 中与 so 有关的一个大坑的更多相关文章

  1. Android中调用C++函数的一个简单Demo

    这里我不想多解释什么,对于什么JNI和NDK的相关内容大家自己去百度或谷歌.我对Android的学习也只是个新手.废话少说直接进入正题. 一.在Eclipse中创建一个Android Applicat ...

  2. Android中在activity中弹出一个popwindow

    //-----在onCreate方法--中------创建popwindow布局   --pop_item--------------------------        View view=Lay ...

  3. android中返回数据给上一个活动,可以用来回显数据

    (一)who简介:没错,就是startActivityForResult()方法,这个方法用来在活动被销毁的时候返回数据给上一个方法.参数说明: startActivityForResult(inte ...

  4. Android中一个经典理解误区的剖析

    今天,在Q群中有网友(@广州-包晴天)发出了网上的一个相对经典的问题,问题具体见下图. 本来是无意写此文的,但群里多个网友热情不好推却,于是,撰此文予以分析. 从这个问题的陈述中,我们发现,提问者明显 ...

  5. Android中的sharedUserId属性详解

    在Android里面每个app都有一个唯一的linux user ID,则这样权限就被设置成该应用程序的文件只对该用户可见,只对该应用程序自身可见,而我们可以使他们对其他的应用程序可见,这会使我们用到 ...

  6. Android中@id与@+id区别

    Android中的组件需要用一个int类型的值来表示,这个值也就是组件标签中的id属性值. id属性只能接受资源类型的值,也就是必须以@开头的值,例如,@id/abc.@+id/xyz等. 如果在@后 ...

  7. Android中Activity启动模式详解

    在Android中每个界面都是一个Activity,切换界面操作其实是多个不同Activity之间的实例化操作.在Android中Activity的启动模式决定了Activity的启动运行方式. An ...

  8. Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用说明

    今天给大家介绍下Android中滑屏功能的一个基本实现过程以及原理初探,最后给大家重点讲解View视图中scrollTo 与 scrollBy这两个函数的区别 . 首先 ,我们必须明白在Android ...

  9. 【转】整理一下Android中的ListView

    原文网址:http://sunbofu.blog.51cto.com/6431507/1280441 Android中的listview目测是一个使用频率很高的组件,所以今天来总结一下listview ...

随机推荐

  1. 解决ansible首次连接host服务器需验证问题

    问题描述: [root@iZm5e79rtwsq2hm57teyk5Z ansible]# ansible aofeng -f 5 -m ping 47.93.18.191 | FAILED! =&g ...

  2. 值得珍藏的HTTP协议详解

    转自:http://www.cnblogs.com/li0803/archive/2008/11/03/1324746.html 引言 HTTP是一个属于应用层的面向对象的协议,由于其简捷.快速的方式 ...

  3. Django笔记--模型

    ORM是"对象-关系-映射"的简称,在Django当中,ORM就是模型类的管理器对象.操作顺序是先定义模型类,再定义模型类管理器,然后在模型类中实例化一个模型类管理器的对象,作为模 ...

  4. python while条件和if判断的总练习

    输出123456 89的数字 num =1 while num < 11: if num == 7: pass else: print(num) num = num + 1 输出1-100的奇数 ...

  5. CSharpGL(48)用ShadowVolume画模型的影子

    CSharpGL(48)用ShadowVolume画模型的影子 在Per-Fragment Operations & Tests阶段,有一个步骤是模版测试(Stencil Test).依靠这一 ...

  6. delphi 组件安装教程详解

    学习安装组件的最好方法,就是自己编写一个组件并安装一遍,然后就真正明白其中的原理了.   本例,编写了两个BPL, dclSimpleEdit.bpl 与 SimpleLabel.bpl ,其中,dc ...

  7. webpack4.1.1的使用详细教程

    安装全局webpack cnpm install -g webpack 安装全局webpack-cli npm install -g webpack-cli 初始化:生成package.json文件 ...

  8. ELK学习记录一 :初识ELK

    ELK是elastic公司提供的一套完整的收集日志并分析展示的产品,分别表示Elasticsearch.Logstash和kibana. (官网截个图) 先来一段个人粗浅的认识: Elasticsea ...

  9. 亲密接触Redis-第三天(Redis的Load Balance)

    前言 上两天讲述了Redis的基本搭建和基于HA的集群布署方式以及相关的策略和注意点.今天开始讲述Redis的Cluster功能,而这块目前来说网上资料不是太全,就算有1,2篇也只是单讲服务端的搭建也 ...

  10. Android简易实战教程--第三十九话《Chronometer实现倒计时》

    Android提供了实现按照秒计时的API,今天就是用这个API实现简单的倒计时. 来个布局: <?xml version="1.0" encoding="utf- ...