16KB 页大小兼容:为什么一个 .so 会因为内存页变大而无法运行
这一篇看起来很底层,但先别怕。我们先从“内存页”这个概念讲起。
Android 15 开始,AOSP 支持配置为 16KB page size 的设备。官方文档明确说明:如果 App 直接或间接使用 NDK native 库,就需要重新构建以支持这些 16KB 设备。
内存页是什么
程序看到的是一大片连续内存,但物理内存并不是这样简单分配的。操作系统会把内存切成固定大小的小块管理,这些小块叫 page,中文常叫内存页。
可以把内存页想成仓库货架上的格子。以前一个格子 4KB,现在某些设备一个格子 16KB。
4KB page:每个格子小,管理粒度细
16KB page:每个格子大,管理条目更少,某些场景性能更好
问题来了:native .so 文件里有一些段需要按 page 对齐。如果 .so 还是按 4KB 方式排布,16KB 设备上的 linker 可能无法正确映射。
为什么 Java/Kotlin 代码通常不感知
纯 Kotlin/Java 代码运行在 ART 上,由系统运行时管理。
但 .so 是 ELF 二进制,运行时 linker 会直接把它映射到进程地址空间。
所以 16KB 影响的是 native 二进制的装载约束。
纯 managed 代码:多数情况下无需关心
包含 .so:必须确认 .so 页对齐兼容
三方 SDK 包含 .so:也算你的 App 间接使用 native 库
最常见风险:三方预编译库
你自己的 C++ 可以升级 NDK 后重编。麻烦的是三方 AAR 里的 .so。
lib/arm64-v8a/libxxx.so
lib/x86_64/libxxx.so
如果它们是旧 NDK 或旧链接参数构建的,你的 App 即使自己没写 native 代码,也可能被 Google Play 或设备运行时判定不兼容。
代码层风险:硬编码 PAGE_SIZE
很多历史代码会写:
constexpr size_t kPageSize = 4096;
这在 4KB 设备上没问题,在 16KB 设备上就是隐患。
更稳的方式是运行时获取。
#include <unistd.h>
size_t runtimePageSize() {
long value = sysconf(_SC_PAGESIZE);
if (value <= 0) {
return 4096;
}
return static_cast<size_t>(value);
}
凡是涉及 mmap、内存池、文件映射、对齐计算的代码,都要避免写死 4096。
构建层策略
官方 16KB 文档建议使用支持 16KB page size 的新工具链重建 native 库。工程上应做三件事。
升级 NDK/AGP 到支持 16KB 的版本组合
所有自研 .so 全量重编
所有三方 .so 做兼容性盘点
不要只改一个 ABI。每个 ABI 目录下的 .so 都要检查。
检查 ELF 对齐
可以用 llvm-readelf 查看 load segment 对齐。
llvm-readelf -l libplayer_core.so
关注 LOAD 段的 Align。
LOAD ... Align 0x4000
0x4000 就是 16KB。
APK 对齐也要检查
除了 .so 自身,打包也有对齐问题。官方文档提到可以使用新版构建工具,并检查 APK 中 native 库对齐情况。
工程上把检查放进 CI。
构建 APK/AAB
解包或使用工具扫描 lib/**/*.so
检查所有 ABI 下的 .so
报告不兼容库名和来源
本章实验
做一张 native 库清单。
库名
ABI
来源:自研 / 三方 SDK
是否可重编
LOAD Align
是否通过 16KB 检查
示例:
libplayer_core.so arm64-v8a 自研 Align=0x4000 PASS
libthird_party.so arm64-v8a 三方 Align=0x1000 FAIL
发现 FAIL 时,优先升级三方 SDK。如果三方没有版本支持,就需要替换依赖或联系供应方。
小白容易误解的地方
第一,16KB 不是让你的代码“申请更多内存”。它说的是系统管理内存的基本粒度变了。
第二,16KB 不是只影响你自己写的 C++。只要 APK 里有 .so,哪怕来自三方 SDK,也要检查。
第三,升级 Gradle 或 compileSdk 不等于自动解决所有问题。真正要看的是最终打包进去的每一个 .so 是否兼容。
第四,模拟器通过不代表真机通过。不同设备、不同 ABI、不同打包路径都需要审计。
工程风险与观测
16KB 问题适合做成发布门禁。
扫描 APK/AAB 中所有 lib/**/*.so
记录 ABI、库名、来源、Align
任何 0x1000 对齐库进入阻断项
三方库无法升级时进入风险审计
还要检查代码里的硬编码。
rg "4096|0x1000|PAGE_SIZE" src/main/cpp
看到 4096 不一定就是错,但必须知道它表达的是协议常量、文件格式常量,还是系统 page size。只有第三类需要改成运行时获取。
线上观测建议记录:
device api
abi
native libraries
page size
load failure message
如果未来某批设备只在启动阶段崩溃,这些信息能帮助你快速判断是否和页大小有关。
推荐迁移顺序
如果项目已经上线,不要一次性盲目升级所有东西。建议按这个顺序迁移。
第一步:列出 APK/AAB 中所有 .so
第二步:区分自研库和三方库
第三步:自研库升级 NDK 后重编
第四步:三方库升级到支持 16KB 的版本
第五步:用 llvm-readelf 检查 LOAD Align
第六步:跑 16KB 兼容设备或模拟环境
第七步:把检查加入 CI
这套顺序的重点是“先盘点,再修改”。如果直接升级工具链,遇到失败时你不知道是自研库、三方库、打包配置还是硬编码 page size 引起的。
迁移记录也要保留。
库名
旧版本
新版本
是否可重编
是否通过检查
负责人
备注
这样后续三方 SDK 再升级时,不会把已经解决的 16KB 问题重新带回来。
本章结论
16KB 页大小不是“性能小知识”,而是 native 交付硬约束。只要 App 里存在 .so,就必须把页对齐、三方库来源、硬编码 page size 和 CI 检查纳入工程流程。