Android 15 适配之16K Page Size

15来了 适配问题也就来了,之前讲过Android 15行为变更:所有应用_软件包停止状态变更-CSDN博客 里面有简单提到过16K内存适配,

本文就针对16KB页面设备适配详细介绍一下,

16KB页面(Page Size)带来的好处

配置为 16 KB 页面的设备平均使用的内存会略多,但系统和应用性能也会有所提升:

  • 在系统面临内存压力时缩短应用启动时间:平均降低了 3.16%,对于我们测试过的一些应用而言,改进幅度更显著(提升幅度高达 30%)
  • 降低应用启动时的功耗:平均降低 4.56%
  • 相机启动速度更快:平均热启动速度加快 4.48%,冷启动速度平均加快 6.60%
  • 缩短了系统启动时间:平均缩短了 1.5%(约 0.8 秒)

 Page Size 

        页面(Page)指的就是 Linux 虚拟内存管理中使用的最小数据单位,页面大小(Page Size)就是虚拟地址空间中的页面大小, Linux 中进程的虚拟地址空间是由固定大小的页面组成。

对于虚拟内存, CPU 的内存管理单元(MMU)会将虚拟地址转换为物理地址,所以虚拟内存最终也会映射到物理内存页面。

而为了实现虚拟内存到物理的映射,两个地址空间都会被划分为多个固定页面,而虚拟空间和物理空间中的页面需要大小相同,通常长度为 4K,为了区分虚拟页面和物理页面,后者一般会被称为页框(page frames )。

因为 Android 用的是 Linux 内核,所以在内存逻辑一直以来都是遵循 Linux 的实现,只是 Android 由于“历史因素”限制,一直只支持 4 KB 内存页面大小,而现在为了优化系统内存性能,提高内存密集型工作负载的性能,Android 15 开始将采用 16KB 页面大小的要求。

异常日志:
当含有 “XXX.so” 的 android 项目,很可能需要重新编译带有全新的动态库(.so) 才能正常运行对应功能,不然大概率会 crash 。

Fatal signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x7a58c6e5fcd0 in tid 25818 (e.myapplication), pid 25818 (e.myapplication)
2024-07-29 17:10:24.790 25877-25877 DEBUG                   pid-25877                            A  Cmdline: com.example.myapplication
2024-07-29 17:10:24.790 25877-25877 DEBUG                   pid-25877                            A  pid: 25818, tid: 25818, name: e.myapplication  >>> com.example.myapplication <<<
2024-07-29 17:10:24.790 25877-25877 DEBUG                   pid-25877                            A        #00 pc 0000000000006804  /data/app/~~hK7B_wzimzcm9Nv2eKOnnQ==/com.example.myapplication-y1VtTf5VHdrAyLxw2DByug==/lib/arm64/libmediashow.so (BuildId: fb881d8f365f1b7861b7bd43c62490c2bce98522)
FATAL EXCEPTION: Thread-42
Process: com.aa.debug225, PID: 13380
 java.lang.NoClassDefFoundError: com.b.miitmdid.core.Mdelper
 	at com.utils.Abc.getDeviceIds(Abc.java:103)
 	at com.utils.Abc.getDeviceIds(Abc.java:67)
 	at com.utils.OAUtil$1.run(OAUtil.java:58)
 Caused by: java.lang.UnsatisfiedLinkError: No implementation found for int com.b.miitmdid.e.a() (tried Java_com_b_miitmdid_e_a and Java_com_b_miitmdid_e_a__) - is the library loaded, e.g. System.loadLibrary?
 	at com.b.miitmdid.e.a(Native Method)
 	at com.b.miitmdid.core.Mdelper.<clinit>(Unknown Source:0)
 	at com.utils.Abc.getDeviceIds(Abc.java:79)
 	at com.utils.Abc.getDeviceIds(Abc.java:67) 
 	at com.utils.OAUtil$1.run(OAUtil.java:58) 

查看是否存在动态库 

1、直接查看工程源码 libs下是否有so文件

2、AS->Build->Analyze APK 功能->打开对应apk文件(暴力方式 直接将apk文件拖拽到AS上面),在显示窗口中查看APK内容 lib 下面是否有so文件存在

是否可以做兼容:将4k 页面大小转换为 16k 页面大小?

理论上这是可行的,比如你可以使用虚拟化技术让 4K 的应用跑在 16K 主机上,但是想要系统直接混搭 Page Size 支持,是一个比较困难的事情。对于空间页面而言,在任意时刻要么是 4K,要么是 16K ,页表结构在 4K 模式和 16K 模式下完全不同(每级页面、级数、屏蔽)。

因为 Linux 不支持混合 Page Size,默认上 android 也不支持

如何检测

最简单且最实用的做法就是通过模拟器 VanillaIceCream 的 APIs Experimental 16k Page Size ARM 64 v8a System Image 进行测试。

另外,官方提供了一个 16 KB ELF 对齐脚本来验证共享库的 ELF 段是否正确对齐。

#!/bin/bash

# usage: alignment.sh path to search for *.so files

dir="$1"

RED="\e[31m"
GREEN="\e[32m"
ENDCOLOR="\e[0m"

matches="$(find $dir -name "*.so" -type f)"
IFS=$'\n'
for match in $matches; do
  res="$(objdump -p ${match} | grep LOAD | awk '{ print $NF }' | head -1)"
  if [[ $res =~ "2**14" ]] || [[ $res =~ "2**16" ]]; then
    echo -e "${match}: ${GREEN}ALIGNED${ENDCOLOR} ($res)"
  else
    echo -e "${match}: ${RED}UNALIGNED${ENDCOLOR} ($res)"
  fi
done

如果此时动态库是 ALIGNED 的,并且是 2**16 ,也就是 armv68a 的动态库此时的 ELF 对齐。

另外,通过 sdk 的 build-tools/35.0.0 下的 zipalign 命令运行:

./zipalign -c -P 16 -v 4 app-release.apk

80346011 res/zw4.xml (OK - compressed)
80347284 res/zyn.png (OK)
80373212 resources.arsc (OK)
82519476 ASCore-stats.properties (OK - compressed)
82519581 ASCore-ui.properties (OK - compressed)
82519710 META-INF/android.support.design_material.version (OK - compressed)
82524952 META-INF/kotlin-stdlib-jdk8.kotlin_module (OK - compressed)
82525149 META-INF/kotlin-stdlib.kotlin_module (OK - compressed)
82526568 META-INF/push.properties (OK - compressed)
82526718 META-INF/retrofit.kotlin_module (OK - compressed)
82526834 META-INF/rxjava.properties (OK - compressed)
82527331 META-INF/services/com.fasterxml.jackson.core.JsonFactory (OK - compressed)
82527473 META-INF/services/com.fasterxml.jackson.core.ObjectCodec (OK - compressed)
82527591 ddd-core.properties (OK - compressed)
91016415 picture/symbolletter.png (OK - compressed)
91018295 assets/info.y (OK - compressed)
91020374 classes.dex (OK - compressed)
105907159 lib/armeabi-v7a/libxxx-x86.so (OK - compressed)
106365387 META-INF/XET_KEYS.SF (OK - compressed)
106499474 META-INF/XET_KEYS.RSA (OK - compressed)
106500606 META-INF/MANIFEST.MF (OK - compressed)
Verification succesful
mac@MacdeMBP 35.0.0 %

可以看到此时的 apk 对齐也是没有问题的,所以此时的运行问题可能更多在于 C++ 代码里的 mmap 或者 sysconf 等代码存在问题,例如写死了 4096 等硬编码。

使用 16 KB ELF 对齐编译应用

16 KB 设备要求使用 16 KB ELF 对齐来正确对齐共享库的 ELF 段,以便应用运行

注意 :如果您的应用未将原生库提取到文件系统(extractNativeLibs 设置为 false),在使用 16 KB ELF 对齐进行编译后,您可能会注意到应用的二进制文件大小略有增加。Android 15 中对软件包管理器进行了优化,消除了因这一增加而产生的运行时成本。

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

​重要提示 :如果您的应用使用任何预构建共享库,您还必须以相同的方式重新编译这些库,并将 16 KB 对齐的库重新导入您的应用。
Android NDK r26 及更低版本

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

ndk-build​

更新 Android.mk 以启用 16 KB ELF 对齐:
LOCAL_LDFLAGS += "-Wl,-z,max-page-size=16384"


cmake 配置

更新 CMakeLists.txt 以启用 16 KB ELF 对齐:
target_link_options(${CMAKE_PROJECT_NAME} PRIVATE "-Wl,-z,max-page-size=16384")
​
重要提示 :如果应用从 Android NDK r26 或更低版本动态链接到 C++ 标准库 (libc++_shared.so),建议迁移至 Android NDK r27 或更高版本,以更新到与 16 KB ELF 匹配的 C++ 标准库版本。否则,应用将无法在 16 KB 设备上安装。

如果无法迁移到新版 Android NDK,可以更新应用,以将 C++ 标准库静态编译到共享库中。

Android NDK r27 及更高版本

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

//Application.mk 中:
APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true


//在 build.gradle 文件中,设置参数 -DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON:
android {
  ...
  defaultConfig {
    ...
    // This block is different from the one you use to link Gradle
    // to your CMake or ndk-build script.
    externalNativeBuild {
      // For ndk-build, instead use the ndkBuild block.
      cmake {
        // Passes optional arguments to CMake.
        arguments "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
      }
    }
  }
}

检查引用特定页面大小的代码实例

即使应用采用 16 KB 对齐,如果代码中放置的内容假定设备使用特定页面大小,也可能会遇到错误。为避免出现这种情况,需完成以下步骤:

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

    改用  getpagesize()或sysconf(_SC_PAGESIZE)

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

在某些情况下,如果应用使用 PAGE_SIZE 作为不与底层页面大小相关联的便捷值,那么在 16 KB 模式下使用时,不会导致应用崩溃。不过,如果将此值传递给包含 mmap 但不带 MAP_FIXED 的内核,内核仍会使用整个页面,而这会浪费一些内存。因此,在 NDK r27 及更高版本上启用 16 KB 模式时,PAGE_SIZE 未定义。

如果应用以这种方式使用 PAGE_SIZE 且从未将此值直接传递给内核,就不要使用 PAGE_SIZE,而是创建一个具有新名称的新变量,以反映它用于其他用途,而不是反映实际内存页面。

题外话:要是老 so 没有源码的情况下,基本就没有的救了

但好消息是Flutter 3.22是可以在16K模拟器上正常运行。

所以想要更早的适配15,还是早一点动手重新打包升级吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值