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
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
