標籤:android ndk jni 例子 sample
在第零篇文章簡單地介紹了JNI編程的模式之後,後面兩三篇文章,我們又針對JNI中的一些概念做了一些簡單的介紹,也不知道我到底說的清楚沒有,但相信很多童鞋跟我一樣,在剛開始學習一個東西的時候,入門最好的方式就是一個現成的例子來參考,慢慢研究,再學習概念,再回過來研究代碼,加深印象,從而開始慢慢掌握。
今天我們就再來做一個小Demo,這個例子會比前面稍微複雜一點,但是如果閱讀過前面幾篇文章的話,理解起來也還是很簡單的。很多東西就是這樣,未知的時候很可怕,理解了就很簡單了。
1)我們首先定義一個Java類,裡麵包含幾個native方法,如下:
public class ParamTransferTest {public static int testval = 1;public native void changeTestVal();public native int add(int x, int y);public native String addTail(String tail);public native int[] changeArray(int[] arr);}
2)利用javah工具產生對應的標頭檔,如下:
/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_lms_jni_ParamTransferTest */#ifndef _Included_com_lms_jni_ParamTransferTest#define _Included_com_lms_jni_ParamTransferTest#ifdef __cplusplusextern "C" {#endif/* * Class: com_lms_jni_ParamTransferTest * Method: changeTestVal * Signature: ()V */JNIEXPORT void JNICALL Java_com_lms_jni_ParamTransferTest_changeTestVal (JNIEnv *, jobject);/* * Class: com_lms_jni_ParamTransferTest * Method: add * Signature: (II)I */JNIEXPORT jint JNICALL Java_com_lms_jni_ParamTransferTest_add (JNIEnv *, jobject, jint, jint);/* * Class: com_lms_jni_ParamTransferTest * Method: addTail * Signature: (Ljava/lang/String;)Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_lms_jni_ParamTransferTest_addTail (JNIEnv *, jobject, jstring);/* * Class: com_lms_jni_ParamTransferTest * Method: changeArray * Signature: ([I)[I */JNIEXPORT jintArray JNICALL Java_com_lms_jni_ParamTransferTest_changeArray (JNIEnv *, jobject, jintArray);#ifdef __cplusplus}#endif#endif
上面就產生了對應的方法,在上面我們可以看到前面文章所介紹過的方法名稱以Java開頭,方法簽名等資訊,對吧。
3)編寫 C 檔案,如下:
#include <stdio.h>#include <stdlib.h>#include "com_lms_jni_ParamTransferTest.h"#include <android/log.h>#include <jni.h>#include <malloc.h>#define LOG_TAG "System.out"#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)char* Jstring2CStr(JNIEnv * env, jstring str){char * rtn = NULL;jclass clsstring = (*env)->FindClass(env, "java/lang/String");//通過FindClass方法獲得Java的String類jstring strencode = (*env)->NewStringUTF(env, "UTF-8");//調用NewStringUTF方法,獲得"UTF-8"字串,作為編碼jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");//擷取String類的getBytes方法jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, str, mid, strencode);//調用String類的getBytes方法jsize alen = (*env)->GetArrayLength(env, barr);//獲得數組長度jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);//獲得數組的首地址,C/C++中數組的首元素就是一個指標if(alen > 0){rtn = (char*) malloc(alen + 1);memcpy(rtn, ba, alen);rtn[alen] = 0;}//上面這一步是將數組的值複製到一個char*的數組中,也就是C/C++的char數組,因為C/C++沒有字串概念,最後以0結尾。(*env)->ReleaseByteArrayElements(env, barr, ba, 0);//釋放記憶體return rtn;}/* * Class: com_lms_jni_ParamTransferTest * Method: changeTestVal * Signature: ()V */JNIEXPORT void JNICALL Java_com_lms_jni_ParamTransferTest_changeTestVal (JNIEnv * env, jobject obj){jclass clazz = (*env)->GetObjectClass(env,obj);//獲得obj對應的類,也就是ParamTransferTestjint val = (*env)->GetStaticIntField(env, clazz,(*env)->GetStaticFieldID(env, clazz,"testval","I"));//擷取欄位testval的值LOGI("before change testval = %d", val);//添加Log資訊val = val + 1;LOGI("after change testval = %d", val);(*env)->SetStaticIntField(env, clazz,(*env)->GetStaticFieldID(env, clazz,"testval","I"),val);//設定欄位testval的值}/* * Class: com_lms_jni_ParamTransferTest * Method: add * Signature: (II)I */JNIEXPORT jint JNICALL Java_com_lms_jni_ParamTransferTest_add (JNIEnv * env, jobject obj, jint x, jint y){LOGI("x = %d", x);LOGI("y = %d", y);return x + y; //返回參數x和y的和}/* * Class: com_lms_jni_ParamTransferTest * Method: addTail * Signature: (Ljava/lang/String;)Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_lms_jni_ParamTransferTest_addTail (JNIEnv * env, jobject obj, jstring str){char* p = Jstring2CStr(env,str);//將Java中的string轉化為C/C++中的char數組LOGI("str = %s", p);char* newStr = " Tail ";return (*env)->NewStringUTF(env, strcat(p, newStr));//調用strcat函數串連兩個char數組,將通過NewStringUTF返回。}/* * Class: com_lms_jni_ParamTransferTest * Method: changeArray * Signature: ([I)[I */JNIEXPORT jintArray JNICALL Java_com_lms_jni_ParamTransferTest_changeArray (JNIEnv * env, jobject obj, jintArray ja){int len = (*env)->GetArrayLength(env, ja);/擷取數組長度LOGI("len = %d", len);LOGI("address = %#x", &ja);jint* arr = (*env)->GetIntArrayElements(env, ja, 0);//將數組中的所有元素存入以jint*為首地址的數組中(數組的首地址就是數組名)int i = 0;for(;i < len; i++){LOGI("arr[%d] = %d", i, *(arr + i));*(arr + i) += 10;} //數組中每個元素的值加上10return ja;由於GetIntArrayElements的最後一個參數是0,即表明擷取的數組是不複製的,即它們操作同一塊記憶體,所以返回哪個數組都是ok的return ja;}
對應這四個方法,在上面都添加了一些注釋,大家看著注釋,自己研究一下邏輯,也就清楚了這幾個方法實現的功能是什麼,很簡單的。
4)編寫Android.mk檔案,如下:
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := com_lms_jni_HwDemoLOCAL_SRC_FILES := HwDemo.c JniTest.c ParamTransferTest.cLOCAL_LDLIBS += -lloginclude $(BUILD_SHARED_LIBRARY)
在這裡有一點的注意的是,編譯多個檔案的時候,要利用反斜線 "\"來進行換行,區分不同的C/C++檔案。
而這裡LOCAL_LDLIBS是JNI中運用log所需要添加的動態包,下一篇文章會講。
5)編寫好Android.mk檔案之後,就利用ndk-build檔案進行編譯,檔案結構如下:
在jni目錄下運行ndk-build,如下:
到這裡,關於JNI層的實現就結事了,下面就是Java端的事情。
6)Activity中調用,如下:
public class HwDemo extends Activity {static {System.loadLibrary("com_lms_jni_HwDemo");//載入動態包,名稱就是Android.mk中的Module名稱。}public native String printHello();@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);...TextView tvAdd = (TextView)findViewById(R.id.tvAdd);TextView tvString = (TextView)findViewById(R.id.tvString);TextView tvArray = (TextView)findViewById(R.id.tvArray);TextView tvChangeTestVal = (TextView)findViewById(R.id.tvChangeTestVal);ParamTransferTest ptt = new ParamTransferTest();//調用changeTestVa()方法ptt.changeTestVal();tvChangeTestVal.setText("" + ptt.testval);//調用add方法int sum = ptt.add(1, 2);tvAdd.setText(String.valueOf(sum));//調用addTail方法tvString.setText(ptt.addTail("lms"));//調用changeArray方法int[] newArr = ptt.changeArray(new int[]{1,2});StringBuilder sb = new StringBuilder();sb.append("[");sb.append(newArr[0]).append(",").append(newArr[1]);sb.append("]");tvArray.setText(sb.toString());}}
7)結果顯示:
關於如果在Java層調用JNI方法,還有在JNI層調用Java的方法,並操作Java的對象,相信經過這一個例子,再結合前面幾篇文章所講的東西,大家應該能夠對JNI的作用和應用有一個比較基本的瞭解了。
Android中底層架構的實現,尤其是在啟動的時候,Native層去載入一個系統核心服務,或者啟動Zygote虛擬機器的時候,用到了大量的JNI層面的架構,大家如果對JNI熟悉瞭解了,再去瞭解這些架構的東西,會有很大的協助的。
結束。