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
相关产品推荐
相关产品推荐

