Android進階之路(二) -- NDK初探

繼續學習NDK開發,今天來實現一個簡單的計算器功能,NativeUtil類中有一個靜態的native方法,它接收三個參數,分別是兩個操作數和一個操作符,並且返回C的計算結果。


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 類型本地類型描述
booleanjbooleanC/C++8位整型
bytejbyteC/C++帶符號的8位整型
charjcharC/C++無符號的16位整型
shortjshortC/C++帶符號的16位整型
intjintC/C++帶符號的32位整型
longjlongC/C++帶符號的64位整型e
floatjfloatC/C++32位浮點型
doublejdoubleC/C++64位浮點型
Objectjobject任何Java對象,或者沒有對應java類型的對象
ClassjclassClass對象
Stringjstring字符串對象
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(); 
}
運行結果如下:

我們可以看到,實例化一個類時,會優先調用靜態塊,再調用代碼塊和給成員變量賦值。

那麼,代碼塊和給成員變量賦值哪個先執行呢?

答案是從頭開始執行,程序運行到哪裏,就先調用哪裏,按順序執行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章