JNI是Java Native Interface的縮寫,通過JNI可以方便我們在Android平臺上進行C/C++編程。要用JNI首先必須安裝Android的NDK,配置好NDK環境之後就可以在Eclipse下進行C/C++開發了。
其實JNI的原理很容易理解,其本質就是在Java層定義一個接口,同時在C層用C/C++代碼實現該接口的功能並編譯成動態鏈接庫,這樣Activity就可以通過Java層接口調用生成的動態鏈接庫,完成相應的功能。簡單地說就是兩點:(1)定義Java接口(JNI),(2)用C/C++實現接口功能並打包成庫文件以供調用。
在這裏,我將假設讀者已經在Linux環境下安裝好了NDK,並且會使用Eclipse。下面以C++爲例一步一步說明JNI開發過程。
1 創建Android工程
新建一個TestJNI目錄,用Eclipse在該目錄下創建一個名爲TestJNI的Android工程,包名爲com.TestJNI.jni。創建完成後在工程的src目錄下會自動創建一個名爲TestJNIActivity.java的文件。
2 設計Java接口
先不用管這個文件,在該文件的位置再創建一個名爲TestJNI.java文件:
打開TestJNI.java,我們將在這個文件裏創建一個JNI接口類,該Java類提供一個加法運算的接口:
這裏的函數聲明一定要加native修飾。
3 編譯JNI
將TestJNI.java文件複製到工程的bin目錄下,在終端中進入該工程的bin目錄,輸入javac TestJNI.java,這時會生成一個TestJNI.class文件。
在bin文件夾下,如果沒有則創建目錄:/com/TestJNI/jni,並把TestJNI.class複製到/bin/com/TestJNI/jni目錄下。然後在終端裏進入工程的bin目錄,輸入javah -jni com.TestJNI.jni.TestJNI,此時會生成一個com_TestJNI_jni_TestJNI.h文件。
com_TestJNI_jni_TestJNI.h文件就是對應於上面定義的Java接口的C/C++頭文件。打開這個文件,可以看到系統已經爲我們自動完成了接口函數的聲明:
這三個函數分別對應於JNI的三個接口函數,命名方式只是在前面加上了Java包名。
4 用C/C++實現JNI
有了JNI的C/C++頭文件,就可以在C層實現JNI接口了。首先在工程目錄下創建一個jni目錄,這個目錄就是專門用來放C/C++代碼的。把com_TestJNI_jni_TestJNI.h文件複製到jni目錄下,並在這裏創建一個com_TestJNI_jni_TestJNI.cpp文件。
由於我想用C++來實現JNI,所以上面兩個文件我只是用來作爲動態鏈接庫的接口,具體的實現我希望放在一個類裏面來完成,因此我再添加兩個文件:Add.h和Add.cpp。
下面我們就來實現CAdd類和JNI接口。首先實現CAdd類:
Add.h
Add.cpp
然後我們來寫com_TestJNI_jni_TestJNI.cpp,實現JNI:
到此我們的C/C++部分就全部實現了。
5 創建mk文件
JNI實現了之後就要把C/C++代碼編譯成動態鏈接庫.so文件,這樣Java程序才能調用JNI的接口。要編譯so文件,需要寫Android.mk和Application.mk兩個文件。我們先來寫Android.mk。
先在工程目錄的jni下創建一個Android.mk文件:
其中LOCAL_PATH是C/C++代碼所在目錄,也就是我們的jni目錄。
LOCAL_MODULE是要編譯的庫的名稱。編譯器會自動在前面加上lib,在後面加上.so。
LOCAL_SRC_FILES是要編譯的C/C++文件。
現在我們在工程的根目錄下創建一個Application.mk文件,並輸入如下內容:
寫完了mk文件就可以開始編譯C/C++代碼了。在終端裏進入工程的根目錄,輸入ndk-build即可:
編譯成功後會在工程目錄的libs/armeabi目錄下生成一個libTestJNI.so文件。
7 在Java中調用JNI
大家應該還記得在創建工程的時候就已經生成了一個TestJNIActivity.java文件吧。現在打開這個文件,我們來對它進行一點修改,添加我們調用JNI計算加法的代碼,如下:
在這裏我們首先用System.loadLibrary("TestJNI")加載了C/C++編譯的so文件,然後創建了一個TestJNI對象,通過該對象調用了so庫中的方法。現在的代碼應該是編譯不過去的,因爲我們還沒有給TextView添加ID,所以R.id.tv是無效的。那麼我們來添加這個ID。找到res/layout目錄下的main.xml文件:
雙擊打開該文件,在TextView標籤下添加一行android:id="@+id/tv",這樣就創建了一個名爲tv的域並自動分配ID,如下圖:
這個例子只是爲了顯示JNI的用法,只要寫好了JNI接口,其他的編程就跟C/C++一樣了。編譯動態鏈接庫的時候,除了使用ndk-build命令之外,也可以在Eclipse工程中配置一個Builder,直接在Eclipse下編譯。