当前位置: 首页 > news >正文

如何查看项目是否支持最新 Android 16K Page Size 一文汇总

前几天刚聊过 《Google 开始正式强制 Android 适配 16 K Page Size》 之后,被问到最多的问题是「怎么查看项目是否支持 16K Page Size」 ?其实有很多直接的方式,但是最难的是当你的项目有很多依赖时,怎么知道这个「不支持的动态库 so」 文件是哪个依赖?有不少人的项目里可能有几十个 so ,如果一个一个那场景可太"有爱"了。

后面的脚本提供查找思路。

首先第一种方法用官方的脚本,保存下方脚本为 shell.sh ,然后执行 ./shell.sh src/main/jinLibs ,就可以检测到目录下所有动态库是否支持 16K:

#!/bin/bash# usage: alignment.sh path to search for *.so filesdir="$1"RED="\e[31m"
GREEN="\e[32m"
ENDCOLOR="\e[0m"matches="$(find $dir -name "*.so" -type f)"
IFS=$'\n'
for match in $matches; dores="$(objdump -p ${match} | grep LOAD | awk '{ print $NF }' | head -1)"if [[ $res =~ "2**14" ]] || [[ $res =~ "2**16" ]]; thenecho -e "${match}: ${GREEN}ALIGNED${ENDCOLOR} ($res)"elseecho -e "${match}: ${RED}UNALIGNED${ENDCOLOR} ($res)"fi
done

整个 Apk 的话,可以直接解压 Apk ,然后对动态库的目录用脚本扫描。

第二种方法就是通过 Google Play 的 app bundle 资源管理器页面直接查看,如果有问题,会看到类似的情况:

另外,如果 so 没问题,但是还是提示你不支持 16 KB,那么很可能是你需要升级 AGP ,建议至少升级到 AGP 8+ ,最优是升级到 8.5.1 之后:

没有问题的情况下是这样:

第三种方法就是下载最新的 libchecker ,如果动态库都有“16 KB” ,那就是正常:

还有一种方法就是使用 readelf 工具,通过终端对比 so 的 elf 对齐情况,工具一般位于 /Users/guoshuyu/Library/Android/sdk/ndk/21.4.7075529/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin,通过以下命令可以输出对应参数:

./aarch64-linux-android-readelf -l /Users/guoshuyu/workspace/android/******/libs/arm64-v8a/libijkffmpeg.so

如下两种图所示:

  • 图 1 LOAD 段在 Align 栏目显示 1000 (16进制,即 4096) ,也就是还没增加 16K 对齐的状态
  • 图 2 LOAD 段在 Align 栏目显示 4000 (16进制,即 16384) ,是增加 16K 对齐的状态

注意,是 1000 才是 4K,而 10000 是 65536 ,那就是64K 对齐,属于 16K 的 4倍,那「理论上」应该是对齐的

最后,你还可以在 Android Studio 里运行你的 App,然后 Android Studio 会提示存在哪些动态库没适配 16 KB

注意,目前需要 Android Studio Narwhal ,最新版 Canary 10,并且有个 Bug ,需要首次运行之后,关闭 Android Studio ,然后再次打开,再运行,才会弹出提示

最后,如果你发现存在动态库不适配,但是你又不知道这个动态库是哪个 aar 远程依赖的,可以通过下方脚本执行 ./gradlew findSoFileOrigins 来输出:

// 在你的模块级别 build.gradle 文件中添加此任务
// 例如: app/build.gradletask findSoFileOrigins {description = "扫描项目依赖的 AAR 文件,找出 .so 文件的来源。"group = "reporting" // 将任务归类到 "reporting" 组下doLast {// 用于存储 AAR 标识符及其包含的 .so 文件路径// 键 (Key): AAR 的字符串标识符 (例如:"project :gsyVideoPlayer", "com.example.library:core:1.0.0")// 值 (Value): 一个 Set 集合,包含该 AAR 内所有 .so 文件的路径 (字符串)def aarSoFilesMap = [:]def variants = nullif (project.plugins.hasPlugin('com.android.application')) {variants = project.android.applicationVariants} else if (project.plugins.hasPlugin('com.android.library')) {variants = project.android.libraryVariants} else {project.logger.warn("警告: findSoFileOrigins 任务需要 Android 应用插件 (com.android.application) 或库插件 (com.android.library)。")return}if (variants == null || variants.isEmpty()) {project.logger.warn("警告: 未找到任何变体 (variants) 来处理。")return}variants.all { variant ->project.logger.lifecycle("正在扫描变体 '${variant.name}' 中的 AAR 依赖以查找 .so 文件...")// 获取该变体的运行时配置 (runtime configuration)def configuration = variant.getRuntimeConfiguration()try {// 配置一个构件视图 (artifact view) 来精确请求 AAR 类型的构件def resolvedArtifactsView = configuration.incoming.artifactView { view ->view.attributes { attributes ->// 明确指定我们只对 artifactType 为 'aar' 的构件感兴趣// AGP 也常用 "android-aar",如果 "aar" 效果不佳,可以尝试替换attributes.attribute(Attribute.of("artifactType", String.class), "aar")}// lenient(false) 是默认行为。如果设为 true,它会尝试跳过无法解析的构件而不是让整个视图失败。// 但如果像之前那样,是组件级别的变体选择失败 (如 gsyVideoPlayer),lenient 可能也无法解决。// view.lenient(false)}.artifacts // 获取 ResolvedArtifactSetproject.logger.info("对于变体 '${variant.name}',从配置 '${configuration.name}' 解析到 ${resolvedArtifactsView.artifacts.size()} 个 AAR 类型的构件。")resolvedArtifactsView.each { resolvedArtifactResult ->// resolvedArtifactResult 是 ResolvedArtifactResult 类型的对象File aarFile = resolvedArtifactResult.file// 获取组件的标识符,这能告诉我们依赖的来源// 例如:"project :gsyVideoPlayer" 或 "com.google.android.material:material:1.7.0"String aarIdentifier = resolvedArtifactResult.id.componentIdentifier.displayNameaarSoFilesMap.putIfAbsent(aarIdentifier, new HashSet<String>())if (aarFile.exists() && aarFile.name.endsWith('.aar')) {// project.logger.info("正在检查 AAR: ${aarIdentifier} (文件: ${aarFile.name})")try {project.zipTree(aarFile).matching {include '**/*.so' // 匹配 AAR 中的所有 .so 文件}.each { File soFileInZip ->aarSoFilesMap[aarIdentifier].add(soFileInZip.path)}} catch (Exception e) {project.logger.error("错误: 无法检查 AAR 文件 '${aarIdentifier}' (路径: ${aarFile.absolutePath})。原因: ${e.message}")}} else {if (!aarFile.name.endsWith('.aar')) {project.logger.debug("跳过非 AAR 文件 '${aarFile.name}' (来自: ${aarIdentifier}),其构件类型被解析为 AAR。")} else {project.logger.warn("警告: 来自 '${aarIdentifier}' 的 AAR 文件不存在: ${aarFile.absolutePath}")}}}} catch (Exception e) {// 这个 catch 块会捕获解析构件视图时发生的错误// 这可能仍然包括之前遇到的 "Could not resolve all artifacts for configuration" 错误,// 如果问题非常根本,即使是特定的构件视图也无法克服。project.logger.error("错误: 无法为配置 '${configuration.name}' 解析 AAR 类型的构件。" +"可能项目设置中存在依赖变体匹配问题," +"详细信息: ${e.message}", e) // 打印异常堆栈以获取更多信息project.logger.error("建议: 请检查项目依赖(尤其是本地子项目如 ':xxxxx')的构建配置," +"确保它们能正确地发布带有标准 Android 库属性(如组件类别、构建类型,以及适用的 Kotlin 平台类型等)的变体。")// 如果希望任务在此处停止而不是尝试其他变体,可以取消下一行的注释// throw e}}// 打印结果if (aarSoFilesMap.isEmpty()) {project.logger.lifecycle("\n在所有已处理变体的可解析 AAR 依赖中均未找到 .so 文件,或者依赖解析失败。")} else {println "\n--- AAR 依赖中的 .so 文件来源 ---"// 按 AAR 标识符排序以获得一致的输出aarSoFilesMap.sort { it.key }.each { aarId, soFileList ->if (!soFileList.isEmpty()) {println "${aarId}:" // 例如:project :gsyVideoPlayer: 或 com.some.library:core:1.0:soFileList.sort().each { soPath -> // 对 .so 文件路径排序println "  - ${soPath}"     // 例如:  - jni/armeabi-v7a/libexample.so}}}println "----------------------------------"}project.logger.lifecycle("任务执行完毕。要再次运行此任务,请执行: ./gradlew ${project.name}:${name}")}
}

这个脚本我是在 APG 8+ 下测试,不同 AGP 可能存在细微 API 差异,思路上一样。

当然,最终还是要在 16 KB 环境运行没有崩溃才行, 在之前的文章我就分享过,很多 so 查看时虽然分页是 16K 或者 64K ,但是它还是有问题的,跑在 16K 上是会崩溃的,具体原因有 NDK 工具可能过老之类:

当时是 NDK10e 等版本,编译出来的 so 都是两个 LOAD 段的 Align 是 10000(65536) , 也就是 64K 对齐,属于 16K 的 4倍,那「理论上」应该是对齐的,但是跑在 16K 上会 crash ,不过 crash 提示也不是 so 不对齐,而是在某段代码执行时出现 crash,并且你定位到的地址代码会很奇葩。

测试环境可以使用模拟器,一般适配 16 KB 的就是 arm64 ,所以 x86_64 模拟器基本没用,而且需要 Android studio Koala Feature Drop 之后的版本才行:

如果你的 Apk 没适配 16 KB ,那么在 Android 16 的 16 KB 模拟器上会看到这样的提示:

目前在 React Native 和 Flutter 都已经支持了 16 KB:

  • Flutter 至少 3.22 版本
  • RN 至少 0.77 版本

比如你的 RN 版本太老,就是看到类似下面的场景:

最后,如果你还没适配或者了解 16 KB,可以参考一下文章

  • Android 15 上适配 16K Page Size 的填坑思路,以 IJKPlayer 为例子

  • Android 15 之如何快速适配 16K Page Size

  • Android 15 适配之16K Page Size :为什么它会是最坑的一个适配点

相关文章:

  • SSTI记录
  • idea查看pom文件依赖
  • 谈AI/OT 的融合
  • upload-labs通关笔记-第2关 文件上传之MIME绕过
  • Spring急速入门
  • YOLOv5推理代码解析
  • KUKA机器人安装包选项KUKA.PLC mxAutomation软件
  • Shell脚本编程3(函数+正则表达式)
  • 3d模型的添加与设置
  • 西电 | 2025年拟录取研究生个人档案录取通知书邮寄通知
  • 猫咪如厕检测与分类识别系统系列~进阶【三】网页端算法启动架构及数据库实现
  • 大语言模型训练的两个阶段
  • 阿里云人工智能大模型通义千问Qwen3开发部署
  • 在IDEA中导入gitee项目
  • Vue学习百日计划-Gemini版
  • C++匿名函数
  • 【爬虫】12306查票
  • 笔记本电脑升级实战手册[3]:扩展内存与硬盘
  • 案例数据清洗
  • 智能网联汽车“内外协同、虚实共生”的通信生态
  • 习近平出席中国-拉美和加勒比国家共同体论坛第四届部长级会议开幕式
  • 迪奥部分客户数据遭泄露,公司称正持续展开调查
  • 北京“准80后”干部兰天跨省份调任新疆生态环境厅副厅长
  • 印度军方否认S-400防空系统被摧毁
  • 金地集团:今年前4个月实现销售额109.3亿元,同比下降52.44%
  • 98年服装“厂二代”:关税压力下,我仍相信中国供应链|湃客Talk