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
# 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 提供了一系列不一樣返回值的函數,如:CallStaticIntMethod
、CallStaticFloatMethod
、CallStaticShortMethod
、CallStaticObjectMethod
等,分別表示調用返回值爲 int
、float
、short
、Object
類型的函數,引用類型統一調用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 虛擬機實現方式不同的緣由。因此有申請就及時釋放是一個好的習慣!(局部引用和全局引用在後面的文章中會詳細介紹)函數
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
GetMethodID
函數,傳入方法名稱和方法簽名GetStaticMethodID
函數,傳入方法名稱和方法簽名FindClass
函數,傳入類描述符。JVM 會從 classpath 目錄下開始搜索。NewObject
函數,傳入 Class 引用和構造方法 IDDeleteLocalRef
, 傳入引用變量Ljava/lang/String;
GetMethodID
獲取方法 ID 和調用 FindClass
獲取 Class 實例後,要作異常判斷