編輯:關於Android編程
或許你知道了jni的簡單調用,其實不算什麼百度谷歌一大把,雖然這些jni絕大多數情況下都不會讓我們安卓工程師來弄,畢竟還是有點難,但是我們還是得打破砂鍋知道為什麼這樣干吧,至少也讓我們知道調用流程和數據類型以及處理方法,或許你會有不一樣的發現。
其實總的來說從java的角度來看.h文件就是java中的interface(插座),然後.c/.cpp文件呢就是實現類罷了,然後數據類型和java還是有點出入我們還是得了解下(媽蛋,天氣真熱不適合生存了)。
今天也給出一個JNI動態注冊native方法的例子,如圖:
JNI 開發流程主要分為以下步驟:
編寫聲明了 native 方法的 Java 類 將 Java 源代碼編譯成 class 字節碼文件 用 javah -jni 命令生成.h頭文件(javah 是 jdk 自帶的一個命令,-jni 參數表示將 class 中用native 聲明的函數生成 JNI 規則的函數) 用本地代碼(c/c++)實現.h頭文件中的函數 將(c/c++)文件編譯成動態庫(Windows:*.dll,linux/unix:*.so,mac os x:*.jnilib) 拷貝動態庫至本地庫目錄下,並運行 Java 程序(System.loadLibrary(“xxx”))我們安卓開發工程師顯然只需要編寫native的java類,然後clean下編譯器知道把我們的java編譯成了class文件,但是我們必須知道是調用了javac命令,javah jni命令我們還是得執行,其他的工作就差不多了,不管是什麼編譯器,反正jni步驟就這樣。
JVM 查找 native 方法有兩種方式:
按照 JNI 規范的命名規則 調用 JNI 提供的 RegisterNatives 函數,將本地函數注冊到 JVM 中。是不是感到特別的意外,jni還能夠利用RegisterNatives 函數查找native方法,其實我也才剛剛知道有這方法,因為要根據包名類名方法名的規范來寫是很傻逼的,哈哈,有的人或許覺得這樣很直觀。
我們還是按步驟來說吧,先來解讀JNI規范的命名規則:
* 我們先來看下.h文件 *
/* DO NOT EDIT THIS FILE - it is machine generated */ #include/* Header for class com_losileeya_jnimaster_JNIUtils */ #ifndef _Included_com_losileeya_jnimaster_JNIUtils #define _Included_com_losileeya_jnimaster_JNIUtils #ifdef __cplusplus extern "C" { #endif /* * Class: com_losileeya_jnimaster_JNIUtils * Method: say * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_losileeya_jnimaster_JNIUtils_say (JNIEnv *, jclass,jstring); #ifdef __cplusplus } #endif #endif
我們再來看下Linux 下jni_md.h頭文件內容:
#ifndef _JAVASOFT_JNI_MD_H_ #define _JAVASOFT_JNI_MD_H_ #define JNIEXPORT #define JNIIMPORT #define JNICALL typedef int jint; #ifdef _LP64 /* 64-bit Solaris */ typedef long jlong; #else typedef long long jlong; #endif typedef signed char jbyte; #endif
從上面我們可以看出文件以#ifndef開始然後#endif 結尾,不會C的話是不是看起來有點蛋疼,#號呢代表宏,這裡來普及一下宏的使用和定義。
#define 標識符 字符串
其中,#表示這是一條預處理命令;#define為宏定義命令;“標識符”為宏定義的宏名;“字符串”可以上常數、表達式、格式串等。
舉例如下:
#define PI 3.14 // 對3.14進行宏定義,宏名為PI void main() { printf("%f", PI); // 輸出3.14 }
條件編譯的命令
#ifndef def 語句1 # else 語句2 # endif 表示如果def在前面進行了宏定義那麼就編譯語句1(語句2不編譯),否則編譯語句2(語句1不編譯)
再看我們.h文件並沒有else,所以我們就編譯宏定義的本地方法類(com_losileeya_jnimaster_JNIUtils),你突然就會發現我們的宏是我們的native類,然後把包名點類名的點改成了下劃線,然後你會發現多了_Included不要多想,就是included關鍵字加個下劃線,這樣我們就給本地類進行了宏定義。然後
#ifdef __cplusplus
extern “C” {#endif
這是說明如果宏定義了c++,並且裡面有c我們還是支持c的,並且c代碼寫extern “C” {}裡面。可以看出#endif對應上面的#ifdef-cplusplus,#ifdef-cplusplus對應最後的#endif, #ifdef與#endif總是一一對應的,表明條件編譯開始和結束。
JNIEXPORT 和 JNICALL 的作用
因為安卓是跑在 Linux 下的,所以從 Linux 下的jni_md.h頭文件可以看出來,JNIEXPORT 和 JNICALL 是一個空定義,所以在 Linux 下 JNI 函數聲明可以省略這兩個宏。
再來看我們的方法:
JNIEXPORT jstring JNICALL Java_com_losileeya_jnimaster_JNIUtils_say (JNIEnv *, jclass,jstring);
函數命名規則為:Java_類全路徑_方法名。
如:Java_com_losileeya_jnimaster_JNIUtils_say,其中Java_是函數的前綴,com_losileeya_jnimaster_JNIUtils是類名,say是方法名,它們之間用 _(下劃線) 連接。
函數返回值類型:夾在 JNIEXPORT 和 JNICALL 宏中間的 jstring,表示函數的返回值類型,對應 Java 的String 類型。
如果你需要裝逼的話你就可以自己去寫.h文件,然後就可以拋棄javah -jni 命令,只需要按照函數命名規則編寫相應的函數原型和實現即可(逼就是這麼裝出來的)
是不是感覺一個方法的名字太長非常的蛋疼,然後我們呢直接使用,RegisterNatives來自己命名調用native方法,這樣是不是感覺好多了。
要實現呢,我們必須重寫JNI_OnLoad()方法這樣就會當調用 System.loadLibrary(“XXXX”)方法的時候直接來調用JNI_OnLoad(),這樣就達到了動態注冊實現native方法的作用。
/* * System.loadLibrary("lib")時調用 * 如果成功返回JNI版本, 失敗返回-1 */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { return -1; } assert(env != NULL); if (!registerNatives(env)) {//注冊 return -1; } //成功 result = JNI_VERSION_1_4; return result; }
並且我們需要為類注冊本地方法,那樣就能方便我們去調用,不多說看方法:
/* * 為所有類注冊本地方法 */ static int registerNatives(JNIEnv* env) { return registerNativeMethods(env, JNIREG_CLASS, gMethods,sizeof(gMethods) / sizeof(gMethods[0])); }
也可以為某一個類注冊本地方法
/* * 為某一個類注冊本地方法 */ static int registerNativeMethods(JNIEnv* env , const char* className , JNINativeMethod* gMethods, int numMethods) { jclass clazz; clazz = (*env)->FindClass(env, className); if (clazz == NULL) { return JNI_FALSE; } if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { return JNI_FALSE; } return JNI_TRUE; }
JNINativeMethod 結構體的官方定義
typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod;第一個變量name是Java中函數的名字。 第二個變量signature,用字符串是描述了Java中函數的參數和返回值
第三個變量fnPtr是函數指針,指向native函數。前面都要接 (void *)
第一個變量與第三個變量是對應的,一個是java層方法名,對應著第三個參數的native方法名字(不明白請看後面代碼就會清楚了)。
哈哈最後我們就把native方法綁定到JNINativeMethod上我們來看下事例:
static JNINativeMethod gMethods[] = { {"setDataSource", "(Ljava/lang/String;)V", (void *)com_media_ffmpeg_FFMpegPlayer_setDataSource}, {"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)com_media_ffmpeg_FFMpegPlayer_setVideoSurface}, {"prepare", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_prepare}, {"_start", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_start}, {"_stop", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_stop}, {"getVideoWidth", "()I", (void *)com_media_ffmpeg_FFMpegPlayer_getVideoWidth}, {"getVideoHeight", "()I", (void *)com_media_ffmpeg_FFMpegPlayer_getVideoHeight}, {"seekTo", "(I)V", (void *)com_media_ffmpeg_FFMpegPlayer_seekTo}, {"_pause", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_pause}, {"isPlaying", "()Z", (void *)com_media_ffmpeg_FFMpegPlayer_isPlaying}, {"getCurrentPosition", "()I", (void *)com_media_ffmpeg_FFMpegPlayer_getCurrentPosition}, {"getDuration", "()I", (void *)com_media_ffmpeg_FFMpegPlayer_getDuration}, {"_release", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_release}, {"_reset", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_reset}, {"setAudioStreamType", "(I)V", (void *)com_media_ffmpeg_FFMpegPlayer_setAudioStreamType}, {"native_init", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_native_init}, {"native_setup", "(Ljava/lang/Object;)V", (void *)com_media_ffmpeg_FFMpegPlayer_native_setup}, {"native_finalize", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_native_finalize}, {"native_suspend_resume", "(Z)I", (void *)com_media_ffmpeg_FFMpegPlayer_native_suspend_resume}, };
第一個參數就是我們寫的方法,第三個就是.h文件裡面的方法,第二個參數顯得有點難度,這裡會主要去講。
主要是第二個參數比較復雜:
括號裡面表示參數的類型,括號後面表示返回值。
“()” 中的字符表示參數,後面的則代表返回值。例如”()V” 就表示void * Fun(); “(II)V” 表示 void Fun(int a, int b); “(II)I” 表示 int sum(int a, int b);
這些字符與函數的參數類型的映射表如下:
字符 Java類型 C類型
V 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函數的參數是class,則以”L”開頭,以”;”結尾中間是用”/” 隔開的包及類名。而其對應的C函數名的參數則為jobject. 一個例外是String類,其對應的類為jstring<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPkxqYXZhL2xhbmcvU3RyaW5nOyBTdHJpbmcganN0cmluZzxiciAvPg0KTGphdmEvbmV0L1NvY2tldDsgU29ja2V0IGpvYmplY3Q8YnIgLz4NCjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160725/201607251018581158.png" title="\" />
如果JAVA函數位於一個嵌入類,則用
好了,所有 的介紹也完了,那麼我們就來實現我們的代碼:(果斷把h文件刪除,看效果)
JNIUtil.c:
#include#include #include #include #include #define JNIREG_CLASS "com/losileeya/registernatives/JNIUtil"//指定要注冊的類 jstring call(JNIEnv* env, jobject thiz) { return (*env)->NewStringUTF(env, "動態注冊JNI,居然可以把頭文件刪掉也不會影響結果,牛逼不咯"); } jint sum(JNIEnv* env, jobject jobj,jint num1,jint num2){ return num1+num2; } /** * 方法對應表 */ static JNINativeMethod gMethods[] = { {"stringFromJNI", "()Ljava/lang/String;", (void*)call}, {"sum", "(II)I", (void*)sum}, }; /* * 為某一個類注冊本地方法 */ static int registerNativeMethods(JNIEnv* env , const char* className , JNINativeMethod* gMethods, int numMethods) { jclass clazz; clazz = (*env)->FindClass(env, className); if (clazz == NULL) { return JNI_FALSE; } if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { return JNI_FALSE; } return JNI_TRUE; } /* * 為所有類注冊本地方法 */ static int registerNatives(JNIEnv* env) { return registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0])); } /* * System.loadLibrary("lib")時調用 * 如果成功返回JNI版本, 失敗返回-1 */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { return -1; } assert(env != NULL); if (!registerNatives(env)) {//注冊 return -1; } //成功 result = JNI_VERSION_1_4; return result; }
代碼寫完了,主要也是利用findClass來獲取方法,從而實現方法的調用。效果重現:
demo 傳送夢:RegisterNatives.rar
基本類型和本地等效類型表:
引用類型:
接口函數表:
const struct JNINativeInterface ... = { NULL, NULL, NULL, NULL, GetVersion, DefineClass, FindClass, NULL, NULL, NULL, GetSuperclass, IsAssignableFrom, NULL, Throw, ThrowNew, ExceptionOccurred, ExceptionDescribe, ExceptionClear, FatalError, NULL, NULL, NewGlobalRef, DeleteGlobalRef, DeleteLocalRef, IsSameObject, NULL, NULL, AllocObject, NewObject, NewObjectV, NewObjectA, GetObjectClass, IsInstanceOf, GetMethodID, CallObjectMethod, CallObjectMethodV, CallObjectMethodA, CallBooleanMethod, CallBooleanMethodV, CallBooleanMethodA, CallByteMethod, CallByteMethodV, CallByteMethodA, CallCharMethod, CallCharMethodV, CallCharMethodA, CallShortMethod, CallShortMethodV, CallShortMethodA, CallIntMethod, CallIntMethodV, CallIntMethodA, CallLongMethod, CallLongMethodV, CallLongMethodA, CallFloatMethod, CallFloatMethodV, CallFloatMethodA, CallDoubleMethod, CallDoubleMethodV, CallDoubleMethodA, CallVoidMethod, CallVoidMethodV, CallVoidMethodA, CallNonvirtualObjectMethod, CallNonvirtualObjectMethodV, CallNonvirtualObjectMethodA, CallNonvirtualBooleanMethod, CallNonvirtualBooleanMethodV, CallNonvirtualBooleanMethodA, CallNonvirtualByteMethod, CallNonvirtualByteMethodV, CallNonvirtualByteMethodA, CallNonvirtualCharMethod, CallNonvirtualCharMethodV, CallNonvirtualCharMethodA, CallNonvirtualShortMethod, CallNonvirtualShortMethodV, CallNonvirtualShortMethodA, CallNonvirtualIntMethod, CallNonvirtualIntMethodV, CallNonvirtualIntMethodA, CallNonvirtualLongMethod, CallNonvirtualLongMethodV, CallNonvirtualLongMethodA, CallNonvirtualFloatMethod, CallNonvirtualFloatMethodV, CallNonvirtualFloatMethodA, CallNonvirtualDoubleMethod, CallNonvirtualDoubleMethodV, CallNonvirtualDoubleMethodA, CallNonvirtualVoidMethod, CallNonvirtualVoidMethodV, CallNonvirtualVoidMethodA, GetFieldID, GetObjectField, GetBooleanField, GetByteField, GetCharField, GetShortField, GetIntField, GetLongField, GetFloatField, GetDoubleField, SetObjectField, SetBooleanField, SetByteField, SetCharField, SetShortField, SetIntField, SetLongField, SetFloatField, SetDoubleField, GetStaticMethodID, CallStaticObjectMethod, CallStaticObjectMethodV, CallStaticObjectMethodA, CallStaticBooleanMethod, CallStaticBooleanMethodV, CallStaticBooleanMethodA, CallStaticByteMethod, CallStaticByteMethodV, CallStaticByteMethodA, CallStaticCharMethod, CallStaticCharMethodV, CallStaticCharMethodA, CallStaticShortMethod, CallStaticShortMethodV, CallStaticShortMethodA, CallStaticIntMethod, CallStaticIntMethodV, CallStaticIntMethodA, CallStaticLongMethod, CallStaticLongMethodV, CallStaticLongMethodA, CallStaticFloatMethod, CallStaticFloatMethodV, CallStaticFloatMethodA, CallStaticDoubleMethod, CallStaticDoubleMethodV, CallStaticDoubleMethodA, CallStaticVoidMethod, CallStaticVoidMethodV, CallStaticVoidMethodA, GetStaticFieldID, GetStaticObjectField, GetStaticBooleanField, GetStaticByteField, GetStaticCharField, GetStaticShortField, GetStaticIntField, GetStaticLongField, GetStaticFloatField, GetStaticDoubleField, SetStaticObjectField, SetStaticBooleanField, SetStaticByteField, SetStaticCharField, SetStaticShortField, SetStaticIntField, SetStaticLongField, SetStaticFloatField, SetStaticDoubleField, NewString, GetStringLength, GetStringChars, ReleaseStringChars, NewStringUTF, GetStringUTFLength, GetStringUTFChars, ReleaseStringUTFChars, GetArrayLength, NewObjectArray, GetObjectArrayElement, SetObjectArrayElement, NewBooleanArray, NewByteArray, NewCharArray, NewShortArray, NewIntArray, NewLongArray, NewFloatArray, NewDoubleArray, GetBooleanArrayElements, GetByteArrayElements, GetCharArrayElements, GetShortArrayElements, GetIntArrayElements, GetLongArrayElements, GetFloatArrayElements, GetDoubleArrayElements, ReleaseBooleanArrayElements, ReleaseByteArrayElements, ReleaseCharArrayElements, ReleaseShortArrayElements, ReleaseIntArrayElements, ReleaseLongArrayElements, ReleaseFloatArrayElements, ReleaseDoubleArrayElements, GetBooleanArrayRegion, GetByteArrayRegion, GetCharArrayRegion, GetShortArrayRegion, GetIntArrayRegion, GetLongArrayRegion, GetFloatArrayRegion, GetDoubleArrayRegion, SetBooleanArrayRegion, SetByteArrayRegion, SetCharArrayRegion, SetShortArrayRegion, SetIntArrayRegion, SetLongArrayRegion, SetFloatArrayRegion, SetDoubleArrayRegion, RegisterNatives, UnregisterNatives, MonitorEnter, MonitorExit, GetJavaVM, };
基本上jni的數據和方法都差不多放這裡了,你就可以隨便開發了。這個你也可以去看
jni完全手冊
字符數組與jbyteArray
jbyteArray轉字符數組int byteSize = (int) env->GetArrayLength(jbyteArrayData); //jbyteArrayData是jbyteArray類型的數據 unsigned char* data = new unsigned char[byteSize + 1]; env->GetByteArrayRegion(jbyteArrayData, 0, byteSize, reinterpret_cast字符數組轉jbyteArray(data)); data[byteSize] = '\0';
jbyte *jb = (jbyte*) data; //data是字符數組類型 jbyteArray jarray = env->NewByteArray(byteSize); //byteSize是字符數組大小 env->SetByteArrayRegion(jarray, 0, byteSize, jb);
字符數組與jstring
jstring轉字符數組char* JstringToChar(JNIEnv* env, jstring jstr) { if(jstr == NULL) { return NULL; } char* rtn = NULL; jclass clsstring = env->FindClass("java/lang/String"); jstring strencode = env->NewStringUTF("utf-8"); jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode); jsize alen = env->GetArrayLength(barr); jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE); if (alen > 0) { rtn = (char*) malloc(alen + 1); memcpy(rtn, ba, alen); rtn[alen] = 0; } env->ReleaseByteArrayElements(barr, ba, 0); return rtn; }字符數組轉jstring
jstring StrtoJstring(JNIEnv* env, const char* pat) { jclass strClass = env->FindClass("java/lang/String"); jmethodID ctorID = env->GetMethodID(strClass, "", "([BLjava/lang/String;)V"); jbyteArray bytes = env->NewByteArray(strlen(pat)); env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat); jstring encoding = env->NewStringUTF("utf-8"); return (jstring)env->NewObject(strClass, ctorID, bytes, encoding); }
特麼最簡單的可以直接使用
jstring jstr = env->NewStringUTF(str);
jint與int的互轉都可以直接使用強轉,如:
jint i = (jint) 1024;
上面的代碼你看見了嗎,都是env的一級指針來做的,所以是cpp的使用方法,如果你要轉成c的那麼就把env替換為(*env)好了,具體的方法可能有點小改動(請自行去參考jni手冊),報錯的地方請自行引入相關的.h文件,估計對你了解jni有更深的了解。
本篇主要介紹了JNI動態注冊native方法,並且順便截了幾個jni的圖,以及使用的基本數據轉換處理,至於實際應用中比如java 調用c,c調用java以及混合調用等我們都需要實踐中去處理問題。
本文站在巨人的肩膀上 自我感覺又進了一步而成。基於翔神的大作基礎之上寫的一個為RecyclerView添加HeaderView FooterView 的另一種解決方案,上
BroadcastReceiver(廣播接收器)是Android中的四大組件之一。下面就具體介紹一下Broadcast Receiver組件的用法。下面是Android
目前智能電視終端(智能電視和智能電視盒子)已經越來越火,過去主打視頻功能,如今的智能電視終端不僅會繼續完善視頻功能,還會加入電視游戲功能,同時這也趕上了“電視游戲機解禁”
在實現ListView單選時,我們可以在Adapter中自己創建一個selectPosition參數,這樣是能實現需求。但加入要是再加一個多選接著又在Adapter中創建