You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Native Android:不依赖NativeActivity获取AAssetManager对象的实现方法

Native Android:不依赖NativeActivity获取AAssetManager对象的实现方法

嘿,我太懂你这种需求了——想搞一个独立的原生Android库,能直接读取assets或者res/raw里的 shader、纹理文件,还不想被某个特定的Activity绑死,毕竟每次都要把Activity对象传到JNI里,不仅麻烦,还限制了库的灵活性对吧?

其实核心思路很简单:咱们可以借助Application全局上下文来获取AAssetManager。因为Application是整个App生命周期内唯一的全局实例,它的存在时间和App完全同步,用它来获取AssetManager,就能彻底摆脱对单个Activity的依赖。

下面一步步给你说具体怎么实现:

第一步:Java层准备全局入口

首先咱们得自定义一个Application类,把它作为App的全局入口,在这里初始化AssetManager并传给JNI层:

public class MyApp extends Application {
    // 保存全局的Application实例
    private static MyApp sInstance;
    // 保存传给JNI的全局AssetManager引用(用来后续释放)
    private static jobject sGlobalAssetManager;

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
        // 调用JNI方法,把全局AssetManager传过去初始化
        initNativeAssetManager(getAssets());
    }

    public static MyApp getInstance() {
        return sInstance;
    }

    // 声明JNI方法,用来初始化原生层的AssetManager
    private native void initNativeAssetManager(AssetManager assetManager);

    // 当App退出时,释放JNI层的全局引用
    @Override
    public void onTerminate() {
        super.onTerminate();
        releaseNativeAssetResources();
    }

    private native void releaseNativeAssetResources();
}

别忘啦,要在AndroidManifest.xml里注册这个自定义的Application,不然系统会默认用原生的Application类:

<application
    android:name=".MyApp"
    <!-- 其他配置项不变 -->
    ...>
</application>

第二步:JNI层保存全局AAssetManager

在JNI层,我们需要把Java层传过来的AssetManager转换成原生的AAssetManager指针,并且保存全局引用,避免Java层对象被GC回收导致指针失效:

#include <jni.h>
#include <android/asset_manager_jni.h>
#include <android/log.h>

#define LOG_TAG "NativeAssetLib"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

// 全局的AAssetManager指针,后续所有文件操作都用它
static AAssetManager* g_assetManager = nullptr;
// 保存Java层AssetManager的全局引用,用来后续释放
static jobject g_globalAssetManager = nullptr;

extern "C" JNIEXPORT void JNICALL
Java_com_example_mylibrary_MyApp_initNativeAssetManager(JNIEnv* env, jobject thiz, jobject assetManager) {
    // 避免重复初始化
    if (g_assetManager == nullptr && assetManager != nullptr) {
        // 创建全局引用,防止Java层的AssetManager被GC回收
        g_globalAssetManager = env->NewGlobalRef(assetManager);
        // 转换成原生的AAssetManager指针
        g_assetManager = AAssetManager_fromJava(env, g_globalAssetManager);
        LOGD("原生AssetManager初始化成功");
    }
}

extern "C" JNIEXPORT void JNICALL
Java_com_example_mylibrary_MyApp_releaseNativeAssetResources(JNIEnv* env, jobject thiz) {
    // 释放全局引用和指针
    if (g_globalAssetManager != nullptr) {
        env->DeleteGlobalRef(g_globalAssetManager);
        g_globalAssetManager = nullptr;
    }
    if (g_assetManager != nullptr) {
        g_assetManager = nullptr;
        LOGD("原生AssetManager资源已释放");
    }
}

// 示例:读取assets目录下的shader文件
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_example_mylibrary_NativeUtils_readShaderFile(JNIEnv* env, jobject thiz, jstring filePath) {
    if (g_assetManager == nullptr) {
        LOGD("错误:原生AssetManager未初始化");
        return nullptr;
    }

    const char* cFilePath = env->GetStringUTFChars(filePath, nullptr);
    // 打开assets里的文件,AASSET_MODE_BUFFER表示把文件加载到内存
    AAsset* asset = AAssetManager_open(g_assetManager, cFilePath, AASSET_MODE_BUFFER);
    if (asset == nullptr) {
        LOGD("错误:无法打开文件 %s", cFilePath);
        env->ReleaseStringUTFChars(filePath, cFilePath);
        return nullptr;
    }

    // 获取文件大小并读取内容
    off_t fileSize = AAsset_getLength(asset);
    jbyteArray result = env->NewByteArray(fileSize);
    env->SetByteArrayRegion(result, 0, fileSize, (jbyte*)AAsset_getBuffer(asset));

    // 释放资源
    AAsset_close(asset);
    env->ReleaseStringUTFChars(filePath, cFilePath);
    return result;
}

几个关键注意事项

  • 全局引用的重要性:一定要用NewGlobalRef创建Java层AssetManager的全局引用,不能用局部引用。因为JNI方法执行完后,局部引用会被自动回收,要是直接用局部引用转换的AAssetManager指针,后续访问就会直接崩溃。
  • 线程安全:如果你的库是多线程使用的,记得给g_assetManager的访问加锁(比如用pthread_mutex_t),避免多线程同时初始化或者访问导致的问题。
  • res/raw vs assets的访问路径:
    • 放在assets文件夹下的文件,直接用相对路径(比如shaders/vertex.glsl)就能打开;
    • 放在res/raw下的文件,需要用raw/文件名作为路径(比如raw/texture.png)。
  • 资源释放:虽然App退出时系统会回收资源,但手动释放全局引用是个好习惯,能避免潜在的内存泄漏。

这样实现之后,你的原生库就可以在任何地方自由访问assets或res/raw里的文件了,完全不用依赖某个特定的Activity对象,真正做到了库的解耦和独立~

备注:内容来源于stack exchange,提问作者Zack

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 16:29:29