Android JNI開發系列(九)JNI調用Java的靜態方法&實例方法

JNI調用Java的靜態方法&實例方法

package org.professor.jni.bean;

import android.util.Log;

/**
 *  Created by peng on 2018/10/11.
 */ 
public class Person {
    
    /*C/CPP 調用Java 靜態方法 */ 
    public native void callJavaStaticMethod();
    
    /*C/C++ 調用Java 實例方法 */ 
    public native void callJavaInstanceMethod();
    
    public static void setPersonInfo(String name, int age) { Log.i("PERSON", "name= " + name + "\\t age=" + age); }
    
    public void setPersonMoney(float money) { Log.i("PERSON", "money= " + money);
    
    } 
}

上面寫了一個Java Bean類,裏面定義了兩個Native方法,分別用來調用,該類的靜態方法和實例方法,實如今本地native方法裏java

JNI調用靜態方法

# include

# include

# include

//extern "C" // C 編譯器編譯個人代碼

JNIEXPORT void JNICALL Java_org_professor_jni_bean_Person_callJavaStaticMethod(JNIEnv *env, jobject instance) {
	//1.獲取類類型的Class對象
	jclass personClass = (*env)->FindClass(env, "org/professor/jni/Person");
	
	if (NULL == personClass) {
	    __android_log_print(ANDROID_LOG_ERROR, "PERSON", "NOT FIND CLASS");
	    return;
	}
	//2.獲取方法ID  方法簽名:(String:Ljava/lang/String ;int:I)  方法簽名能夠經過javap -s命令生成
	
	jmethodID pJmethodID = (*env)->GetStaticMethodID(env, personClass, "setPersonInfo",
	                                                 "(Ljava/lang/String;I)V");
	if (NULL == pJmethodID) {
	    __android_log_print(ANDROID_LOG_ERROR, "PERSON", "NOT FIND STATIC METHOD");
	    return;
	}
	jstring str = (*env)->NewStringUTF(env, "Yimi");
	
	//3.調用方法
	(*env)->CallStaticVoidMethod(env, personClass, pJmethodID, str, 18);
	
	// 刪除局部變量引用表,JVM內部因爲引用表,
	// 記錄全局和局部的變量,當超過引用表數量,致使內存溢出
	// 形成崩潰
	(*env)->DeleteLocalRef(env, personClass);
	(*env)->DeleteLocalRef(env, str);
}

注意android

  • JVM 針對全部數據類型的返回值都定義了相關的函數。上面callStaticMethod 方法的返回類型爲 void,因此調用 CallStaticVoidMethod。根據返回值類型不一樣,JNI 提供了一系列不一樣返回值的函數,如:CallStaticIntMethodCallStaticFloatMethodCallStaticShortMethodCallStaticObjectMethod等,分別表示調用返回值爲 intfloatshortObject 類型的函數,引用類型統一調用CallStaticObjectMethod 函數。另外,每種返回值類型的函數都提供了接收3種實參類型的實現:CallStaticXXXMethod(env, clazz, methodID, ...)CallStaticXXXMethodV(env, clazz, methodID, va_list args)CallStaticXXXMethodA(env, clazz, methodID, const jvalue args),分別表示:接收可變參數列表、接收 va_list 做爲實參和接收const jvalue爲實參。下面是jni.h頭文件中CallStaticVoidMethod 的三種實參的函數原型:安全

    void (JNICALL *CallStaticVoidMethod)  (JNIEnv *env, jclass cls, jmethodID methodID, ...);  
    void (JNICALL *CallStaticVoidMethodV) (JNIEnv *env, jclass cls, jmethodID methodID, va_list args);  
    void (JNICALL *CallStaticVoidMethodA) (JNIEnv *env, jclass cls, jmethodID methodID, const jvalue * args);
  • 雖然函數結束後,JVM 會自動釋放全部局部引用變量所佔的內存空間。但仍是手動釋放一下比較安全,由於在 JVM 中維護着一個引用表,用於存儲局部和全局引用變量,經測試在 Android NDK 環境下,這個表的最大存儲空間是512 個引用,若是超過這個數就會形成引用表溢出,JVM 崩潰。在 PC 環境下測試,無論申請多少局部引用也不釋放都不會崩,我猜可能與 JVM 和 Android Dalvik 虛擬機實現方式不同的緣由。因此有申請就及時釋放是一個好的習慣!(局部引用和全局引用在後面的文章中會詳細介紹)函數

JNI調用實例方法

JNIEXPORT void JNICALL Java_org_professor_jni_bean_Person_callJavaInstanceMethod(JNIEnv *env, jobject instance) {

	//1.獲取類類型的Class對象
	jclass personClass = (*env)->FindClass(env, "org/professor/jni/Person");
	if (NULL == personClass) {
	    __android_log_print(ANDROID_LOG_ERROR, "PERSON", "NOT FIND CLASS");
	    return;
	}
	
	//2.獲取方法ID  方法簽名:(String:Ljava/lang/String ;int:I)
	jmethodID pJmethodID = (*env)->GetMethodID(env, personClass, "setPersonMoney",
	                                           "(F)V");
	if (NULL == pJmethodID) {
	    __android_log_print(ANDROID_LOG_ERROR, "PERSON", "NOT FIND INSTANCE METHOD");
	    return;
	}
	
	//3.獲取默認的構造方法ID (<init()>; = public org.professor.jni.bean.Person(); ),先獲取構造函數的ID
	jmethodID constructor = (*env)->GetMethodID(env, personClass, "<init>", "()V");
	if (NULL == constructor) {
	    __android_log_print(ANDROID_LOG_ERROR, "PERSON", "NOT FIND INSTANCE CONSTRUCTOR METHOD");
	    return;
	}
	//4.獲取默認構造函數對象
	jobject object = (*env)->NewObject(env, personClass, constructor);
	if (NULL == object) {
	    __android_log_print(ANDROID_LOG_ERROR, "PERSON", "NOT FIND OBJECT");
	    return;
	}
	//5.調用實例方法
	(*env)->CallVoidMethod(env, object, pJmethodID, 30.0);
	
	//6.刪除局部引用變量
	(*env)->DeleteLocalRef(env, personClass);
	(*env)->DeleteLocalRef(env, object);
}

擴展方法簽名表

簽名 java類型
V void
Z boolean
I int
J long
D double
F float
B byte
C char
S short
[I int[]
[F float[]
[B byte[]
[C char[]
[S short[]
[D double[]
[J long[]
[Z boolean[]
L用/分割包的完整類名; Ljava/lang/String; Object

例如: void set(String str); 簽名:"(Ljava/lang/String;)V"測試

能夠用javap -s "ClassFile" 命令查看方法簽名spa

總結

  • 調用靜態方法使用 CallStaticXXXMethod/V/A 函數,XXX 表明返回值的數據類型。如:CallStaticIntMethod
  • 調用實例方法使用 CallXXXMethod/V/A 函數,XXX 表明返回的數據類型,如: `CallIntMethod
  • 獲取一個實例方法的 ID,使用 GetMethodID 函數,傳入方法名稱和方法簽名
  • 獲以一個靜態方法的 ID,使用 GetStaticMethodID 函數,傳入方法名稱和方法簽名
  • 獲取構造方法 ID,方法名稱使用""
  • 獲取一個類的 Class 實例,使用 FindClass 函數,傳入類描述符。JVM 會從 classpath 目錄下開始搜索。
  • 建立一個類的實例,使用 NewObject 函數,傳入 Class 引用和構造方法 ID
  • 刪除局部變量引用,使用 DeleteLocalRef, 傳入引用變量
  • 方法簽名格式:(形參參數列表)返回值類型。注意:形參參數列表之間不須要用空格或其它字符分隔
  • 類描述符格式:L 包名路徑/類名;,包名之間用/分隔。如:Ljava/lang/String;
  • 調用 GetMethodID 獲取方法 ID 和調用 FindClass 獲取 Class 實例後,要作異常判斷
相關文章
相關標籤/搜索