Android進階之路(二) -- NDK初探
阿新 • • 發佈:2019-01-22
繼續學習NDK開發,今天來實現一個簡單的計算器功能,NativeUtil類中有一個靜態的native方法,它接收三個引數,分別是兩個運算元和一個操作符,並且返回C的計算結果。
在動手之前,我們先要解決幾個問題:
1、C怎麼獲取Java層的資料型別?
2、C怎麼訪問NativeUtil的成員變數?
使用時的形式是(引數型別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。
由此,就可以開始編寫C程式碼了,寫出的的程式碼如下:
NativeUtil類定義如下
其中,ADD,SUB,MULTI,DIVISION,分別表示加減乘除,C中取出arg0和arg1的值,根據symbol和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); }
在動手之前,我們先要解決幾個問題:
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();
}
執行結果如下:我們可以看到,例項化一個類時,會優先呼叫靜態塊,再呼叫程式碼塊和給成員變數賦值。
那麼,程式碼塊和給成員變數賦值哪個先執行呢?
答案是從頭開始執行,程式執行到哪裡,就先呼叫哪裡,按順序執行。