Android 中与 so 有关的一个大坑
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 文件夹
Android 中与 so 有关的一个大坑的更多相关文章
- Android中调用C++函数的一个简单Demo
这里我不想多解释什么,对于什么JNI和NDK的相关内容大家自己去百度或谷歌.我对Android的学习也只是个新手.废话少说直接进入正题. 一.在Eclipse中创建一个Android Applicat ...
- Android中在activity中弹出一个popwindow
//-----在onCreate方法--中------创建popwindow布局 --pop_item-------------------------- View view=Lay ...
- android中返回数据给上一个活动,可以用来回显数据
(一)who简介:没错,就是startActivityForResult()方法,这个方法用来在活动被销毁的时候返回数据给上一个方法.参数说明: startActivityForResult(inte ...
- Android中一个经典理解误区的剖析
今天,在Q群中有网友(@广州-包晴天)发出了网上的一个相对经典的问题,问题具体见下图. 本来是无意写此文的,但群里多个网友热情不好推却,于是,撰此文予以分析. 从这个问题的陈述中,我们发现,提问者明显 ...
- Android中的sharedUserId属性详解
在Android里面每个app都有一个唯一的linux user ID,则这样权限就被设置成该应用程序的文件只对该用户可见,只对该应用程序自身可见,而我们可以使他们对其他的应用程序可见,这会使我们用到 ...
- Android中@id与@+id区别
Android中的组件需要用一个int类型的值来表示,这个值也就是组件标签中的id属性值. id属性只能接受资源类型的值,也就是必须以@开头的值,例如,@id/abc.@+id/xyz等. 如果在@后 ...
- Android中Activity启动模式详解
在Android中每个界面都是一个Activity,切换界面操作其实是多个不同Activity之间的实例化操作.在Android中Activity的启动模式决定了Activity的启动运行方式. An ...
- Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用说明
今天给大家介绍下Android中滑屏功能的一个基本实现过程以及原理初探,最后给大家重点讲解View视图中scrollTo 与 scrollBy这两个函数的区别 . 首先 ,我们必须明白在Android ...
- 【转】整理一下Android中的ListView
原文网址:http://sunbofu.blog.51cto.com/6431507/1280441 Android中的listview目测是一个使用频率很高的组件,所以今天来总结一下listview ...
随机推荐
- .net如何引用System.Drawing.Drawing2D 命名空间和System.Drawing.Image及其相关概念
其实这个很简单,直接在引用那里单击右键选择添加框架,然后找到System.Drawing就OK了, 其实并没有网上所说的那样需要下载什么Drawing.BLL. 首先Syetem.Drawing.Dr ...
- Jackson工具
Jackson Jackson包含一个core JAR,和两个依赖core JAR的JAR: jackson-core-2.2.3.jar(核心jar包,下载地址) jackson-annotatio ...
- JVM Class字节码之三-使用BCEL改变类属性
使用BCEL动态改变Class内容 之前对Class文件中的常量池,Method的字节码指令进行了说明.JVM Class详解之一JVM Class详解之二 Method字节码指令现在我们开始实际动手 ...
- C++ 实现俄罗斯方块
C++ 实现俄罗斯方块 一.实验介绍 1.1 实验内容 本节实验我们进行设计俄罗斯方块前的思路分析,以及介绍ncurses 库的使用方法. 1.2 实验知识点 C++ 编程基础 ncurses 库的使 ...
- 使用WeihanLi.Redis操作Redis
WeihanLi.Redis Intro StackExchange.Redis 扩展,更简单的泛型操作,并提供一些的适用于业务场景中的扩展 基于 Redis 的五种数据类型扩展出了一些应用: Str ...
- User-Agent-Switcher和fiddler
浏览器模拟器(可以模拟各种浏览器效果,浏览器中看手机显示的效果) http://chromecj.com/web-development/2014-09/70.html User-Agent-Swit ...
- Linux 下的一个全新的性能测量和调式诊断工具 Systemtap, 第 2 部分: DTrace
DTrace的原理本系列文章详细地介绍了一个 Linux 下的全新的调式.诊断和性能测量工具 Systemtap 和它所依赖的基础 kprobe 以及促使开发该工具的先驱 DTrace 并给出实际使用 ...
- Visual studio debug—Process with an Id of 5616 is not running的解决方法
今天调试的时候,碰到下面的问题 打开项目的csproj文件,拉到最下方找我我图中红框中的部分,删除它即可.
- Android程序员必须掌握的知识点-多进程和多线程
当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程.默认情况下,同一应用的所有组件在相同的进程和线程(称为"主" ...
- java获取ip的方式,注意多级代理的方式获取
public String getIP() { String clientIP = ServletActionContext.getRequest().getHeader("x-forwar ...