NativeUtil類定義如下
public class NativeUtil {
static {
System.loadLibrary("native-lib");
}
public static int ADD = 0;
public static int SUB = 1;
public static int MULTI = 2;
public static int DIVISION = 3;
public static native int calculate(int arg0,int arg1,int symbol);
}
其中,ADD,SUB,MULTI,DIVISION,分別表示加減乘除,C中取出arg0和arg1的值,根據symbol和NativeUtil的靜態成員變量對比,判斷進行哪種操作,然後返回計算結果。在動手之前,我們先要解決幾個問題:
1、C怎麼獲取Java層的數據類型?
2、C怎麼訪問NativeUtil的成員變量?
要解決第一個問題,我們得先了解C與Java數據類型的轉換,轉換表如下:
Java 類型 | 本地類型 | 描述 |
boolean | jboolean | C/C++8位整型 |
byte | jbyte | C/C++帶符號的8位整型 |
char | jchar | C/C++無符號的16位整型 |
short | jshort | C/C++帶符號的16位整型 |
int | jint | C/C++帶符號的32位整型 |
long | jlong | C/C++帶符號的64位整型e |
float | jfloat | C/C++32位浮點型 |
double | jdouble | C/C++64位浮點型 |
Object | jobject | 任何Java對象,或者沒有對應java類型的對象 |
Class | jclass | Class對象 |
String | jstring | 字符串對象 |
Object[] | jobjectArray | 任何對象的數組 |
boolean[] | jbooleanArray | 布爾型數組 |
byte[] | jbyteArray | 比特型數組 |
char[] | jcharArray | 字符型數組 |
short[] | jshortArray | 短整型數組 |
int[] | jintArray | 整型數組 |
long[] | jlongArray | 長整型數組 |
float[] | jfloatArray | 浮點型數組 |
double[] | jdoubleArray | 雙浮點型數組 |
Java中int型的數據,在C中就會用jint表示。
還有很關鍵的一點是方法簽名,方法簽名的作用是唯一確定一個方法,因爲方法可以有重載,所以僅靠方法的名稱是無法準確定位一個方法的,這個時候就需要方法簽名了,方法簽名實際上就是方法返回值和參數表的組合,它的規則如下:
使用時的形式是(參數類型1參數類型2……)返回類型
比如:
public int calculate(int arg0,int arg1,int symbol);
它的方法簽名就是(III)I
C如何訪問NativeUtil的成員變量呢?
這就需要請出JNI大總管JNIEnv了,JNIEnv負責Java與C的交互,比如從調用Java層的方法、訪問Java層的變量,都需要JNIEnv的參與。
大致閱讀源碼可知JNIEnv在C和C++中分別有不同實現,在C中JNIEnv是JNINativeInterface的一個指針類型,在C++中JNIEnv是一個結構體,它們的共同點是都與JNINativeInterface有關,JNINativeInterface中定義了很多指針,C/C++就通過這些指針調用Java層函數,詳細的內容會在後面源碼部分介紹。
查閱文檔可以知道,JNIEnv中有一個GetStaticIntField(jclass clazz, jfieldID fieldID)函數,第一個參數是需要獲取成員變量的類,第二個參數是字段ID。
字段ID如何獲取呢?繼續查閱文檔,我們又找到了GetStaticFieldID(jclass clazz, const char* name, const char* sig)函數,它的三個參數分別是需要獲取成員變量的類,成員變量名和簽名。
與此相同,JNIEnv中還提供了很多很多獲取變量信息、獲取方法信息的函數。
由此,就可以開始編寫C代碼了,寫出的的代碼如下:
#include <jni.h>
extern "C"
JNIEXPORT jint JNICALL
Java_com_tustcs_ndktest_NativeUtil_calculate(JNIEnv *env, jclass type, jint arg0, jint arg1,
jint symbol) {
if(symbol == env->GetStaticIntField(type,env->GetStaticFieldID(type,"ADD","I")))
return arg0 + arg1;
else if(symbol == env->GetStaticIntField(type,env->GetStaticFieldID(type,"SUB","I")))
return arg0 - arg1;
else if(symbol == env->GetStaticIntField(type,env->GetStaticFieldID(type,"MULTI","I")))
return arg0 * arg1;
else if(symbol == env->GetStaticIntField(type,env->GetStaticFieldID(type,"DIVISION","I")))
return arg0 / arg1;
}
然後在CMakeLists.txt中添加路徑,再去MainActivity調用。。嗯,大功告成。
今天附上關於靜態塊和成員變量初始化的小知識:
public class InitTest {
public static int sInt1 = 10;
public static int sInt2;
public int mInt1 = 30;
public int mInt2;
//局部代碼塊
{
if(mInt1 == 30) {
System.out.println("成員變量mInt1已完成初始化:mInt1 = " + mInt1);
}
mInt2 = 40;
System.out.println("代碼塊mInt2初始化:mInt2 = " + mInt2);
}
//靜態塊
static{
sInt2 = 20;
System.out.println("靜態塊sInt2初始化:sInt2 = " + sInt2);
}
public static void print() {
System.out.println("靜態方法:print()調用");
}
}
主方法執行:public static void main(String[] args) {
InitTest.print();
//InitTest test = new InitTest();
}
在主函數調用InitTest的靜態方法,結果如下:我們可以看到,調用類的靜態方法不會調用局部代碼塊,而會先調用靜態塊。
去掉註釋,實例化一個類試試
public static void main(String[] args) {
//InitTest.print();
InitTest test = new InitTest();
}
我們可以看到,實例化一個類時,會優先調用靜態塊,再調用代碼塊和給成員變量賦值。
那麼,代碼塊和給成員變量賦值哪個先執行呢?
答案是從頭開始執行,程序運行到哪裏,就先調用哪裏,按順序執行。