前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你应该了解的JNI知识(二)——Java与JNI互相调用

你应该了解的JNI知识(二)——Java与JNI互相调用

作者头像
用户1108631
发布2019-08-17 12:34:17
1.5K0
发布2019-08-17 12:34:17
举报

在你应该了解的JNI知识(一)——静态注册与动态注册中,了解了JNI是如何使用的,以及两种注册方式的使用以及区别。本篇博客将介绍Java和JNI的互相调用,因此主要包括两部分:

  1. JNI层调用Java层
  2. Java层调用JNI、Native层

JNI层调用Java层

JNI层调用Java层有点类似Java的反射机制,需要首先找到类、再找到某个方法或字段,再进行调用。 这里涉及JNIEnv的几个方法:

代码语言:javascript
复制
//根据全限定名找到类jclass FindClass(const char* name)  //根据方法名和方法参数的签名得到方法idjmethodID GetMethodID(jclass clazz, const char* name, const char* sig)//从方法名可以看到,该方法是获取静态方法的jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)//Java层的方法返回不同类型,需要调用不同的方法_jtype Call##_jname##MethodA(jobject obj, jmethodID methodID,           \        jvalue* args)                                                 //根据字段名得到字段idjfieldID GetFieldID(jclass clazz, const char* name, const char* sig)...

上面的方法主要包括得到jclass类、jmethodId、jfieldId,是不是和Java的反射好像?

有点需要注意的是,JNI层的方法严格区分了返回类型,返回类型是boolean的,会有CallBoolenMethodId,返回类型是Int的,会有CallIntMethodId;同理关于FieldId和数组的方法都是这样的,不能调用错。

这边以一个demo为例:Java层提供了三个方法:JNI层首先调用两个方法得到两个数,然后相加,再调用Java层更新界面。

MainActivity的代码

代码语言:javascript
复制
class MainActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        btnJniInvokeJava.setOnClickListener {            jniInvokeJava()        }    }    external fun jniInvokeJava()    fun getNum1(): Int {        return 10    }    fun getNum2(): Double {        return 20.0    }    fun showResult(value: Double) {        tvResultShow.text = value.toString()    }    companion object {        init {            System.loadLibrary("jniAndJava")        }    }}

JNI层的代码

这里采用静态注册的方式,方法的的实现如下:

代码语言:javascript
复制
//调用MainActivity中的两个方法,得到两个数,相加,再显示到TextView上JNIEXPORT void JNICALLJava_com_enniu_jnidemo_MainActivity_jniInvokeJava(JNIEnv *env, jobject thiz) {    //找到MainActivity类    jclass mainActivityClazz = env->FindClass("com/enniu/jnidemo/MainActivity");    //找到getNum1()方法    jmethodID getNum1MethodId = env->GetMethodID(mainActivityClazz, "getNum1", "()I");    //找到getNum2()方法    jmethodID getNum2MethodId = env->GetMethodID(mainActivityClazz, "getNum2", "()D");    //因为getNum1()返回值是Int,所以调用CallIntMethod    jint num1=env->CallIntMethod(thiz,getNum1MethodId);    //因为getNum2()返回值是Double,所以调用CallDoubleMethod    jdouble  num2=env->CallDoubleMethod(thiz,getNum2MethodId);    //两数相加    jdouble result=num1+num2;    //设置给TextView    jmethodID showResultMethodId=env->GetMethodID(mainActivityClazz,"showResult","(D)V");    //showResult()方法的返回值是void,所以调用CallVoidMethod()方法    env->CallVoidMethod(thiz,showResultMethodId,result);}}

从上面可以看到,整体调用流程和反射是很像的。Call*Method()的第一个参数是jobject,表示在某个对象上调用该方法,因此如果需要调用对象的方法,JNI又无法获取的话,需要从Java层传入。

代码语言:javascript
复制
jclass GetObjectClass(jobject obj)

上面这个方法提供了从jobject-->jclass的快捷方式,就不需要走FindClass()的步骤了,这里是不是发现 getObjectClass====Object.getClass() FindClass()====Class.forName()

是不是很相似?因此很多时候我们要学会类比,这样记忆和理解起来会比较快。

Java层调用C/C++代码

这里可以标题取得有所歧义,因为JNI不就是Java调用C/C++吗?这里的情形可以举个例子:比如说需要在C++层创建多份同一个对象,Java层会根据不同情况调用不同对象,那么该怎么做呢?

Java层要能调用不同对象,得保存各个对象的信息,但那是C层的对象,怎么保存了?答案是指针(对象地址),然后根据不同对象传入不同指针即可。如果C++层需要保存对象,可以使用vector或map来进行保存。

举个例子:C++层有Person类,Java层去创建Person类、设置和获取name字段。

MainActivity代码

代码语言:javascript
复制
override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        btnJavaInvokeCpp.setOnClickListener {            //创建一个Person类并设置name            val person1 = createPerson()            setPersonName(person1, "Hello")            //再创建一个Person类并设置name            val person2 = createPerson()            setPersonName(person2, "World")            //获取Person类的name字段            tvResultShow.text = "${getPersonName(person1)},${getPersonName(person2)}"        }    }    //三个native方法声明    external fun createPerson(): Long    external fun setPersonName(ptr: Long, name: String)    external fun getPersonName(ptr: Long): String

JNI层的代码

创建了一个Person类,有一个字段name。jni中对应的三个代码如下:

代码语言:javascript
复制
JNIEXPORT jlong JNICALLJava_com_enniu_jnidemo_MainActivity_createPerson(JNIEnv *env, jobject thiz, jstring name) {    Person *p = new Person();    //返回指针,供Java层保存    return reinterpret_cast<uintptr_t>(p);}JNIEXPORT void JNICALLJava_com_enniu_jnidemo_MainActivity_setPersonName(JNIEnv *env, jobject thiz, jlong ptr,                                                  jstring name) {    //强转到Person指针    Person *person = reinterpret_cast<Person *>(ptr);    person->setName(env->GetStringUTFChars(name, NULL));}JNIEXPORT jstring JNICALLJava_com_enniu_jnidemo_MainActivity_getPersonName(JNIEnv *env, jobject thiz, jlong ptr) {    //强转到Person指针    Person *person = reinterpret_cast<Person *>(ptr);    return env->NewStringUTF(person->getName());}

可以看到在createPerson()方法中创建一个Person类,然后返回其指针,set和get方法首先都是通过Java层传入的指针强转到Person对象,再进行操作的。

总结

至此,介绍完了Java与JNI代码的互相调用。 JNI调用Java代码是一种类似反射的原理,先找到jclass、再找到jmethodId,然后调用,这样一步步地来;Java调用C/C++代码创建对象是需要保存对象指针,然后各种操作是要将指针传入到jni层,然后强转到具体对象再进行操作的。

关于代码,可以移步:https://github.com/wangli135/ClimbDemo/tree/master/jnidemo

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 每天学点Android知识 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JNI层调用Java层
    • MainActivity的代码
      • JNI层的代码
      • Java层调用C/C++代码
        • MainActivity代码
          • JNI层的代码
          • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档