使用NDK移植開源項目,JNI的使用技巧

 

Jni 的介紹

 JNI是Java Native Interface的縮寫,中文爲JAVA本地調用。從Java1.1開始,Java Native Interface(JNI)標準成爲java平臺的一部分,它容許Java代碼和其餘語言寫的代碼進行交互。JNI一開始是爲了本地已編譯語言,尤爲是C和C++而設計的,可是它並不妨礙你使用其餘語言,只要調用約定受支持就能夠了。如下介紹Android 中如何使用jni移植開源庫的技巧.html

 JNI日誌輸出到Logcat中

#include <android/log.h>
#define LOG_TAG "===xcloud==="
#define LOGI(...)  android_log_print(ANDROID_LOG_INFO,LOG_TAG,VA_ARGS__)
#define LOGW(...)  android_log_print(ANDROID_LOG_WARN,LOG_TAG,VA_ARGS__)
#define LOGE(...)  android_log_print(ANDROID_LOG_ERROR,LOG_TAG,VA_ARGS__)

 

Android.mk文件添加編譯模塊:
LOCAL_LDLIBS=-lm -llog
使用方法:
LOGE("%s",test);java

JNI調用Java方法

以調用String 的getBytes方法爲例:
第一步:
jclass clsstring = (*env)->FindClass(env,"java/lang/String"); //找到Java String 類
第二步:
jstring strencode = (*env)->NewStringUTF(env,"utf-8");  //獲得一個jstring 對象
第三步:
jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B"); //獲得getBytes方法

 

第四步:
jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr, mid, strencode); //「jstr 爲傳入的字符串」調用getBytes方法android

第五步:
jsize alen = (*env)->GetArrayLength(env,barr); //獲得數組長度c++

 

jstring 轉換 char*

char* test=(char*)(*env)->GetStringUTFChars(env,jstringVariable,NULL);

 

 

返回一個java對象數組

第一步:
jclass objectClass=(*env)->FindClass(env,"com/xuzhitech/xcloud/resource"); //找到對應的java 類(對象)
第二步:
jobjectArray array=(*env)->NewObjectArray(env,currentListCount,objectClass,NULL); //經過取到的java類(對象)建立一個指定固定大小的數組
第三步:
jfieldID _uri=(*env)->GetFieldID(env,objectClass,"uri","Ljava/lang/String;");//找到對象中的列
注意:在JNI中並未提供jstring 類型的對象,因此必須經過L指定包名找到該類,在有提供的類型中,能夠直接使用該類型的大寫首字母(jlong 需使用J)
如jint 類型能夠如此編寫:
jfieldID _vcr=(*env)->GetFieldID(env,objectClass,"is_vcr","I");對應的JNI提供類型能夠參考以下  http://download.oracle.com/javase/1.4.2/docs/guide/jni/spec/functions.html
第四步:
jmethodID jid = (*env)->GetMethodID(env,objectClass,"<init>","()V");//"<init>"表明能夠訪問默認構造函數
jobject jobj=(*env)->NewObject(env,objectClass,jid); //經過第一步找到的jclass建立對應的對象

 

第五步:
(*env)->SetObjectField(env,jobj,_modtime,jmodtime); //爲對象中的每一列賦值。注意:若是JNI有提供的數據類型,可按提供的類型爲對象中的列賦值,
如:(*env)->SetIntField(env,jobj,_vcr,current->is_vcr);
第六步:
(*env)->SetObjectArrayElement(env,array,i,jobj);將對象設置進第二步聲明的數組中
DeleteLocalRef(env,jobj);//刪除引用對象數組

在JNI中循環讀取對象數組

 

for (i =  0 ;i < currentListCount ;i++) {
jobject obj = (*env)->GetObjectArrayElement(env,array,i);
jstring jstr=(*env)->GetObjectField(env,obj,_uri);
LOGE( " =====uri===%s==== ",jstringtoChar(env,jstr));
}
上面使用到的jstringtoChar方法代碼:
/* *
*jstring to char
*/
char* jstringtoChar(JNIEnv* env,jstring jstr){
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env, " java/lang/String ");
jstring strencode = (*env)->NewStringUTF(env, " utf-8 ");
jmethodID mid = (*env)->GetMethodID(env,clsstring,  " getBytes "" (Ljava/lang/String;)[B ");
jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr, mid, strencode);
jsize alen = (*env)->GetArrayLength(env,barr);
jbyte* ba = (*env)->GetByteArrayElements(env,barr, JNI_FALSE);
if (alen >  0)
{
rtn = ( char*)malloc(alen +  1);
memcpy(rtn, ba, alen);
rtn[alen] =  0;
}
(*env)->ReleaseByteArrayElements(env,barr, ba,  0);
return rtn;

oracle

 

cpp JNI與 c JNI的主要注意事項:

用C編寫jni文件,訪問JNI內置提供方法格式:
(*env)->SetObjectArrayElement(env,array,i,jobj);
用CPP編寫JNI文件,訪問JNI內置提供方法格式:
env->SetObjectArrayElement(array,i,jobj);
另外,使用CPP編寫的JNI代碼,在調用C語言編寫的庫的時候,要添加如下代碼,才能夠正常使用(否則在連接的時候找不到相關接口:undefined reference.....):
ifdef __cplusplus
extern "C" {
#endif
... //引入的頭文件
#ifdef __cplusplus
}
#endif

 

其餘用法幾乎一致。ide

 

JNI 使用Native註冊

按照上面的方法寫函數體,必須遵循JNI官方的一大堆標準進行方法的定義,有時候方法一多,不大好管理,也不利用查看,而且每次都要寫一大堆噁心的標準方法名也不是一件好事。對此JNI有一套能夠經過Native 註冊的機制可使用,以方便函數體的編寫。

 

如下是Android 調用JNI註冊Natives 的步驟:
第一步
聲明Java中要調用jni 的類路徑:
static const char *className="com/xuzhitech/xcloud/cadaver";
第二步
建立方法格式結構體:
struct JNINativeMethod {
const char* name;//method name 
const char* signature; //java method return value 
void* fnPtr;//c/c++ method 
} ;函數

第三步
使用結構體註冊須要供Android 調用的方法體:post

static JNINativeMethod methods[] = {
{ " StringTestOne "" ()Ljava/lang/String; ", ( void*)StringTestOne},
{ " executels ", " ()[Lcom/xuzhitech/xcloud/resource; ",( void*)executels},
{ " getProgress ", " ()Lcom/xuzhitech/xcloud/fileProgress; ",( void*)getProgress},
{ " getdownloadState ", " ()I ",( void*)getdownloadState},
{ " changeExecutable ", " (Ljava/lang/String;Ljava/lang/String;)I ",( void*)changeExecutable},
{ " Login ", " (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I ",( void*)Login},
{ " getCurrentListCount ", " ()I ",( void*)getCurrentListCount},
{ " mkcol ", " (Ljava/lang/String;)I ",( void*)mkcol},
{ " deleteFile ", " (Ljava/lang/String;)I ",( void*)deleteFile},
{ " deleteCol ", " (Ljava/lang/String;)I ",( void*)deleteCol},
{ " getFullUri ", " ()Ljava/lang/String; ",( void*)getFullUri},
{ " cdCommand ", " (Ljava/lang/String;)I ",( void*)cdCommand},
{ " logout ", " ()V ",( void*)logout},
{ " doCopy ", " (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I ",( void*)doCopy},
{ " doMove ", " (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I ",( void*)doMove},
{ " getFile ", " (Ljava/lang/String;Ljava/lang/String;)V ",( void*)getFile},
{ " putFile ", " (Ljava/lang/String;Ljava/lang/String;)V ",( void*)putFile},
{ " lock ", " (Ljava/lang/String;)I ",( void*) lock},
{ " unlock ", " (Ljava/lang/String;Ljava/lang/String;)I ",( void*)unlock},
{ " getToken ", " (Ljava/lang/String;)Ljava/lang/String; ",( void*)getToken},
{ " isSetToken ", " (Ljava/lang/String;)I ",( void*)isSetToken},

}; 網站

第一個參數爲:Java實現要調用的方法名稱
第二個參數爲:該方法須要的返回值與方法參數,如->(方法參數)返回值,詳細使用參考以下:
具體的每個字符的對應關係以下

字符 Java類型 C類型

void  void
Z jboolean boolean
I jint  int
J jlong  long
D jdouble  double
F jfloat  float
B jbyte  byte
C jchar  char
S jshort  short

 

數組則以"["開始,用兩個字符表示

 

[I jintArray  int[]
[F jfloatArray  float[]
[B jbyteArray  byte[]
[C jcharArray  char[]
[S jshortArray  short[]
[D jdoubleArray  double[]
[J jlongArray  long[]
[Z jbooleanArray boolean[]
也可返回任意java對象,如上代碼的()[Lcom/xuzhitech/xcloud/resource;表明一個不帶參數的方法,而且返回java類中的Lcom/xuzhitech/xcloud/resource;數組。'['表明數組的意思,'L'表明類的意思

 

第三個參數爲,第一個參數須要映射的本地c/c++對應的函數指針方法。

第四步
在加載jni的時候指定JNI版本而且經過傳入進來的class路徑註冊Natives 方法

 

jint JNI_OnLoad(JavaVM* vm,  void * reserved){ 

jint result = JNI_ERR;
JNIEnv* env = NULL;
jclass clazz;
int methodsLenght;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed\n");
return JNI_ERR;
}
// assert(env != NULL); 
clazz = (*env)->FindClass(env,className);
if (clazz == NULL) {
LOGE("Native registration unable to find class '%s'", className);
return JNI_ERR;
}
methodsLenght = sizeof(methods) / sizeof(methods[0]);
if ((*env)->RegisterNatives(env,clazz, methods, methodsLenght) < 0) {
LOGE("RegisterNatives failed for '%s'", className);
return JNI_ERR;
}
// 
result = JNI_VERSION_1_4;
return result;
}

 

 

注意,使用Natives註冊運行程序時,它會先檢測jni與java類使用jni 類的native 方法是否相對應與一致,即便你沒有使用到該 方法也會進行檢測。
第五步
經過上面的修改,c/c++編寫的jni 文件就能夠不帶一大串標準名稱了,您能夠像正常編寫c同樣編寫你的jni函數,以下:

/*
*move file
*/
jint doMove(JNIEnv* env,jobject thiz,jstring jsrc,jstring jdest,jstring jrename){
char* src=( char*)(*env)->GetStringUTFChars(env,jsrc,NULL);
char* dest=( char*)(*env)->GetStringUTFChars(env,jdest,NULL);
char* rename=( char*)(*env)->GetStringUTFChars(env,jrename,NULL);
int returnValue=multi_move(src,dest,rename);
//  free(src);
(*env)->ReleaseStringUTFChars(env,jsrc,src);
//  free(dest);
(*env)->ReleaseStringUTFChars(env,jdest,dest);
//  free(rename);
(*env)->ReleaseStringUTFChars(env,jrename,rename);
return returnValue;
}

 

 注:

JNI的一些基本方法的使用均可以參考這個網站:http://docs.oracle.com/javase/1.4.2/docs/guide/jni/spec/functions.html

 

但願對你有幫助. 

相關文章
相關標籤/搜索