多次调用CallVoidMethod致React Native JSI-JNI崩溃求助
问题分析与修复方案
核心崩溃原因
- JNIEnv跨线程非法访问:你在
nativeInstall中捕获的JNIEnv*属于调用该方法的线程(通常是主线程),但JSI回调是在JS引擎线程(mqt_v_js)执行的。JNIEnv是线程绑定的,跨线程使用会直接触发内存访问错误(SIGSEGV)。 - 未缓存全局引用的jclass:每次回调都调用
FindClass,不仅效率低下,还可能因局部引用积累引发隐性内存问题。 - JSI函数参数声明错误:原代码中
createFromHostFunction的参数个数写为0,但实际需要接收1个参数,会导致参数校验逻辑失效。
修复步骤
1. 保存JavaVM指针而非JNIEnv
JNI允许通过JavaVM在任意线程获取当前线程的JNIEnv,修改nativeInstall方法:
extern "C" JNIEXPORT void JNICALL Java_com_mymodule_rtnstreamstream_RTNStreamModule_nativeInstall(JNIEnv *env, jobject thiz, jlong jsi) { jsi::Runtime * runtime = reinterpret_cast<jsi::Runtime *>(jsi); // 获取全局有效的JavaVM指针 JavaVM* vm = nullptr; env->GetJavaVM(&vm); // 创建模块实例的全局引用 jobject rtnstream_kt_module = env->NewGlobalRef(thiz); // 传递JavaVM而非JNIEnv到install方法 rtnstream::install(*runtime, vm, rtnstream_kt_module); }
2. 缓存jclass全局引用
在install函数中提前获取并缓存模块类的全局引用,避免每次回调重复查找:
// 全局变量,用于缓存模块类的全局引用 static jclass s_rtn_stream_class = nullptr; void install(jsi::Runtime& runtime, JavaVM* vm, jobject module_ref) { JNIEnv* env = nullptr; vm->AttachCurrentThread(&env, nullptr); // 将局部类引用转为全局引用缓存 jclass local_class = env->FindClass("com/myModule/RTNStreamModule"); s_rtn_stream_class = reinterpret_cast<jclass>(env->NewGlobalRef(local_class)); env->DeleteLocalRef(local_class); // 创建JSI函数,修正参数个数为1 jsi::Function receiveDataFunc = jsi::Function::createFromHostFunction( runtime, jsi::PropNameID::forAscii(runtime, "receiveDataFromCpp"), 1, [vm, module_ref]( jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, std::size_t count ) -> jsi::Value { if (count != 1 || !arguments[0].isObject()) { throw jsi::JSError(runtime, "Argument has to be an ArrayBuffer"); } jsi::Object object = arguments[0].asObject(runtime); jsi::ArrayBuffer arrayBuffer = object.getArrayBuffer(runtime); size_t bufferSize = arrayBuffer.size(runtime); uint8_t* bufferData = arrayBuffer.data(runtime); // 获取当前线程的JNIEnv JNIEnv* currentEnv = nullptr; jint result = vm->AttachCurrentThread(¤tEnv, nullptr); if (result != JNI_OK) { throw jsi::JSError(runtime, "Failed to attach JNI thread"); } jbyteArray jbyteArrayData = currentEnv->NewByteArray(bufferSize); if (jbyteArrayData == nullptr) { throw jsi::JSError(runtime, "Failed to create byte array"); } currentEnv->SetByteArrayRegion(jbyteArrayData, 0, bufferSize, reinterpret_cast<const jbyte*>(bufferData)); // 使用缓存的全局类引用获取方法ID jmethodID receiveDataMethod = currentEnv->GetMethodID(s_rtn_stream_class, "receiveDataFromCpp", "([B)V"); if (receiveDataMethod != nullptr) { currentEnv->CallVoidMethod(module_ref, receiveDataMethod, jbyteArrayData); } else { throw jsi::JSError(runtime, "Failed to find receiveDataFromCpp method"); } // 清理局部引用 currentEnv->DeleteLocalRef(jbyteArrayData); return jsi::Value(static_cast<double>(bufferSize)); }); // 将函数挂载到JS全局对象 runtime.global().setProperty(runtime, "receiveDataFromCpp", std::move(receiveDataFunc)); }
3. 全局引用清理(避免内存泄漏)
在模块卸载时,释放缓存的全局引用:
// 模块卸载时调用该方法 void uninstall(JavaVM* vm, jobject module_ref) { JNIEnv* env = nullptr; vm->AttachCurrentThread(&env, nullptr); if (s_rtn_stream_class != nullptr) { env->DeleteGlobalRef(s_rtn_stream_class); s_rtn_stream_class = nullptr; } if (module_ref != nullptr) { env->DeleteGlobalRef(module_ref); } }
修复效果验证
修复后重复调用receiveDataFromCpp时,JS线程会使用自身绑定的JNIEnv,不会再出现跨线程访问JNIEnv导致的SIGSEGV错误,同时缓存jclass也提升了回调执行效率。
内容的提问来源于stack exchange,提问作者Stéphane R.
相关产品推荐
相关产品推荐

