使用PJSIP库开发SIP通话时接电话触发JNI崩溃问题求助
解决PJSIP JNI层无效对象导致的接听崩溃问题
我之前在基于PJSIP开发SIP通话功能时,遇到过几乎一模一样的偶发崩溃问题。从你提供的错误日志来看,JNI DETECTED ERROR IN APPLICATION: use of invalid jobject这个报错,核心原因基本是Java对象的生命周期管理混乱,再结合PJSIP天生的多线程架构,很容易在跨线程回调时踩坑。下面是我整理的排查方向和解决方法:
可能的原因及解决方案
1. 局部引用被提前回收,未使用全局引用
PJSIP的回调事件(比如接听请求)是在独立的native线程(日志里的sipStack线程)触发的。如果你在JNI方法里只创建了Java对象的局部引用,当该JNI方法执行完毕返回后,局部引用会被VM自动回收,后续native线程再尝试访问这个对象时,就会出现无效对象错误。
解决方法:
- 在初始化回调相关的Java对象时,使用
NewGlobalRef创建全局引用,将其存储在native层的结构体或全局变量中; - 当确认不再需要该对象时(比如通话结束后),调用
DeleteGlobalRef手动释放全局引用,避免内存泄漏。
示例代码(C++):
// 假设你有一个Java层的SipListener对象需要在native回调中使用 jobject g_sip_listener = nullptr; // 在初始化回调时创建全局引用 JNIEXPORT void JNICALL Java_com_xxx_xxx_SipService_setListener(JNIEnv *env, jobject thiz, jobject listener) { if (g_sip_listener != nullptr) { env->DeleteGlobalRef(g_sip_listener); } // 创建全局引用 g_sip_listener = env->NewGlobalRef(listener); } // 在通话结束或销毁时释放全局引用 JNIEXPORT void JNICALL Java_com_xxx_xxx_SipService_destroy(JNIEnv *env, jobject thiz) { if (g_sip_listener != nullptr) { env->DeleteGlobalRef(g_sip_listener); g_sip_listener = nullptr; } }
2. 跨线程访问未做同步,对象被Java层GC回收
PJSIP的native线程和Java层的线程(比如UI线程)是独立的,如果Java层的对象没有被强引用持有,很可能被GC提前回收,此时native层再访问就会触发无效对象错误。另外,直接在native线程操作Java对象也可能存在线程安全问题。
解决方法:
- 在Java层保持对回调对象(比如SipListener、通话管理器)的强引用,避免GC自动回收;
- 在native回调中,先检查对象是否有效,再执行操作:
JNIEnv* getEnv() { JNIEnv* env; // 确保当前线程关联到VM g_java_vm->AttachCurrentThread(&env, nullptr); return env; } // 在接听回调中检查对象有效性 void onCallAnswer() { JNIEnv* env = getEnv(); if (env->IsSameObject(g_sip_listener, nullptr)) { // 对象已无效,直接返回 g_java_vm->DetachCurrentThread(); return; } // 调用Java层方法处理接听逻辑 jmethodID method = env->GetMethodID(env->GetObjectClass(g_sip_listener), "onCallAnswered", "()V"); env->CallVoidMethod(g_sip_listener, method); g_java_vm->DetachCurrentThread(); }
- 如果需要操作UI相关对象,通过Java层的
Handler或runOnUiThread切换到UI线程执行,避免跨线程操作风险。
3. 对象生命周期不匹配,native层仍在使用时Java层已销毁
比如接听时创建的音频流对象、通话会话对象,在native层还在处理数据时,Java层已经调用了销毁方法释放这些对象,导致native层访问无效引用。
解决方法:
- 梳理通话流程中的对象创建、使用、销毁时机,添加状态标记(比如
isCallActive),确保native层不再使用该对象后,Java层才执行销毁操作; - 可以在native层维护引用计数,当引用计数归0时,再通知Java层回收对象。
总结
这种偶发崩溃的核心是JNI层与Java层的对象生命周期不同步,尤其是多线程场景下的引用管理问题。优先排查全局引用的使用是否正确,再检查对象是否被提前GC回收,最后确认跨线程操作的同步逻辑是否完善。
内容的提问来源于stack exchange,提问作者Jyoti
相关产品推荐
相关产品推荐

