通过前面的几篇文章的学习,咱们已经打通了Java
层到Native
层的通道。Java
层调用Native
层很简单,只须要调用一个native
方法,那么Native
层如何回调Java
层呢,从这篇文章开始,咱们就来探讨这个问题。html
Java
类的成员(变量和方法)有静态和非静态之分,静态成员一般经过类来直接调用(固然也能够用对象调用),非静态成员经过类的对象来调用。在JNI
层也听从一样的规则。所以我将经过两篇文章分别来讲明,而今天这篇文章就是讲述在JNI
层如何经过Java
对象来访问成员(变量和方法)。java
经过对前面文章的学习,咱们已经学会了如何以最快的速度实现函数的"动态注册",那么咱们来先作一个准备工做,实现"动态注册"ios
首先准备一个带有native
方法的Java
类,名为Person.java
c++
package com.bxll.jnidemo;
public class Person {
static {
System.loadLibrary("person_jni");
}
native void native_init();
private int mAge = -1;
private String mName;
public Person() {
native_init();
}
public void setName(String name) {
mName = name;
}
public String toString() {
return "Name: " + mName + ", Age: " + mAge;
}
public static void main(String[] args) {
System.out.println(new Person());
}
}
复制代码
而后在Native
层进行实现,实现的文件为person.cpp
数组
#include <jni.h>
#include <iostream>
static void com_bxll_jnidemo_Person_init(JNIEnv * env, jobject obj) {
}
const JNINativeMethod methods[] =
{
{"native_init", "()V", (void *)com_bxll_jnidemo_Person_init}
};
jint JNI_OnLoad(JavaVM * vm, void * reserved) {
int jni_version = -1;
JNIEnv * env = NULL;
if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) == JNI_OK)
{
jclass clazz_person = env->FindClass("com/bxll/jnidemo/Person");
if (env->RegisterNatives(clazz_person, methods, sizeof methods / sizeof methods[0]) == JNI_OK)
{
jni_version = JNI_VERSION_1_6;
}
}
return jni_version;
}
复制代码
那么,com_bxll_jnidemo_Person_init
函数就是Java
层的native
方法的实现,咱们将在这个函数中来说解如何访问Java
对象的变量和方法。bash
在com_bxll_jnidemo_Person_init
函数中来实现获取Java
对象变量的值,以及设置变量的值。oracle
static void com_bxll_jnidemo_Person_init(JNIEnv * env, jobject obj) {
// 获取Java对象obj的Class对象
jclass clazz_person = env->GetObjectClass(obj);
// 从Class对象中获取mAge变量
jfieldID fieldID_mAge = env->GetFieldID(clazz_person, "mAge", "I");
// 从Java对象obj中获取变量mAge的值
jint age = env->GetIntField(obj, fieldID_mAge);
std::cout << "Age: " << age << std::endl;
// 设置Java对象obj的变量mAge的值
env->SetIntField(obj, fieldID_mAge, 18);
}
复制代码
从注释中能够看出,JNI
层获取Java
对象的某个变量值以及设置变量值的方式,是否是和使用Java
反射的方式很是像。ide
若是你懂Java
反射,那么这里的逻辑就瓜熟蒂落,固然,若是你不懂也不要紧,只要咱们记住获取对象变量值最终调用的函数是什么,在这里就是GetIntField
,而后经过参数一步一步推断就能够了。函数
那么如今,咱们来看看这里涉及的函数post
jclass GetObjectClass(JNIEnv *env, jobject obj);
复制代码
jobject obj
表明一个Java
对象,函数返回一个对象的Class
对象。
经过JNI函数动态注册之JNI类型和签名这篇文章可知,
jclass
是Java
的Class
对象在JNI
中的类型。
与Java
反射同样,获取到了Class
对象后就能够从中获取变量和方法。
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
复制代码
参数
env
: 指向JNIEnv
的指针。clazz
: Class
对象,能够经过GetObjectClass()
或者FindClass()
获取。name
: Class
对象的某个变量的名字。sig
: Class
对象的变量的类型签名。若是不知道参数
sig
的类型签名如何写,能够阅读JNI函数动态注册之JNI类型和签名进行了解。
返回一个Class
对象的变量,类型为jfieldID
。Class
对象的变量由参数name
和sig
惟一标识。
与Java
反射同样,获取到了变量后,就能够经过这个变量获取到变量的值,也能够设置变量的值。
根据变量的类型,会有不一样的函数来获取变量的值。函数的基本形式以下
NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
复制代码
基本类型函数名 | 返回类型 |
---|---|
GetBooleanField() | jboolean |
GetByteField() | jbyte |
GetCharField() | jchar |
GetShortField() | jshort |
GetIntField() | jint |
GetLongField() | jlong |
GetFloatField() | jfloat |
GetDoubleField() | jdouble |
GetObjectField() | jobject |
前八项对应于Java
的八中基本类型,第九项对应于Java
的全部引用类型。
例如对于int
类型,对应的函数原型你以下
jint GetIntField(JNIEnv * env, jobject obj, jfieldID fieldID);
复制代码
参数
jobject obj
表明一个Java
对象jfieldID fieldID
表明Class
对象的某个变量,经过GetFieldID()
获取。根据变量的类型,也会有不一样的函数来设置变量的值。函数的基本形式以下
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID,
NativeType value);
复制代码
函数名 | 参数类型 |
---|---|
SetBooleanField() | jboolean |
SetByteField() | jbyte |
SetCharField() | jchar |
SetShortField() | jshort |
SetIntField() | jint |
SetLongField() | jlong |
SetFloatField() | jfloat |
SetDoubleField() | jdouble |
SetObjectField() | jobject |
前八项对应于Java
的八中基本类型,第九项对应于Java
的全部引用类型。
例如,对与int
类型,函数原型以下
void setIntField(JNIEnv * env, jobject obj, jfieldID fieldID, jint value);
复制代码
参数
jobject obj
表明Java
对象jfieldID fieldID
表明Class
对象的某个方法,经过GetFieldID()
获取jint value
表明要设置的int
值咱们仍然在com_bxll_jnidemo_Person_init
函数中来调用Java
对象的方法
static void com_uni_ndkdemo_Person_init(JNIEnv *env, jobject obj) {
// 获取Class对象
jclass clazz_person = env->GetObjectClass(obj);
// 从Class对象中获取setName方法
jmethodID methodID_setName = env->GetMethodID(clazz_person, "setName", "(Ljava/lang/String;)V");
// 建立调用setName()须要的参数值
jstring name = env->NewStringUTF("David");
// 调用Java对象obj的setName()方法
env->CallVoidMethod(obj, methodID_setName, name);
}
复制代码
咱们一样能够发现,在JNI
中调用Java
对象的方法,和Java
反射使用的方式几乎同样。咱们如今来看看涉及到的JNI
函数的做用。
jmethodID GetMethodID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);
复制代码
参数
jclass clazz
: Java
类的Class
对象,须要经过GetObjectClass()
或者FindClass()
获取。const char * name
: 方法名,须要经过GetMethodID()
获取。const char * sig
: 方法签名。若是不知道参数
sig
的方法签名如何写,能够阅读JNI函数动态注册之JNI类型和签名进行了解。
返回值
jmethodID
对象,不然返回NULL
。注意: 若是要获取
Java
类的构造方法,参数const char *name
的值为<init>
,参数const char *sig
的值为void (V)
。这个是比较特殊的,须要注意。
根据Java
方法返回的类型的不一样,JNI
有不一样的函数来调用Java
对象的方法。不过基本形式有三种
NativeType Call<type>Method(JNIEnv *env, jobject obj,
jmethodID methodID, ...);
NativeType Call<type>MethodA(JNIEnv *env, jobject obj,
jmethodID methodID, const jvalue *args);
NativeType Call<type>MethodV(JNIEnv *env, jobject obj,
jmethodID methodID, va_list args);
复制代码
能够看到这三类函数的区别在于传参的方式不一样。
第一个函数和第三个函数其实原理都是同样的,不过最经常使用的应该就是第一个。
那么第二个函数就有点意思了,参数使用的是jvalue
类型的数组做为参数。
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
复制代码
jvalue
是一个联合体,联合体中定义Java
基本类型和引用类型的变量,所以就能够以数组的行为来传递参数,这下就明白了吧。
咱们以第一个函数为例来讲明下具体的函数原型。
若是一个Java
方法返回的是基本类型,例如int
,那么函数原型以下
jint CallIntMethod(JNIEnv * env, jobject obj, jmethodID methodID, ...);
复制代码
而若是一个Java
方法的返回类型是引用类型,例如String
,那么函数原型以下
jobject CallObjectMethod(JNIEnv * env, jobject obj, jmethodID methodID, ...);
复制代码
固然,若是一个Java
方法的返回类型为void
,那么函数原型以下
void CallVoidMethod(JNIEnv * env, jobject obj, jmethodID methodID, ...);
复制代码
参数
jobject obj
: 表明Java
对象jmethodID methodID
: 对象的方法,经过GetMethodID()
获取...
: 可变参数,表明须要传入给Java
方法的参数。static void com_bxll_jnidemo_Person_init(JNIEnv * env, jobject obj) {
jclass clazz_person = env->GetObjectClass(obj);
jfieldID fieldID_mAge = env->GetFieldID(clazz_person, "mAge", "I");
// 判断是否能获取到变量
if (fieldID_mAge == NULL)
{
std::cout << "Can't find Person.mAge.'" << std::endl;
return;
}
jint age = env->GetIntField(obj, fieldID_mAge);
std::cout << "Age: " << age << std::endl;
env->SetIntField(obj, fieldID_mAge, 18);
jmethodID methodID_setName = env->GetMethodID(clazz_person, "setName", "(Ljava/lang/String;)V");
// 判断是否能获取到方法
if (methodID_setName == NULL)
{
std::cout << "Can't find setName method." << std::endl;
return;
}
jstring name = env->NewStringUTF("David");
env->CallVoidMethod(obj, methodID_setName, name);
// 手动释放局部引用
env->DeleteLocalRef(name);
env->DeleteLocalRef(clazz_person);
}
复制代码
在这个完整的实现中,咱们增长了判空,以及手动释放局部引用,这是一个比较好的JNI
开发习惯。
经过调用Java
对象的native
方法,虚拟机会调用相应的本地函数,而本地函数的第二个参数必定为jobject object
,表明的就是Java
对象。因而能够经过JNI
技术,访问这个对象的成员(变量和方法)。
在下一篇文章,咱们就来说解如何在JNI
层访问类的静态成员(变量和方法)。若是你理解了本文的内容,那么下一篇文章就只是一个拓展。