Android中JNI的作用,就是讓Java能夠去調用由C/C++實現的代碼,爲了實現這個功能,需要用到Anrdoid提供的NDK工具包,在這裏不講如何配置了,好麻煩,配置了好久。。。
本質上,Java去調用C/C++的代碼其實就是去調用C/C++提供的方法,所以,第一步,我們要創建一個類,並且定義一個Native方法,如下:
JniTest類:
- public class JniTest {
- public native String getTestString();
- }
可以看到,在這個方法的前面,用到了native關鍵字。
接着,我們要在命令行中編譯這個java文件,得到一個class文件,如下:
然後我們可以利用javah命令文件,生成一個C的頭文件,其實javah這一步不是必需的,因爲創建這個頭文件,只是爲了方便我們複製這個Jni中對應的方法名稱,因爲這些名稱實在太複雜了。
在這裏有一點要注意,javah命令要在包的根目錄下調用,對應的類文件,必須是完整的類名,如上圖所示,會先回到src目錄,再調用javah命令。
這樣我們就會在src文件夾下在產生一個頭文件,如下圖所示:
我們可以看到其名稱是com_lms_jni_JniTest.h,其實就是包名+類名,我們可以看看裏面的內容:
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class com_lms_jni_JniTest */
- #ifndef _Included_com_lms_jni_JniTest
- #define _Included_com_lms_jni_JniTest
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: com_lms_jni_JniTest
- * Method: getTestString
- * Signature: ()Ljava/lang/String;
- */
- JNIEXPORT jstring JNICALL Java_com_lms_jni_JniTest_getTestString
- (JNIEnv *, jobject);
- #ifdef __cplusplus
- }
- #endif
- #endif
我們可以看到,在這裏面有一個方法,名稱是Java_com_lms_jni_JniTest_getTestString,夠複雜吧,其實如果我們知道這個名稱規則,並且知道如何去實現這樣一個方法的話,我們是完全可以不生成這個頭文件的,我們可以直接寫出對應的C文件。
接下來,在jni文件中創建一個對應的C文件,名稱是值得並無所謂,但爲了統一,我們就把它叫JniTest.c吧,如下:
在這裏,我們也把com_lms_jni_JniTest.h也放到這裏了,這個其實是沒關係的,只是爲了內容的協調和統一而已,一般情況下,我們會把所以由C/C++實現的文件都放在項目目錄下一個叫 jni 的文件夾下面。
下面是在JniTest.c中實現native方法,getTestString,如下:
- #include <stdio.h>
- #include <stdlib.h>
- #include <jni.h>
- JNIEXPORT jstring JNICALL Java_com_lms_jni_JniTest_getTestString
- (JNIEnv *e, jobject obj){
- return (**e).NewStringUTF(e,"Hello from JniTest Function");
- }
在這個c文件中,我們看到,並沒有引用頭文件com_lms_jni_JniTest.h,而只是引用了一般的C/C++庫文件,比如stido.h和stdlib.h文件等,在這裏注意到一點,我們還會引用jni.h文件,jni.h文件是JNI編程中很重要的一個頭文件,關於Java中的數據類型跟jni中的數據類型的對應全部是在這個文件中定義的,後續會來看一下這個jni.h文件。
在上面JniTest.c文件中實現了方法之後,關於C/C++這邊的實現其實也就實現了,那麼接下來就是要將這個C文件編譯成so文件由Android來調用。
爲什麼是so文件呢,這是因爲Android本質上就是一個linux系統,所以其調用的JNI庫文件,都是so形式。
Android提供的NDK庫提供了ndk-build的命令來實現這個編譯過程,但在此之前,我們要先創建一個Android.mk文件,這是一個簡單的小小的Make文件,其內容如下:
- LOCAL_PATH := $(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_MODULE := com_lms_jni_HwDemo
- LOCAL_SRC_FILES := \
- HwDemo.c \
- JniTest.c \
- include $(BUILD_SHARED_LIBRARY)
在這裏,我們會定義幾個變量:
LOCAL_PATH:其值是call my-dir,而my-dir是個宏函數,會返回Android.mk所在的路徑,在這裏,就是jni文件夾。
include $(CLEAR_VARS),這個命令會清除掉所有LOCAL開頭的變量,比如LOCAL_MODULE之類的,但有一個例外,就是其上面的LOCAL_PATH 。
LOCAL_MODULE:要生成的so包名,也是Android中Java代碼加載時的名稱。
LOCAL_SRC_FILES:要進行編譯的源文件,如在這裏,有HwDemo.c和JniTest.c等。
include $(BUILD_SHARED_LIBRARY):表明生成一個動態鏈接庫。
定義後這樣一個Android.mk文件之後,在命令行中調用ndk-build命令,如下:
命令實行之後,我們可以在項目目錄下看到libs中多了一個so庫,如下:
到這裏,關於Jni實現的就結束了,接下來就是如何在Android中使用這個本地方法了。
我們創建了一個Activity,在它裏面只放置一個TextView控件,它的佈局如下:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical" >
- <TextView
- android:id="@+id/tvJni"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="test" />
- </LinearLayout>
然後在Activity中,我們要加載這個so庫,如下:
- public class HwDemo extends Activity {
- static {
- System.loadLibrary("com_lms_jni_HwDemo");//加載so庫
- }
- public native String printHello();
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- TextView tv = (TextView)findViewById(R.id.tvJni);
- JniTest jniTest = new JniTest();//調用JniTest文件的方法
- tv.setText(jniTest.getTestString());
- }
- }
1)利用static靜態代碼塊,加載so庫文件,可以看到在這裏,這個名稱就是Anrdoid.mk中定義的LOCAL_MODULE值。
2)創建JniTest對象,調用其getTestString()方法,最終顯示結果如下:
到這裏,通過一個簡單的例子,我們明白瞭如何在Android中利用JNI來調用C/C++的方法了。
最後,我們總結一下這幾個步驟:
1)創建Java類文件,並定義Native方法,如JniTest類。
2)利用javac生成class文件,然後回到src目錄,利用javah生成C/C++頭文件,在這裏要注意,javah命令要在包的根目錄下調用,對應的類文件,必須是完整的類名,如下:
在Src目錄:javah com.lms.jni.JniTest,在上面的截圖,也可以看到javac之後,是回到src目錄,再調用javah。
3)編寫對應的C文件,如JniTest.c,在裏面實現C/C++的方法,記得要放在jni文件夾下面。
4)編寫Android.mk文件,利用ndk-build命令生成so文件。
5)在Android中利用static靜態代碼塊,調用system.loadLibrary方法來加載so庫文件。
6)在Java邏輯中調用之前定義的JniTest類的方法。
結束。源代碼下載!