背景
在移动端部署 AI 模型时,内存(RAM)通常是极其珍贵的资源。传统的模型加载方式往往需要将模型文件先从磁盘或 Assets 读取到内存缓冲区,再由推理引擎解析。这种方式导致了至少双倍的内存占用。
ncnn 提供的 from_android_asset 加载模式,允许引擎直接通过 Android 的 AAssetManager 访问资源。结合系统级的文件映射机制,可以实现“零拷贝”加载,即模型权重在物理内存中只有一份副本,且由系统按需调页。
实战步骤
1. 准备环境
确保你的 CMakeLists.txt 中链接了 android 库,因为我们需要用到 AAssetManager。
find_library(android-lib android)
target_link_libraries(your_target ncnn ${android-lib})
2. C++ 层核心代码
在 JNI 函数中,我们需要接收从 Java 层传递过来的 AssetManager 对象,并将其转换为 C++ 的指针。
#include <android/asset_manager_jni.h>
#include "net.h"
extern "C" JNIEXPORT void JNICALL
Java_com_example_app_NativeLib_initModel(JNIEnv* env, jobject thiz, jobject asset_manager) {
// 1. 获取 AAssetManager 指针
AAssetManager* mgr = AAssetManager_fromJava(env, asset_manager);
ncnn::Net* net = new ncnn::Net();
// 2. 设置推理选项 (可选)
net->opt.use_vulkan_compute = false; // 示例使用 CPU 推理
// 3. 使用 load_param / load_model 的 AAssetManager 重载版本
// 注意:路径应为 assets 目录下的相对路径,且不需要前缀 "assets/"
int ret_param = net->load_param(mgr, "yolov8n.param");
int ret_model = net->load_model(mgr, "yolov8n.bin");
if (ret_param != 0 || ret_model != 0) {
// 处理加载失败逻辑
}
}
3. Java/Kotlin 层调用
在 Android 代码中获取 AssetManager 并传递给 Native 层:
val assetManager = context.assets
nativeLib.initModel(assetManager)
关键点解析:如何确保真正的“零拷贝”?
默认情况下,Android 的 AAssetManager 对于压缩文件会进行解压,这会产生临时副本。为了实现真正的零拷贝,我们需要在 build.gradle 中确保 .bin 文件不被压缩:
android {
aaptOptions {
noCompress "bin", "param"
}
}
当文件不被压缩时,AAssetManager 会直接返回文件在 APK/AAB 中的偏移量,ncnn 内部利用 mmap 将该区域映射到进程空间,从而实现:
– 内存节省:无需预先申请巨大的 malloc 空间来存放权重。
– 启动加速:省去了文件读取和拷贝的 I/O 耗时。
– 按需加载:只有在推理真正用到某一层权重时,操作系统才会将其加载进物理内存。
总结
利用 ncnn 的 from_android_asset 结合 aaptOptions 设置,是 Android 端优化 AI 模型内存占用的“银弹”。这种方案不仅代码改动量小,而且能显著提升在大模型场景下的应用稳定性。
汤不热吧