背景
网络上大部分文章都是JNI的编写, 但是一些情况下在so里面,也有Native调用回Java的场景,文章相对较少。而且如果照着一些文章写的,可能会出现写的c的每一行代码都执行了,但是Java层就是没执行等奇奇怪怪的问题。做项目时也遇到过这些坑,特总结如下。
部分代码
//TestJNI.kt
class TestJNI{
init{
System.loadLibrary("test")
initTest()
}
//防止混淆
@Keep
fun nativeInvokeAction(action: Int, contentValue: Int) {
when (action) {
1->{}
2->{}
}
}
private external fun initTest(): Int
}
//test.h
struct object_service{
jobject serviceObject;
jmethodID invokeAction;
};
//test.c
JavaVM *jvm;
void service_device_invoke_action(unsigned int action, unsigned long contentValue) {
JNIEnv *env;
//判断c层的这个线程是否在JVM环境有对应线程。
bool detached = (*jvm)->GetEnv(jvm, (void **) &env, JNI_VERSION_1_6) == JNI_EDETACHED;
//如果没有,则会依附到JVM,其实就是在JVM生成一个对应线程,用于执行方法
if(detached) (*jvm)->AttachCurrentThread(jvm, &env, NULL);
//调用JVM层方法无返回值是CallVoidMethod,有返回值就是比如CallObjectMethod这些
(*env)->CallVoidMethod(env, testService.serviceObject,
testService.invokeAction, action, (int)contentValue);
//用完后,放弃使用JVM层的线程,便于JVM回收
if(detached) (*jvm)->DetachCurrentThread(jvm);
}
struct object_service testService;
JNIEXPORT jint JNICALL
Java_com_test_testjni_TestJNI_initTest(JNIEnv *env, jobject thiz) {
testService.serviceObject = (*env)->NewGlobalRef(env, thiz);
jclass testClass = (*env)->FindClass(env, "com/test/testjni/TestJNI");
testService.invokeAction = (*env)->GetMethodID(env, testClass, "nativeInvokeAction",
"(II)V");
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){
jvm = vm;
return JNI_VERSION_1_6;
}
个人理解
Native 回调 Java需要这些东西:jvm, 包含需要执行的方法的object, 方法,参数,还有执行线程。
所以在JVM调用so初始化的时候,先通过JNI_OnLoad拿到jvm。
初始化方法中,通过NewGlobalRef(env, thiz)拿到了对象,通过FindClass和GetMethodID拿到方法。
在service_device_invoke_action方法里面,通过jni提供的GetEnv AttachCurrentThread拿到JVM里的执行线程。
遇到的一些问题
- kt中nativeInvokeAction方法被混淆,导致异常,所以添加了 @Keep 或者proguard-rules.pro等
- c的代码都执行了,但是没有回调成功,没有反应。原因是线程问题,可查看service_device_invoke_action注释。因为Native层可能会自己新建线程等,这时新建出来的线程在JVM是没有对应的。
- 回调的nativeInvokeAction假如有返回值应该怎么做,比如返回String。这时候CallVoidMethod就需要用CallObjectMethod。以此类推。