Android 16KB Page Size 适配实战流程:以重编译FFmpeg so库为例

Android 16KB Page Size 适配实战流程:以重编译FFmpeg so库为例

前言

为了优化了系统内存性能,谷歌计划将 Android 平台架构向 16KB Page Size(16384 字节内存页大小)迁移,这会导致许多仅支持4 KB 内存页面大小的依赖 C/C++ 原生库(.so 文件)的应用在 16KB AVD 或设备上遭遇加载崩溃。根据谷歌最新官方消息,自 2025 年 11 月 1 日起,提交到 Google Play 且以 Android 15 及更高版本为目标平台的所有新应用和现有应用更新都必须在 64 位设备上支持 16KB Page Size。 依赖 C/C++ 原生库(.so 文件)的应用没办法躲过适配16KB Page Size的工作了。

我手上的项目AudioAndVideoEditorAeroFFmpeg均依赖FFmpeg so库且未适配16KB Page Size,适配16KB Page Size对于我而言也是一个很大的挑战。我本人也不是很懂 C/C++ 的编译底层原理,通过抄谷歌官方指导教程和大佬博客、问ai一路适配,最终应该是成功适配的了。

本文以重编译FFmpeg so库为例子讲诉适配流程:从判断是否支持16KB Page Size,到交叉编译so库,到重新编译构建整个项目,最终确保应用在最新的 16KB Android 环境中稳定运行。

适配难点

虽然 16KB 适配对平台理论上有显著益处,但其大规模落地面临很大挑战。纯Java/Kolin项目理论上可以直接支持16KB Page Size设备,关键在于庞大的C/C++ 原生库生态。

1.源代码缺失或不可修改: 许多应用使用了多年前编译的、已停止维护的第三方 SDK 或原生库。如果无法获取源代码或修改其构建脚本,则根本无法重新编译并注入 16KB 链接器标志。

2.多层依赖嵌套: 复杂应用可能依赖的 A 库又依赖 B 库,而 B 库又依赖 C 库。只要依赖链中有一个库未适配,整个应用在 16KB 环境中就可能崩溃。

总之,没有 C/C++ 原生库就没有烦恼。

不同项目使用的 C/C++ 编译工具链和构建系统 不一样,涉及的原生库不一样,本文以AudioAndVideoEditorAeroFFmpeg为例子介绍C/C++ 原生库适配16KB Page Size的流程。AudioAndVideoEditorAeroFFmpeg依赖的x264、x265、LAME、FFmpeg都是知名项目且代码开源,因此可以自己重新编译源码适配16KB Page Size,这一点是比较幸运的。我是先在Linux环境使用NDK r20交叉编译x264、x265、LAME、FFmpeg得到对应so库,再集成到Android项目里面使用CMake+NDK r25编译构建得到最终的Android应用和SDK库。重新编译so库的过程还是比较琐碎且耗时的。

判断你的应用是否满足16KB Page Size的要求

16KB 适配最好安装新的Android Studio以使用谷歌提供的最新工具。虽然升级Android Studio可能会给老项目又带来一些问题,但是长痛不如短痛,升级Android Studio尽量跟上谷歌的步伐是最好的。

  1. 使用Android Studio中的Analyze APK工具
    在Android Studio打开项目,在最顶上菜单栏中,依次点击 Build > Analyze APK...
    如果你的 C/C++ 原生库已经适配 16KB ,结果如下:\
  1. 16 KB 虚拟机设备直接运行测试
    arm64-v8a或x86_64的64位设备才有16KB Page Size的问题,我们可以在Android Studio的16KB 虚拟机上测试应用是否能正常运行。注意如果你是在AMD的Windows上开发,那你应该选择x86_64的16KB Page Size虚拟机,如果是ARM架构的Mac应该选择arm64-v8a的16KB Page Size虚拟机。
    默认情况下,16KB Page Size虚拟机会使用16 KB 向后兼容模式,也就是说你的应用不支持16KB Page Size也不会一下崩掉,而是会弹出提示。\
复制代码
如果你想看看你的应用崩溃的样子,可以在应用设置里面选择关掉16 KB 向后兼容模式,报错信息如下。

适配16KB Page Size

关键的链接器标志

为Android编译 C/C++ 原生库依靠NDK,不同版本NDK对16KB Page Size的支持不一样。NDK 版本 r28 及更高版本默认编译为 16 KB 对齐。Android NDK r27及以下版本需要设置相关配置文件、编译脚本文件的参数,主要是链接器标志参数。谷歌官方推荐始终更新 NDK,但实际上对于旧项目而言更新NDK往往会带来一系列麻烦的问题(往往不是花一天两天能解决的),因此我依靠不升级NDK的适配方法。

适配 16KB 的关键在于,在原生库的链接阶段,告诉链接器(ld)将所有 ELF 程序段的对齐值设置为 16KB。我们可以使用以下链接器标志:

ini 复制代码
-Wl,-z,max-page-size=16384
-Wl,-z,common-page-size=16384

max-page-size是适配16KB Page Size的关键,它强制链接器将 ELF 文件中所有可加载的程序段(Program Segments,例如包含代码的 .text 段或包含数据的 .data 段)的对齐要求 p_align 设置为 16384 字节(16KB)。Android NDK r27~r23理论上用它就行。

common-page-size用于设置链接器在进行内部优化和确定文件布局时所使用的通用页大小。它影响链接器如何组织代码和数据,以优化空间利用和内存映射。它可以解决适配16KB Page Size过程中(对于Android NDK r22 及更低版本)旧版 GNU ld 和 LLVM lld 链接器中的 bug。

出于稳妥起见,我两个链接器标志都使用。

例如在CMakeLists.txt这样使用:

bash 复制代码
target_link_options(${CMAKE_PROJECT_NAME} PRIVATE "-Wl,-z,max-page-size=16384")
target_link_options(${CMAKE_PROJECT_NAME} PRIVATE "-Wl,-z,common-page-size=16384")     

重新交叉编译so库

编译环境:VMware Workstation Pro中的ubuntu虚拟机

NDK:NDK r20

使用NDK交叉编译so库的时候在编译脚本加入上面两个链接器标志参数。

编译x264脚本关键部分如下:

bash 复制代码
    # ...
    export LDFLAGS="-Wl,-z,max-page-size=16384 -Wl,-z,common-page-size=16384"
	
	function build
	{
	echo ">>>>>>>> build start <<<<<<<<<<"
	
	  ./configure \
	  --prefix=$TARGET \
	  --enable-static \
	  --enable-shared \
	  --enable-pic \
	  --host=aarch64-linux-android \
	  --disable-asm \
	  --disable-opencl \
	  --disable-cli \
	  --cross-prefix=$TOOLCHAIN/bin/aarch64-linux-android- \
	  --sysroot=$TOOLCHAIN/sysroot \
	
	make clean
	make
	make install
	
	echo ">>>>>> build done <<<<<<"
	}
	
	build

编译LAME脚本关键部分如下:

bash 复制代码
    # ...
LDFLAGS="-Wl,-z,max-page-size=16384 -Wl,-z,common-page-size=16384"
API=24

function build_android_arm
{
echo "build for android $CPU"
./configure \
--host=$HOST \
--disable-static \
--enable-shared \
--disable-frontend \
--prefix="$TARGET" \
LDFLAGS="${LDFLAGS}" \
CPPFLAGS="-fPIC"

make clean
make -j8
make install

echo "build for android $CPU completed"
}

最终编译FFmpeg关键部分如下:

bash 复制代码
DYNAMIC_LINKER_FLAGS="-Wl,-z,max-page-size=16384 -Wl,-z,common-page-size=16384"
# ...
function build
{
  ./configure \
  --pkg-config=pkg-config \
  --pkg-config-flags="--static" \
  --prefix=$OUTPUT \
  --target-os=android \
  --arch=arm64 \
  --cpu=armv8-a \
  --disable-x86asm \
  --disable-asm \
  --enable-neon \
  --enable-cross-compile \
  --enable-shared \
  --disable-static \
  --disable-doc \
  --disable-ffplay \
  --disable-ffprobe \
  --disable-symver \
  --disable-ffmpeg \
  --enable-pic \
  --sysroot=$SYSROOT \
  --cross-prefix=$TOOLCHAIN/bin/aarch64-linux-android- \
  --cross-prefix-clang=$TOOLCHAIN/bin/aarch64-linux-android$API- \
  --enable-encoder=aac \
  --enable-decoder=aac \
  --enable-gpl \
  --enable-encoder=libx264 \
  --enable-libx264 \
  --enable-encoder=libx265 \
  --enable-libx265 \
  --enable-libmp3lame \
  --extra-cflags="-I$X264_DIR/include -I$LAME_DIR/include -I$X265_DIR/include" \
  --extra-ldflags="$DYNAMIC_LINKER_FLAGS -L$X264_DIR/lib -L$LAME_DIR/lib -L$X265_DIR/lib" \
  --extra-cflags="-fPIC"

  make clean all
  # 这里是定义用几个CPU编译
  make -j12
  make install
}

可以看见都加上了max-page-size和common-page-size两个链接器标志,这样设置最终得到的so库是符合16KB Page Size要求的。

修改CMakeLists.txt重新构建项目

将新鲜出炉的16KB so库放到Android项目的对应目录下。

注意在Android项目里面的CMakeLists.txt也需要添加max-page-size和common-page-size两个链接器标志。

ini 复制代码
target_link_libraries(

        # 指定目标库,native-lib 是在上面 add_library 中配置的目标库
        aeroffmpeglib

        # 4. 连接 FFmpeg 相关的库
        avutil
        swresample
        avcodec
        avfilter
        swscale
        avformat
        avdevice
        h264
        h265
        mp3lame
        ffmpeg

        -landroid
        OpenSLES
        -lEGL
        -lGLESv2
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib}
        -Wl,-z,max-page-size=16384
        -Wl,-z,common-page-size=16384
)

修改CMakeLists.txt后重新同步配置信息并构建整个项目。

总结

AudioAndVideoEditorAeroFFmpeg现在应该都支持Android 16KB Page Size。

我希望谷歌以后少做涉及 C/C++ 原生库的更新了。

参考

谷歌支持 16 KB 的页面大小指南
Android 15 上适配 16K Page Size 的填坑思路,以 IJKPlayer 为例子

相关推荐
阿巴斯甜17 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker18 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952719 小时前
Andorid Google 登录接入文档
android
黄林晴20 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android