知識(shí)
不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們?cè)谧非笃湟曈X(jué)表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營(yíng)銷(xiāo)的便利,運(yùn)營(yíng)的高效,讓網(wǎng)站成為營(yíng)銷(xiāo)工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏?jí)提供便捷的支持!
Androidframework層JNI的使用淺析
發(fā)表時(shí)間:2020-10-19
發(fā)布人:葵宇科技
瀏覽次數(shù):73
尊敬原創(chuàng):http://blog.csdn.net/yuanzeyao/article/details/42418977
JNI技巧對(duì)于多java開(kāi)辟的同伙信賴并不陌生,即(java native interface),本地調(diào)用接口,重要功能有以下兩點(diǎn):
1、java層調(diào)用C/C++層代碼
2、C/C++層調(diào)用java層代碼
可能有些人會(huì)認(rèn)為jni技巧破壞了Java說(shuō)話的跨平臺(tái)性,有這種設(shè)法主意可能是因?yàn)槟銓?duì)java懂得得還不敷深,如不雅你看看jdk源碼,你會(huì)發(fā)明在jdk瑯綾擎大年夜量應(yīng)用了jni技巧,并且java虛擬機(jī)就是用本地說(shuō)話寫(xiě)的,所以導(dǎo)致jvm并不克不及跨平臺(tái)性,所以說(shuō)java的跨平臺(tái)性并不是100%的跨平臺(tái)的。相反你應(yīng)當(dāng)看到應(yīng)用Jni的優(yōu)勢(shì):
1、因?yàn)镃/C++說(shuō)話本來(lái)機(jī)比java說(shuō)話出生早,所以很多庫(kù)代碼都是應(yīng)用C/C++寫(xiě)的,有了Jni我們就可以直接應(yīng)用了,不消反復(fù)造輪子。
2、弗成否定,C/C++履行效力比java 高,對(duì)于一些對(duì)效力有請(qǐng)求的功能,必須應(yīng)用C/C++.
因?yàn)楸P(pán)似揭捉究Android 中java層和native層是若何連接起來(lái)的,所以想研究一下Android中的jni技巧(在瀏覽之前,最好懂得jni中的根本常識(shí),如jni中數(shù)據(jù)類(lèi)型,簽名格局,不然看起來(lái)可能有些吃力),因?yàn)楣ぷ骱蚆ediaPlayer有關(guān),這里就應(yīng)用MediaPlayer為例吧。
下面給出一張圖,經(jīng)由過(guò)程此圖,我們扼要解釋一下jni是若何連接Java層和本地層的。
[img]http://img.blog.csdn.net/20150105104601352?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXVhbnpleWFv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
當(dāng)我們的app要播放視頻的時(shí)刻,我們應(yīng)用的是java層的MediaPlayer類(lèi),我們進(jìn)入到MediaPlayer.java看看(提示:我這里應(yīng)用的是源碼4.1)
重要留意的有兩點(diǎn):
1、靜態(tài)代碼塊:
static { System.loadLibrary("media_jni"); native_init(); }
2、native_init的簽名:
private static native final void native_init();
看到靜態(tài)代碼塊后,我們可以知道MediaPlayer對(duì)應(yīng)的jni層代碼在Media_jni.so庫(kù)中
本地層對(duì)應(yīng)的so庫(kù)是libmedia.so,所以MediaPlayer.java經(jīng)由過(guò)程Media_jni.so和MediaPlayer.cpp(libmedia.so)進(jìn)行交互
下面我們就深刻到細(xì)節(jié)吧。不過(guò)在深刻細(xì)節(jié)前,我先要告訴你一個(gè)規(guī)矩,在Android中,平日java層類(lèi)和jni層類(lèi)的名字有如下關(guān)系,拿MediaPlayer為例,java層叫android.media.MediaPlayer.java,那么jni層叫做android_media_MediaPlayer.cpp
因?yàn)閚ative_init是一個(gè)本處所法,那么我們就到android_media_MediaPlayer.cpp找到native_init的對(duì)應(yīng)辦法吧
static void android_media_MediaPlayer_native_init(JNIEnv *env) { jclass clazz; clazz = env->FindClass("android/media/MediaPlayer"); if (clazz == NULL) { return; } fields.context = env->GetFieldID(clazz, "mNativeContext", "I"); if (fields.context == NULL) { return; } fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V"); if (fields.post_event == NULL) { return; } fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "I"); if (fields.surface_texture == NULL) { return; } }
對(duì)應(yīng)膳綾擎的代碼,如不雅你對(duì)java中的反射懂得得很透辟的話,其實(shí)很好懂得,起首找到j(luò)ava層的MediaPlayer的Class對(duì)象,jclass是java層Class在native層的代碼,然后分別保存mNaviceContext字段,postEventFromNative辦法,mNativeSurfaceTexture字段。
其拭魅這里我最想解釋的是別的一個(gè)問(wèn)題,就是MediaPlayer中的native_init辦法時(shí)若何跟android_media_MediaPlayer.cpp中的android_media_MediaPlayer_native_init對(duì)應(yīng)起來(lái)的,因?yàn)槲覀冎廊绮谎艖?yīng)用javah主動(dòng)生成的頭文件,那么在jni層的名字應(yīng)當(dāng)是java_android_media_MediaPlayer_native_linit。其拭魅這里涉及到一個(gè)動(dòng)態(tài)注冊(cè)的過(guò)程。
其實(shí)袈溱java層代用System.loadLibrary成功后,就會(huì)調(diào)用jni文件中的JNI_onLoad辦法,android_media_MediaPlayer.cpp中的JNI_onLoad辦法如下(朝長(zhǎng)進(jìn)步部分)
jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { ALOGE("ERROR: GetEnv failed\n"); goto bail; } assert(env != NULL); if (register_android_media_MediaPlayer(env) < 0) { ALOGE("ERROR: MediaPlayer native registration failed\n"); goto bail; } /* success -- return valid version number */ result = JNI_VERSION_1_4; bail: return result; }這里有一個(gè)辦法叫做register_android_media_MediaPlayer,我們進(jìn)入此辦法,看看注冊(cè)了什么
static int register_android_media_MediaPlayer(JNIEnv *env) { return AndroidRuntime::registerNativeMethods(env, "android/media/MediaPlayer", gMethods, NELEM(gMethods)); }
這里就是調(diào)用了AndroidRuntime供給的registerNativeMethods辦法,這里涉及到一個(gè)gMethods的變量,它其實(shí)是一個(gè)構(gòu)造體
typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod;
name:就是在java層辦法名稱(chēng)
signature:就是辦法在簽名
fnPtr:在jni層對(duì)應(yīng)的函數(shù)名稱(chēng)
,那么我們找到native_init在gMethods對(duì)應(yīng)的值吧
static JNINativeMethod gMethods[] = { { "_setDataSource", "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V", (void *)android_media_MediaPlayer_setDataSourceAndHeaders }, .... {"native_init", "()V", (void *)android_media_MediaPlayer_native_init}, ... };接下來(lái),我們看看AndroidRuntime中的registerNativeMethods做了什么吧
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { return jniRegisterNativeMethods(env, className, gMethods, numMethods); }
調(diào)用了jniRegisterNativeMethods
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); ALOGV("Registering %s natives", className); scoped_local_ref<jclass> c(env, findClass(env, className)); if (c.get() == NULL) { ALOGE("Native registration unable to find class '%s', aborting", className); abort(); } if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { ALOGE("RegisterNatives failed for '%s', aborting", className); abort(); } return 0; }
最終調(diào)用了env的RegisterNativers完成了注冊(cè)。
其實(shí)寫(xiě)到這里,我們已經(jīng)知道了java層和jni是若何接洽起來(lái)的,接下來(lái)我想說(shuō)的是jni是若何將java層和native接洽起來(lái)的,照樣用MediaPlayer為例吧,我們進(jìn)入MediaPlayer的構(gòu)造函數(shù)。
public MediaPlayer() { Looper looper; if ((looper = Looper.myLooper()) != null) { mEventHandler = new EventHandler(this, looper); } else if ((looper = Looper.getMainLooper()) != null) { mEventHandler = new EventHandler(this, looper); } else { mEventHandler = null; } /* Native setup requires a weak reference to our object. * It's easier to create it here than in C++. */ native_setup(new WeakReference<MediaPlayer>(this)); }
這里創(chuàng)建了一個(gè)mEventHandler對(duì)象,并調(diào)用了native_setup辦法,我們進(jìn)入到android_media_MediaPlayer.cpp的對(duì)應(yīng)辦法看看
static void android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this) { ALOGV("native_setup"); sp<MediaPlayer> mp = new MediaPlayer(); if (mp == NULL) { jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); return; } // create new listener and give it to MediaPlayer sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this); mp->setListener(listener); // Stow our new C++ MediaPlayer in an opaque field in the Java object. setMediaPlayer(env, thiz, mp); }
這里創(chuàng)建了一個(gè)本地MediaPlayer對(duì)象,并且設(shè)置了listener,(如不雅做過(guò)播放器的同窗應(yīng)當(dāng)知道這個(gè)listener應(yīng)當(dāng)知道干啥,不知道也沒(méi)緊要),最后調(diào)用了setMediaPlayer辦法,這個(gè)才是我們須要存眷的。
static sp<MediaPlayer> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer>& player) { Mutex::Autolock l(sLock); sp<MediaPlayer> old = (MediaPlayer*)env->GetIntField(thiz, fields.context); if (player.get()) { player->incStrong(thiz); } if (old != 0) { old->decStrong(thiz); } env->SetIntField(thiz, fields.context, (int)player.get()); return old; }其實(shí)就是先拿到fields.context的對(duì)應(yīng)的值,還記得這個(gè)這個(gè)值是什么嗎,不記得的可以回到膳綾擎看看
fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
其實(shí)就是java層mNativeContext對(duì)應(yīng)的值,就是將本地MediaPlayer的地址存放到mNativeContext中。
如今參加我們要播放一個(gè)本地Mp4視頻,那么應(yīng)用如下代碼即可
mediaPlayer.setDataSource("/mnt/sdcard/a.mp4"); mediaPlayer.setDisplay(surface1.getHolder()); mediaPlayer.prepare(); mediaPlayer.start();
其拭魅這里調(diào)用的 幾個(gè)都是本處所法,這里我就是用prepare辦法為例,講解MediaPlaeyr.java和MediaPlayer.cpp的交互
當(dāng)在java層調(diào)用prepare辦法時(shí),在jni層會(huì)調(diào)用如下辦法
static void android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } // Handle the case where the display surface was set before the mp was // initialized. We try again to make it stick. sp<ISurfaceTexture> st = getVideoSurfaceTexture(env, thiz); mp->setVideoSurfaceTexture(st); process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." ); }這里經(jīng)由過(guò)程getMediaPlayer辦法拿到本地的MediaPlayer對(duì)象,調(diào)用調(diào)用本處所法process_media_player_call,并將本地MediaPlayer調(diào)用parepare辦法的結(jié)不雅傳遞給此辦法。
static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message) { if (exception == NULL) { // Don't throw exception. Instead, send an event. if (opStatus != (status_t) OK) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0); } } else { // Throw exception! if ( opStatus == (status_t) INVALID_OPERATION ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); } else if ( opStatus == (status_t) PERMISSION_DENIED ) { jniThrowException(env, "java/lang/SecurityException", NULL); } else if ( opStatus != (status_t) OK ) { if (strlen(message) > 230) { // if the message is too long, don't bother displaying the status code jniThrowException( env, exception, message); } else { char msg[256]; // append the status code to the message sprintf(msg, "%s: status=0x%X", message, opStatus); jniThrowException( env, exception, msg); } } } }在這個(gè)瑯綾擎根據(jù)prepare返回的狀況,如不雅exception==null 并且prepare履行掉敗,測(cè)試不拋異常,而是調(diào)用本地MediaPlayer的notify辦法。
void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj) { ALOGV("message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2); bool send = true; bool locked = false; ... switch (msg) { case MEDIA_NOP: // interface test message break; case MEDIA_PREPARED: ALOGV("prepared"); mCurrentState = MEDIA_PLAYER_PREPARED; if (mPrepareSync) { ALOGV("signal application thread"); mPrepareSync = false; mPrepareStatus = NO_ERROR; mSignal.signal(); } break; case MEDIA_PLAYBACK_COMPLETE: ALOGV("playback complete"); if (mCurrentState == MEDIA_PLAYER_IDLE) { ALOGE("playback complete in idle state"); } if (!mLoop) { mCurrentState = MEDIA_PLAYER_PLAYBACK_COMPLETE; } break; case MEDIA_ERROR: // Always log errors. // ext1: Media framework error code. // ext2: Implementation dependant error code. ALOGE("error (%d, %d)", ext1, ext2); mCurrentState = MEDIA_PLAYER_STATE_ERROR; if (mPrepareSync) { ALOGV("signal application thread"); mPrepareSync = false; mPrepareStatus = ext1; mSignal.signal(); send = false; } break; case MEDIA_INFO: // ext1: Media framework error code. // ext2: Implementation dependant error code. if (ext1 != MEDIA_INFO_VIDEO_TRACK_LAGGING) { ALOGW("info/warning (%d, %d)", ext1, ext2); } break; case MEDIA_SEEK_COMPLETE: ALOGV("Received seek complete"); if (mSeekPosition != mCurrentPosition) { ALOGV("Executing queued seekTo(%d)", mSeekPosition); mSeekPosition = -1; seekTo_l(mCurrentPosition); } else { ALOGV("All seeks complete - return to regularly scheduled program"); mCurrentPosition = mSeekPosition = -1; } break; case MEDIA_BUFFERING_UPDATE: ALOGV("buffering %d", ext1); break; case MEDIA_SET_VIDEO_SIZE: ALOGV("New video size %d x %d", ext1, ext2); mVideoWidth = ext1; mVideoHeight = ext2; break; case MEDIA_TIMED_TEXT: ALOGV("Received timed text message"); break; default: ALOGV("unrecognized message: (%d, %d, %d)", msg, ext1, ext2); break; } sp<MediaPlayerListener> listener = mListener; if (locked) mLock.unlock(); // this prevents re-entrant calls into client code if ((listener != 0) && send) { Mutex::Autolock _l(mNotifyLock); ALOGV("callback application"); listener->notify(msg, ext1, ext2, obj); ALOGV("back from callback"); } }
做過(guò)播放器的同窗應(yīng)當(dāng)對(duì)膳綾擎幾個(gè)消息都不陌生吧,因?yàn)閯偛耪{(diào)用prepare辦法掉敗了,所以這里應(yīng)當(dāng)履行MEDIA_ERROR分支,最后調(diào)用listener的notify代碼,這個(gè)listener就是在native_setup中設(shè)置的
void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2, const Parcel *obj) { JNIEnv *env = AndroidRuntime::getJNIEnv(); if (obj && obj->dataSize() > 0) { jobject jParcel = createJavaParcelObject(env); if (jParcel != NULL) { Parcel* nativeParcel = parcelForJavaObject(env, jParcel); nativeParcel->setData(obj->data(), obj->dataSize()); env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, jParcel); } } else { env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, NULL); } if (env->ExceptionCheck()) { ALOGW("An exception occurred while notifying an event."); LOGW_EX(env); env->ExceptionClear(); } }
還記得fields.post_event保存的是什么嗎
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V");
就是java層MediaPlayer的postEventFromNative辦法,也就是說(shuō)如不雅播放掉足了,那么就經(jīng)由過(guò)程調(diào)用postEventFromNative辦法來(lái)告訴java層的MediaPlayer。
private static void postEventFromNative(Object mediaplayer_ref, int what, int arg1, int arg2, Object obj) { MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref).get(); if (mp == null) { return; } if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) { // this acquires the wakelock if needed, and sets the client side state mp.start(); } if (mp.mEventHandler != null) { Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj); mp.mEventHandler.sendMessage(m); } }
這個(gè)時(shí)光最終經(jīng)由過(guò)程mEventHandler處理,也就是在我們app過(guò)程中處理這個(gè)缺點(diǎn)。
寫(xiě)到這里,信賴你應(yīng)當(dāng)對(duì)java層和native層的交淮竽暌剮了導(dǎo)致的懂得。
相關(guān)案例查看更多
相關(guān)閱讀
- 網(wǎng)站建設(shè)專(zhuān)家
- 跳轉(zhuǎn)小程序
- 云南網(wǎng)站制作
- uniapp開(kāi)發(fā)小程序
- 昆明網(wǎng)絡(luò)公司
- 河南小程序制作
- 網(wǎng)站排名優(yōu)化
- 汽車(chē)報(bào)廢回收
- 紅河小程序開(kāi)發(fā)
- 網(wǎng)站建設(shè)公司地址
- 昆明網(wǎng)站設(shè)計(jì)
- 昆明小程序定制開(kāi)發(fā)
- 開(kāi)發(fā)框架
- 云南省城鄉(xiāng)建設(shè)廳網(wǎng)站
- 百度小程序開(kāi)發(fā)公司
- 云南小程序哪家好
- web前端
- 網(wǎng)絡(luò)公司聯(lián)系方式
- .net網(wǎng)站
- 云南小程序開(kāi)發(fā)公司推薦
- 小程序分銷(xiāo)商城
- 網(wǎng)絡(luò)公司哪家好
- 網(wǎng)站維護(hù)
- 開(kāi)發(fā)制作小程序
- 網(wǎng)絡(luò)公司排名
- 全國(guó)前十名小程序開(kāi)發(fā)公司
- 云南網(wǎng)站建設(shè)專(zhuān)家
- 網(wǎng)站建設(shè)公司哪家好
- 支付寶小程序被騙
- 網(wǎng)站優(yōu)化