Android 15 大变更:支持 16K 内存分页,所有 native app 必须重编译~

图片

一直以来,Android 系统仅支持 4KB 的内存分页大小,这在一定程度上限制了整体的性能。而随着设备制造商不断打造具有更大物理内存 (RAM) 的设备,这些设备中的许多可能会配置 16 KB(最终更大)的分页大小,作为 Android 系统设计者,是时候考虑是否需要支持 16KB 的设备了。

所以,Google 团队的 Kalesh Singh 和 Juan Yescas 于 2023 年针对 16KB 分页大小的 Android 性能表现进行了详细测试。并发现,在 Pixel 6 和 Pixel 6 Pro 上进行的 Benchmark 显示:

  • 内存 page 引发的错误大幅减少

  • 系统 boot 加快,缩短了 1.5%,约 0.8s

  • App 启动加快,平均节省约 3.16% 的时间。其中部分 App 优化尤为显著,比如:Google Search 节省了 17% 的时间;Google News 更是节省了30% 的时间

  • 手机电量消耗平均下降了 4.56%

  • 等等

详细的信息见《Android - 16K Page Size Support pdf》论文,充分说明了各项 benchmark 的结果、影响等细节。

图片

最终 Google 决定在 Android 15 上支持 16K 内存分页大小的设备。可是既存的 App 都是以 4KB 内存分页大小进行的编译、部署,想要支持运行在 16KB 上是不行的,所以 Android 官方提供了一些指南进行说明。

笔者以国民 app WeChat 为例,查看是否会受影响、是否真得受到了影响、以及如何处理。

1. 如何判断 App 是否会受到影响?

如果 App 使用了任何原生代码,则无关 App 的 target SDK version,必然会受影响。反之,如果仅使用以 Java 编程语言或 Kotlin 编写的代码(包括所有 LIB / SDK),那么该 App 已经支持 16 KB 设备,不用额外适配。如果不确定自己的 App 是否使用了原生代码、或者引用的 LIB / SDK 是否使用了原生代码,则可以使用 AS 自带的 APK Analyzer 进行识别。

话说回来,即便可以肯定自己的 App 没有使用到原生代码,为防止以外造成的回退,仍然推荐在时间富余的情况在 16KB 的环境中运行和测试一番。

是否使用了原生代码?

App 符合以下任一情况,则会使用原生代码:

  • 使用任何 C/C++(原生)代码。如果 App 使用 Android NDK,那么该 App 将使用原生代码

  • 会与使用它们的任何第三方原生库或依赖项相关联

  • 由使用设备上的原生库的第三方 App 构建器构建

使用 APK 分析器识别原生库

我们从网上下载 Wechat 的 apk 包,直接拖入 AS,会自动使用 APK Analyzer 工具进行分析(当然也可以按照路径逐步打开:File -> Open -> xxx.apk)

图片

 

打开识别到的 lib 目录,可以看到一堆 so 文件,毫无悬念肯定使用到了原生代码。

图片

2. 直接测试 app 是否受到影响?

正如前面章节提到的,如果 App 使用到了原生代码,一定要在 Android 15 16KB 内存分页的设备上进行测试,以查看 App 是否出现任何回归问题。

对于仍然在开发中的 Android 15 来说,官方提供了多个方式以供验证:

  • 使用基于 16-KB 的 Android 15 系统映像设置 Android 模拟器

  • 使用页面大小为 16KB 的编译 Cuttlefish 镜像:氛围 ARM64 和 x86-64 版本:

    • 在 ARM64 上使用页面大小为 16KB 的 Cuttlefish

    • 在 x86-64 上模拟 Cuttlefish,页面大小为 16KB

我们采用比较简单、易用的模拟器方式进行验证,首先得部署该环境。

部署基于 16 KB 的 Android 15 模拟器

重要提示: 由于 Android 15 Beta 2 中的已知问题,使用 LLDB 进行调试目前尚不支持 16 KB 模拟器系统映像。

可以按以下步骤操作:

  1. 基于 16 KB 的 Android 15 模拟器系统映像与 Android Studio Jellyfish | 2023.3.1 或更高版本兼容。不过,为了在使用 Android 15 Beta 版时获得最佳体验,请下载最新预览版 Android Studio。

  2. 在 Android Studio 中,依次点击 Tools > SDK Manager

  3. 在 SDK Platforms 标签页中,展开 Android VanillaIceCream Preview 部分,然后选择以下一个或两个模拟器系统映像,具体取决于要创建的虚拟设备:

    注意 :如果打算模拟受支持的 Google Pixel 设备,则只需要 ARM 64 v8a 系统映像。

    图片

    使用 Android Studio 中的 SDK 管理器下载 16 KB 模拟器系统映像

    • Google API 实验性 16k 页面大小 ARM 64 v8a 系统映像

    • Google API 实验性 16k 页面大小 Intel x86_64 Atom 系统映像

  4. 点击 Apply > OK 以下载选择的任何系统映像。

  5. 按照相关步骤针对 Android 15 设置虚拟设备,当系统提示选择系统映像时,请选择下载的 16 KB 系统映像。如果系统未自动推荐这种做法,可以在 Other Images 标签页中找到 16 KB 系统映像。

    图片

    在“Other Images”标签页中找到 16 KB 模拟器映像

测试 Wechat

测试 16KB 之前,我们在 Android 15 的未开启 16KB 设置的版本即只支持 4KB 内存分页,进行尝试。

adb install -r -d -t /Users/ellisonchan/Downloads/weixin7010android1580_arm64.apk

安装 Wechat 发现启动成功,没有任何问题。

图片

其次,我们按照官方文档指导的那样创建 16KB 开启的镜像,安装 Wechat 后进行尝试。

图片

可以看到 Wechat 打开失败,crash 了,我们摘取出问题的 log:确实是 Wechat 的 so 部分引发了加载失败。

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
 Build fingerprint: 'google/sdk_gphone16k_arm64/emu64a16k:VanillaIceCream/AP31.240426.022.B1/11834390:userdebug/dev-keys'
 Revision: '0'
 ABI: 'arm64'
 Timestamp: 2024-06-17 22:11:39.492045821+0800
 Process uptime: 1s
 Cmdline: com.tencent.mm
 pid: 3620, tid: 3620, name: com.tencent.mm  >>> com.tencent.mm <<<
 uid: 10192
 tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
 pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
 signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x000078d8180d3b18
     x0  0000000000000000  x1  0000000000000001  x2  000078d8181faed0  x3  000078db527f50b0
     x4  000078db527f5100  x5  0000000001414d4c  x6  0000000001414d4c  x7  000078db527f4004
     x8  0000000000000100  x9  000078db527f7e00  x10 000000000000007f  x11 757274736e6f6320
     x12 676e696c6c61632b  x13 0000000000000000  x14 0000000000000008  x15 0000000000000000
     x16 0000000000000001  x17 000078db4c539254  x18 fffffffffffffff7  x19 000078d8180d3b18
     x20 000078d8181faef8  x21 000078d8181faed0  x22 000078db52670ac0  x23 000078db52670ac0
     x24 000078db53b57000  x25 0000000000000007  x26 000078db53b53000  x27 000078db53b53000
     x28 000078db53b53000  x29 00007fffeee59320
     lr  000078d8181b7fe4  sp  00007fffeee59300  pc  000078d8181b805c  pst 0000000040001000
 60 total frames
 backtrace:
       #00 pc 00000000000b405c  /data/app/~~0SlVVSl-ojRlf9CQhyIeLw==/com.tencent.mm-Ur-5Z_vXquqYWD_eymkkaQ==/lib/arm64/libc++_shared.so (__cxa_guard_acquire+152) (BuildId: a9c0aad7747976d255d9ea273f6f4c5003901317)
       #01 pc 0000000000024c1c  /data/app/~~0SlVVSl-ojRlf9CQhyIeLw==/com.tencent.mm-Ur-5Z_vXquqYWD_eymkkaQ==/lib/arm64/libwechatxlog.so (BuildId: 5eb1ac5a41e996c9d51422577619493a50d25449)
       #02 pc 00000000000054cc  /data/app/~~0SlVVSl-ojRlf9CQhyIeLw==/com.tencent.mm-Ur-5Z_vXquqYWD_eymkkaQ==/lib/arm64/libwechatxlog.so (BuildId: 5eb1ac5a41e996c9d51422577619493a50d25449)
       #03 pc 00000000000b37fc  /apex/com.android.runtime/bin/linker64 (__dl__ZN6soinfo17call_constructorsEv+760) (BuildId: c08adba44c0fc120e06d56d862fce57e)
       #04 pc 0000000000099a4c  /apex/com.android.runtime/bin/linker64 (__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv+732) (BuildId: c08adba44c0fc120e06d56d862fce57e)
       #05 pc 00000000000950a8  /apex/com.android.runtime/bin/linker64 (__dl__ZL10dlopen_extPKciPK17android_dlextinfoPKv.__uniq.234527301065430621646263515731762262959+72) (BuildId: c08adba44c0fc120e06d56d862fce57e)
       #06 pc 00000000000040c8  /apex/com.android.runtime/lib64/bionic/libdl.so (android_dlopen_ext+16) (BuildId: 78ed0a0bc595ebbb171246d2fec02b93)
       #07 pc 000000000001bf30  /apex/com.android.art/lib64/libnativeloader.so (android::NativeLoaderNamespace::Load(char const*) const+140) (BuildId: 876011460da2b12581e2c19c5303598a)
       #08 pc 000000000000d1a0  /apex/com.android.art/lib64/libnativeloader.so (OpenNativeLibrary+1196) (BuildId: 876011460da2b12581e2c19c5303598a)
       #09 pc 000000000066bbbc  /apex/com.android.art/lib64/libart.so (art::JavaVMExt::LoadNativeLibrary(_JNIEnv*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, _jobject*, _jclass*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*)+620) (BuildId: 00a927538d6e55b7346c6547b4436b00)
       #10 pc 00000000000058a8  /apex/com.android.art/lib64/libopenjdkjvm.so (JVM_NativeLoad+364) (BuildId: fee884f6680e39017670ed6bdbe02f38)
       #11 pc 000000000009c764  /system/framework/arm64/boot.oat (art_jni_trampoline+148) (BuildId: dcaffa30214d477b1969b6438f214e603fe86308)
       #12 pc 00000000000aca48  /system/framework/arm64/boot.oat (java.lang.Runtime.load0+424) (BuildId: dcaffa30214d477b1969b6438f214e603fe86308)
       #13 pc 000000000073d570  /apex/com.android.art/lib64/libart.so (nterp_helper+4016) (BuildId: 00a927538d6e55b7346c6547b4436b00)
       #14 pc 0000000000109c88  /apex/com.android.art/javalib/core-oj.jar (java.lang.Runtime.load+8)
       #15 pc 000000000073d514  /apex/com.android.art/lib64/libart.so (nterp_helper+3924) (BuildId: 00a927538d6e55b7346c6547b4436b00)
       #16 pc 0000000000312afc  /data/app/~~0SlVVSl-ojRlf9CQhyIeLw==/com.tencent.mm-Ur-5Z_vXquqYWD_eymkkaQ==/oat/arm64/base.vdex (com.tencent.mm.compatible.util.k.Av+356)
       #17 pc 000000000073c5f4  /apex/com.android.art/lib64/libart.so (nterp_helper+52) (BuildId: 00a927538d6e55b7346c6547b4436b00)
       #18 pc 000000000055dce0  /data/app/~~0SlVVSl-ojRlf9CQhyIeLw==/com.tencent.mm-Ur-5Z_vXquqYWD_eymkkaQ==/oat/arm64/base.vdex (com.tencent.mm.app.MMApplicationWrapper.onBaseContextAttached+180)
       #19 pc 000000000073e334  /apex/com.android.art/lib64/libart.so (nterp_helper+7540) (BuildId: 00a927538d6e55b7346c6547b4436b00)
       #20 pc 000000000076f124  /data/app/~~0SlVVSl-ojRlf9CQhyIeLw==/com.tencent.mm-Ur-5Z_vXquqYWD_eymkkaQ==/oat/arm64/base.vdex (com.tencent.mm.app.MMApplicationLike.onBaseContextAttached+1568)
       #21 pc 000000000073d514  /apex/com.android.art/lib64/libart.so (nterp_helper+3924) (BuildId: 00a927538d6e55b7346c6547b4436b00)
       #22 pc 00000000047def68  /data/app/~~0SlVVSl-ojRlf9CQhyIeLw==/com.tencent.mm-Ur-5Z_vXquqYWD_eymkkaQ==/oat/arm64/base.vdex (com.tencent.tinker.entry.TinkerApplicationInlineFence.handleMessageImpl+40)
       #23 pc 000000000073d514  /apex/com.android.art/lib64/libart.so (nterp_helper+3924) (BuildId: 00a927538d6e55b7346c6547b4436b00)
       #24 pc 00000000047df0a6  /data/app/~~0SlVVSl-ojRlf9CQhyIeLw==/com.tencent.mm-Ur-5Z_vXquqYWD_eymkkaQ==/oat/arm64/base.vdex (com.tencent.tinker.entry.TinkerApplicationInlineFence.handleMessage_$noinline$+6)
       #25 pc 000000000073d514  /apex/com.android.art/lib64/libart.so (nterp_helper+3924) (BuildId: 00a927538d6e55b7346c6547b4436b00)
       #26 pc 00000000047df0d4  /data/app/~~0SlVVSl-ojRlf9CQhyIeLw==/com.tencent.mm-Ur-5Z_vXquqYWD_eymkkaQ==/oat/arm64/base.vdex (com.tencent.tinker.entry.TinkerApplicationInlineFence.handleMessage+0)
       #27 pc 00000000003aeb74  /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+612) (BuildId: 00a927538d6e55b7346c6547b4436b00)
       #28 pc 000000000052ed00  /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)+1004) (BuildId: 00a927538d6e55b7346c6547b4436b00) :       #29 pc 0000000000533c88  /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>(art::interpreter::SwitchImplContext*)+13620) (BuildId: 00a927538d6e55b7346c6547b4436b00)

3. 如何重新编译支持 16 KB?

如需支持 16 KB 设备,使用原生代码的 App 应完成下面几部分中列出的步骤。

3.1 更新到 AGP 版本 8.3 或更高版本

16 KB 设备要求附带未压缩共享库的 App 在 16 KB 的压缩对齐边界上对齐它们。为此,需要升级到 Android Gradle 插件 (AGP) 8.3 或更高版本。如需详细了解升级流程,请参阅 Android Gradle 插件升级助理部分。

建议使用未压缩的共享库,但如果无法将 AGP 升级到版本 8.3 或更高版本,替代方案是改为使用压缩的共享库。更新 Gradle 配置,让 Gradle 在打包 App 时压缩共享库,以避免未对齐的共享库出现 App 安装问题。

在 build.gradle 文件中,添加以下选项:

android {
   ...
   packagingOptions {
       jniLibs {
         useLegacyPackaging true
       }
   }
 }

3.2 使用 16 KB ELF 对齐编译 App

16 KB 设备要求共享库的 ELF 段使用 16 KB ELF 对齐正确对齐, App 才能运行。

注意 :如果 App 不会将原生库提取到文件系统(extractNativeLibs 设置为 false),那么在使用 16 KB ELF 对齐进行编译后,可能会发现 App 的二进制文件大小略有增加。对 Android 15 中的软件包管理器进行优化后,这种增加可抵消运行时费用。

如需使用 16 KB ELF 对齐方式编译 App ,请根据所使用的 Android NDK 版本,完成以下某一部分中的步骤。

重要提示 :如果 App 使用任何预构建共享库,还必须以相同的方式重新编译这些共享库,并将 16 KB 对齐的库重新导入 App 。

Android NDK r26 及更低版本

如需支持使用 Android NDK 版本 r26 或更低版本编译 16 KB 对齐的共享库,需要更新 ndk-build 或 cmake 配置,如下所示:

ndk-buildCMake

更新 Android.mk 以启用 16 KB ELF 对齐:

LOCAL_LDFLAGS += "-Wl,-z,max-page-size=16384"

重要提示 :如果 App 从 Android NDK r26 或更低版本动态链接到 C++ 标准库 (libc++_shared.so),建议迁移到 Android NDK r27 或更高版本,以更新到 16 KB ELF 对齐的 C++ 标准库版本。否则, App 将无法在 16 KB 设备上安装。

如果无法迁移到较新的 Android NDK,或许可以更新 App,以将 C++ 标准库静态编译到共享库中。如需详细了解如何静态链接到 C++ 标准库,请参阅 C++ 库支持部分,并确保阅读重要注意事项部分。

Android NDK r27 及更高版本

如需支持使用 Android NDK 版本 r27 及更高版本编译 16 KB 对齐的共享库,需要按如下方式更新 ndk-buildbuild.gradlebuild.gradle.kts 或链接器标志:

ndk-buildGroovyKotlin其他构建系统

在 Application.mk 中:

APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true

3.3 检查是否存在引用特定页面大小的代码实例

如果代码中的位置假定设备使用的是特定的页面大小,那么即使 App 是 16 KB 对齐的, App 也可能遇到错误。为避免这种情况,请完成以下步骤:

  1. 移除任何引用 PAGE_SIZE 常量或代码逻辑中假定设备的页面大小为 4 KB (4096) 的实例的硬编码依赖项。

    请改用 getpagesize() 或 sysconf(_SC_PAGESIZE)

  2. 查找 mmap() 和其他需要页面对齐参数的 API 的使用情况,并在必要时将其替换为替代方案。

4. 参考资料

  • Android 15 针对 16KB 内存分页的支持:https://developer.android.google.cn/about/versions/15/behavior-changes-all#16-kb

  • Android 内存分页大小:https://developer.android.google.cn/guide/practices/page-sizes

  • Android 内存管理:https://developer.android.google.cn/topic/performance/memory-management

  • Android - 16K Page Size Support pdf:https://lpc.events/event/17/contributions/1443/attachments/1144/2566/Android%2016K%20Page%20Size%20Support%20-%20LPC%20-%202023.pdf

转自:Android 15 大变更:支持 16K 内存分页,所有 native app 必须重编译~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值