標籤:android ndk jni anrdoid.mk 例子
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 __cplusplusextern "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_HwDemoLOCAL_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();@Overridepublic 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類的方法。
結束。原始碼下載!