jni 的介紹
JNI是Java Native Interface的縮寫,中文為JAVA本地調用。從Java1.1開始,Java Native Interface(JNI)標准成為java平台的一部分,它允許Java代碼和其他語言寫的代碼進行交互。JNI一開始是為了本地已編譯語言,尤其是C和C++而設計的,但是它並不妨礙你使用其他語言,只要調用約定受支持就可以了。以下介紹Android 中如何使用jni移植開源庫的技巧.
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);
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方法
第五步:
jsize alen = (*env)->GetArrayLength(env,barr); //得到數組長度
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中循環讀取對象數組
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;
}
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
其他用法幾乎一致。
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 調用的方法體:
{ " 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類型
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
數組則以"["開始,用兩個字符表示
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
[Z jbooleanArray boolean[]
第三個參數為,第一個參數需要映射的本地c/c++對應的函數指針方法。
第四步
在加載jni的時候指定JNI版本並且通過傳入進來的class路徑注冊Natives 方法
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
希望對你有幫助.