JNI回调Java

时间:2022-07-22
本文章向大家介绍JNI回调Java,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

jclass、jobject、jmethodID 和 jfieldID

jni回调java是通过反射来实现的,这些反射的接口都定义在 JNIEnv中。

jclass

java类引用 可以通过FindClass来获取

const char* className  = "pri/tool/ffmediaplayer/MediaPlayer";
jclass clazz;
clazz = env->FindClass(className);

另一种获取方法是已经知道对象/实例的引用,通过GetObjectClass来获取

jclass clazz = env->GetObjectClass(thiz);

jobject

实例/对象的引用 两种应用场景: 第一种情况是Java层将实例传递下来。 第二种情况是native层拿到jclass对象后,创建jobject实例,并将实例返回给java层,以如何创建一个java层的ArrayList为例:

    jclass list_class = env->FindClass("java/util/ArrayList");
    if (list_class == NULL) {
        return 0;
    }

    jmethodID list_costruct = env->GetMethodID(list_class , "<init>","()V"); //获得得构造函数Id
    jobject list_obj = env->NewObject(list_class , list_costruct); //创建一个Arraylist集合对象

分3步:1 获取类应用jclass;2 获取构造函数jmethodID; 3 通过NewObject创建对象的引用. <init>是构造函数统一的方法名,()V 为函数签名

jmethodID

方法的id,通过jmethodID 可以操作java类对应的方法。 获取方法id的接口为

jmethodID GetMethodID(JNIEnv *env,jclass clazz,const char*name,const char* sig);

调用非static java方法的函数为

NativeType Call<type>Method(JNIEnv *env,jobject obj,jmethodID methodID,...);

同样以上面ArrayList为例,说明下如何使用

//创建一个Arraylist集合对象
jobject listFrameRate_obj = env->NewObject(list_class , list_costruct); 

//获取ArrayList的add方法
 jmethodID list_add = env->GetMethodID(list_class, "add", "(Ljava/lang/Object;)Z");

 //获取自定义类的jclass和构造方法,该自定义类作为ArrayList模板类成员
 jclass frameRate_cls = env->FindClass("com/iview/camera/module/Data/FrameRate");//获得Student类引用
 jmethodID frameRate_costruct = env->GetMethodID(frameRate_cls , "<init>", "(II)V");

 for (FrameRate frameRate : frame.frameRate) {
        //创建自定义类,后面两个参数为构造函数的方法
        jobject frameRate_obj = env->NewObject(frameRate_cls, frameRate_costruct, frameRate.numerator, frameRate.denominator);
       //调用ArrayList的add方法将 frameRate_obj 添加到ArrayList中
        env->CallBooleanMethod(listFrameRate_obj , list_add , frameRate_obj);
}

jfieldID

java类属性的id 可以通过以下函数来获取

jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

比如MediaPlayer中

 fields.context = env->GetFieldID(clazz, "mNativeContext", "J");

获取到的是java层的

 private long mNativeContext; // accessed by native method

也可以使用对应的Get<type>Field来获取

jobject GetObjectField(jobject obj, jfieldID fieldID)
jboolean GetBooleanField(jobject obj, jfieldID fieldID)
jbyte GetByteField(jobject obj, jfieldID fieldID)
jchar GetCharField(jobject obj, jfieldID fieldID)
jshort GetShortField(jobject obj, jfieldID fieldID)
jint GetIntField(jobject obj, jfieldID fieldID)
jlong GetLongField(jobject obj, jfieldID fieldID)
jfloat GetFloatField(jobject obj, jfieldID fieldID)
jdouble GetDoubleField(jobject obj, jfieldID fieldID)

修改fieldID可以通过对应的Set<type>Field来设置

  void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
  void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value)
  void SetByteField(jobject obj, jfieldID fieldID, jbyte value)
  void SetCharField(jobject obj, jfieldID fieldID, jchar value)
  void SetShortField(jobject obj, jfieldID fieldID, jshort value)
  void SetIntField(jobject obj, jfieldID fieldID, jint value)
  void SetLongField(jobject obj, jfieldID fieldID, jlong value)
  void SetFloatField(jobject obj, jfieldID fieldID, jfloat value)
  void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value)

关于Static和非Static

我们知道类的方法和属性有static和非static之分,对应的jni反射的接口也有差异 常见的获取方法有:

jfieldID GetFieldID(jclass clazz, const char *name, const char *sig);
jmethodID GetMethodID(jclass clazz, const char *name, const char *sig);
jfieldID GetStaticFieldID(jclass clazz, const char *name, const char *sig);
jmethodID GetStaticMethodID( jclass clazz,const char *name, const char *sig);

常见的设置方法

void Set<type>Field(jobject clazz,jfieldID fieldID,NativeType value);
void SetStatic<type>Field(jclass clazz,jfieldID fieldID,NativeType value);
NativeType Call<type>Method(jobject clazz,jmethodID methodID,...);
NativeType CallStatic<type>Method(jclass clazz,jmethodID methodID,...);

设置的函数除了方法名不一样外, static 的参数用了jclass, 非static用的是jobject,jobject必须是已经实例化的引用

常见问题解答:为什么 FindClass 找不到我的类?

确保类名称字符串的格式正确无误。JNI 类名称以软件包名称开头,并用斜线分隔,例如 java/lang/String。如果您要查找某个数组类,则需要以适当数量的英文方括号开头,并且还必须用“L”和“;”将该类括起来,因此 String 的一维数组将是 [Ljava/lang/String;。如果您要查找内部类,请使用“$”而不是“.”。通常,在 .class 文件上使用 javap 是查找类的内部名称的好方法。 如果类名称形式正确,则可能是您遇到了类加载器问题。FindClass 需要在与您的代码关联的类加载器中启动类搜索。它会检查调用堆栈,如下所示:

Foo.myfunc(Native Method)
        Foo.main(Foo.java:10)

最顶层的方法是 Foo.myfunc。FindClass 会查找与 Foo 类关联的 ClassLoader 对象并使用它。

采用这种方法通常会完成您想要执行的操作。如果您自行创建线程(可能通过调用 pthread_create,然后使用 AttachCurrentThread 进行附加),可能会遇到麻烦。现在您的应用中没有堆栈帧。如果从此线程调用 FindClass,JavaVM 会在“系统”类加载器(而不是与应用关联的类加载器)中启动,因此尝试查找特定于应用的类将失败。

您可以通过以下几种方法来解决此问题:

  • 在 JNI_OnLoad 中执行一次 FindClass 查找,然后缓存类引用以供日后使用。在执行 JNI_OnLoad 过程中发出的任何 FindClass 调用都会使用与调用 System.loadLibrary 的函数关联的类加载器(这是一条特殊规则,用于更方便地进行库初始化)。如果您的应用代码要加载库,FindClass 会使用正确的类加载器。
  • 通过声明原生方法来获取 Class 参数,然后传入 Foo.class,从而将类的实例传递给需要它的函数。
  • 在某个便捷位置缓存对 ClassLoader 对象的引用,然后直接发出 loadClass 调用。这需要花费一些精力来完成。

参考 https://www.jianshu.com/p/67081d9b0a9c https://developer.android.google.cn/training/articles/perf-jni#64-bit-considerations