Android M 的 NDK 行为变更对 APK 包体积的影响

该来到的终会到来,如果你还没有经历过上下求索的磨砺,说明你的加快前进的脚步了。Google 的大神们早就挖好坑,头也不回的继续挖下一个坑去了,我们能做的只是尽量跟着这些人的脚步,不至于被甩开太远。开始正文之前,我想有必要知道 Android M(6.0)是 Google 在 2015 年 5 月 28 日的 Google I/O 大会上正式推出的版本,没错,这特么是一个 N 年前的版本!但这个版本的行为变更一直默默无闻的在那躺着无人问津,直到你将 minSdkVersion 指向了 23,这时你会发现 APK 签名包的体积极大的超出了预期。到这,你是不是能理解一个程序员在文章开头发出的感叹了?

1. 奇怪的 APK 体积差异

体积对比

上图展示的是 minSdkVersion 分别为 22 和 23 时 APK 签名包的体积对比,估计你看到 68.4M 体积差异的感受和我是一样的,这特么是在逗我?因为,这张图里的总量差异不等于分量差异的和,并且总量差异与分量差异的和的差值也很大,这才是困惑大部分人的原因所在。所以,这里需要说明下 Android Studio 里对比分析 APK 的逻辑:

位置对比类型逻辑描述
第一行总量总量对比只比较 APK 文件的体积大小
剩余行分量分量对比是将 APK 文件解压后对其所含文件进行逐一对比

看完这个对比逻辑后,大概能理解两个 APK 包之间的分量对比差异很小的原因了。因为这两个 APK 包是由同一套代码打包而来,唯一的区别就是 minSdkVersion 的不同。但是,为什么总量差异(也就是 APK 包的体积)会那么大呢?因为分量差异对比的是解压后的文件,那就意味着两个 APK 中有一个在打包过程中对文件进行了压缩,只有这种情况才会呈现出总量大小不一致,而分量又基本相等的情况。

2. Android M 的 NDK 行为变更

通过 Google 搜索,终于有文章将原因指向了 节点下的 extractNativeLibs 属性,紧接着在官方文档中找到了关于 android:extractNativeLibs 的描述。

属性描述
android:extractNativeLibs软件包安装程序是否将原生库从 APK 提取到文件系统。如果设为 false,则原生库必须保持页面对齐状态并以未压缩的形式存储在 APK 中。无需更改代码,因为链接器在运行时直接从 APK 加载库。默认值为 “true”。

这段描述的意思是:如果 extractNativeLibs 属性的值为 false,那么 APK 在打包签名过程中 so 库会保持页面对齐状态并以未压缩的形式被打包到签名包中,并且在 APK 安装的过程也不会将应用内的 so 库提取到系统中。因为 so 库没有被压缩,所以调用 so 库的时候也是直接从 APK 中进行加载。如果 extractNativeLibs 属性的值为 true,那么 APK 在打包签名过程中 so 库会以压缩的形式被打包到签名包中,并且会在 APK 安装的过程中将应用内的 so 库提取到系统中,调用 so 库的时候会从系统的提取目录中进行加载。

如果 extractNativeLibs 属性值为 false 有什么好处呢?可以节省用户手机的空间。因为应用和系统共同持有一份 so 库,但由于 so 库在打包过程中没有被压缩,会造成签名包体积增大。如果 extractNativeLibs 属性值为 true,那么 so 库会被压缩打包到应用中,优点是 APK 体积会很小;但缺点也显而易见,安装后的应用会持有一份压缩的 so 库,系统会持有一份解压后的 so 库,那份无法被调用的压缩 so 库会占用用户的手机空间。

明白了这些,我们继续看上面的属性描述,描述中说到默认值为 true 但没有明确从哪个 SDK 开始这个值会被动态的改为 false,直到我给 IssueTracker 提交了 Android Studio 4.0 Generate Signed APK BUG,官方的小伙伴才有了如下回复:
tracker

翻译:如果 minSdkVersion 为 23+,开发人员也没有手动设置 android:extractNativeLibs=”true”,系统会在清单中自动注入 android:extractNativeLibs=”false”。

tracker

翻译:如果 API 级别是 23+,AGP 将不再压缩 APK 中的 so 文件,因为 Android 23+ 能够直接从 APK 中读取它们,而不必提取它们。
这意味着 APK 体积较大时,实际的安装体积反而更小:
• 当 API <= 22 时,已安装的 APK 持有压缩的 so 文件和从 APK 中提取的 so 副本。
• 当 API >= 23 时,已安装的 APK 仅持有未压缩的 so 文件,而没有提取的 so 副本。
虽然 APK 体积更大了,但下载大小不会受到影响,因为应用商店最终会使用压缩补丁。

3. 沉睡的 NDK 行为变更文档

虽然已经完全清楚了包体积变大的原因,但我的内心依然有个疑惑?这么剧烈的行为变更不可能仅靠几个程序员口口相传吧!我在查找问题的过程中一定漏掉了什么。终于,在 Android 源码的一个备注文档 Android linker changes for NDK developers 中找到了行为变更的准确出处:

Opening shared libraries directly from an APK

In API level 23 and above, it’s possible to open a .so file directly from your APK. Just use `System.loadLibrary("foo")` exactly as normal but set `android:extractNativeLibs="false"` in your `AndroidManifest.xml`. In older releases, the .so files were extracted from the APK file at install time. This meant that they took up space in your APK and again in your installation directory (and this was counted against you and reported to the user as space taken up by your app). Any .so file that you want to load directly from your APK must be page aligned (on a 4096-byte boundary) in the zip file and stored uncompressed. Current versions of the zipalign tool take care of alignment.

Note that in API level 23 and above dlopen(3) will open a library from any zip file, not just your APK. Just give dlopen(3) a path of the form “my_zip_file.zip!/libs/libstuff.so”. As with APKs, the library must be page-aligned and stored uncompressed for this to work.

紧接着,又在 Google Git 上找到了一处标识为 988bbf3e4fb5e56c050d06665efc3749d74bf6cb 的提交,一个名为 Dmitriy Ivanov 的程序员于 2015 年 5 月 19 日执行了这次提交,9 天后,也就是 2015 年 5 月 28 日,Google I/O 大会上正式推出了 Android M(6.0),意不意外,惊不惊喜!这处提交的 Bug 描述指向了 IssueTracker 的 Gradle support for open native libraries directly from apk,在这个贴子里有详细的关于 Android 运行时支持直接从 APK 打开库的描述,但只有少的可怜的 9 个人关注了这个 issue,包括我在内。

tracker

4. 开发者的困惑

绝大部分开发者设置的 MiniSDK 都是 21 或者更低,因为他们希望自己的 APP 也能够运行在这些低版本系统的手机上。也正是因为这样的设置,绝大部分开发者都不会有 APK 体积变大的困扰。但随着 Android 的版本迭代和手机厂商的跟进,MiniSDK = 23+ 的日子终究会到来,到时候会有很多像我一样困惑的开发者。所以,写这篇文章希望能为几年后遇到同样问题又一头雾水的开发者解惑。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值