在c++ jni中调用java实例方法需先获取jobject,再用getmethodid获取方法id,最后用callobjectmethod等调用;三者任一出错将崩溃或返回垃圾值。

如何在 C++ JNI 中调用 Java 实例方法
必须先拿到 jobject,再通过 GetMethodID 获取方法 ID,最后用 CallObjectMethod(或对应类型的方法)调用。没对象、没方法 ID、类型不匹配,三者任一出错都会崩溃或返回垃圾值。
- 常见错误现象:
java.lang.NoSuchMethodError多半是签名写错,比如把Ljava/lang/String;写成Ljava/lang/string;(大小写敏感) - 签名必须严格匹配:用
javap -s查真实签名,别手写。例如void foo(String s, int x)对应(Ljava/lang/String;I)V - 如果 Java 方法是
private或static,要用GetStaticMethodID+CallStaticXXXMethod,混用会返回NULL -
jobject必须是有效的本地引用(local ref),不能是已DeleteLocalRef过的,也不能跨线程复用
如何安全获取 Java 类和方法 ID
类名用斜杠分隔(java/lang/String),不是点号;方法 ID 不能缓存到全局变量里直接用,除非你确保类加载器没变、且做了同步。
- 推荐写法:
FindClass后立即NewGlobalRef持有类引用,再GetMethodID;否则类可能被卸载,后续调用 crash - Android 上
FindClass在非主线程调用前,必须先AttachCurrentThread,否则返回NULL - 方法 ID 是运行时解析的,只要类没重定义(如热修复场景),可缓存为静态局部变量,但首次获取仍需判空
- 别用
GetObjectClass反推类名——它返回的是运行时实际类,可能不是你期望的父类,容易导致GetMethodID找不到方法
基本类型与对象参数怎么传进 Java 方法
JNI 不自动转换 C++ 类型和 Java 对象,所有参数都得手动包装。基本类型直传,对象必须是 jobject 或其子类型(如 jstring、jintArray)。
-
jstring要用NewStringUTF(仅支持修改过的 UTF-8)或NewString(UCS-2),别直接传 C 字符串指针 - 传
byte[]用NewByteArray+SetByteArrayRegion;传String[]得循环新建每个jstring,再用NewObjectArray - Java 方法参数是泛型(如
List<string></string>)?JNI 层无法识别泛型,只认原始类型java/util/List,你得确保传进去的是正确实现类(如ArrayList)的实例 - 数组长度超过 65535 时,某些旧版 Android ART 会拒绝创建,建议分批或改用其他序列化方式
为什么 CallVoidMethod 后程序偶尔卡住或崩溃
最常踩的坑是未检查异常。JNI 调用 Java 方法时若抛出异常,JVM 不会自动终止,但后续任何 JNI 调用(包括 ExceptionCheck 以外的)都可能失败或静默失效。
- 每次
CallXXXMethod后,务必用ExceptionCheck判断是否出异常;真出了,得立刻ExceptionDescribe打印日志,并ExceptionClear,否则下一次 JNI 调用大概率失败 - 不要在 Java 方法里 throw unchecked exception 然后指望 C++ 捕获——JNI 层没有 try/catch 机制,只能靠
ExceptionCheck - 回调 Java 方法时,如果 Java 层执行耗时操作(如网络请求),C++ 线程会同步阻塞,Android 上还可能触发 ANR;需要异步处理时,应在 Java 层启动新线程或用
Handler - 释放资源要配对:
NewGlobalRef对应DeleteGlobalRef,NewLocalRef对应DeleteLocalRef,漏掉会导致内存泄漏或随机 crash
JNI 的边界很薄,类型、生命周期、线程、异常,四个维度只要一个没对齐,问题就藏得深、复现难。别信“差不多”,每个 jobject 都得问自己:它哪来的?谁负责删?能不能跨线程?有没有可能为空?
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南







