通過前面的幾篇文章的學習,咱們已經打通了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
層訪問類的靜態成員(變量和方法)。若是你理解了本文的內容,那麼下一篇文章就只是一個拓展。