Android framework層JNI的使用淺析

來源:互聯網
上載者:User

Android framework層JNI的使用淺析

 

JNI技術對於多java開發的朋友相信並不陌生,即(java native interface),本地調用介面,主要功能有以下兩點:

1、java層調用C/C++層代碼

2、C/C++層調用java層代碼

 

可能有些人會覺得jni技術破壞了Java語言的跨平台性,有這種想法可能是因為你對java理解得還不夠深,如果你看看jdk源碼,你會發現在jdk裡面大量使用了jni技術,而且java虛擬機器就是用本地語言寫的,所以導致jvm並不能跨平台性,所以說java的跨平台性並不是100%的跨平台的。相反你應該看到使用Jni的優勢:

1、因為C/C++語言本來機比java語言誕生早,所以很多庫代碼都是使用C/C++寫的,有了Jni我們就可以直接使用了,不用重複造輪子。

2、不可否認,C/C++執行效率比java 高,對於一些對效率有要求的功能,必須使用C/C++.

 

由於打算研究Android 中java層和native層是如何串連起來的,所以想研究一下Android中的jni技術(在閱讀之前,最好瞭解jni中的基本知識,如jni中資料類型,簽名格式,不然看起來可能有些吃力),由於工作和MediaPlayer有關,這裡就使用MediaPlayer為例吧。

 

當我們的app要播放視頻的時候,我們使用的是java層的MediaPlayer類,我們進入到MediaPlayer.java看看(提醒:我這裡使用的是源碼4.1)

主要注意的有兩點:

1、靜態代碼塊:

 

 static {        System.loadLibrary(media_jni);        native_init();    }

2、native_init的簽名:

 

 

private static native final void native_init();

看到靜態代碼塊後,我們可以知道MediaPlayer對應的jni層代碼在Media_jni.so庫中

 

 

本地層對應的so庫是libmedia.so,所以MediaPlayer.java通過Media_jni.so和MediaPlayer.cpp(libmedia.so)進行互動

 

下面我們就深入到細節吧。不過在深入細節前,我先要告訴你一個規則,在Android中,通常java層類和jni層類的名字有如下關係,拿MediaPlayer為例,java層叫android.media.MediaPlayer.java,那麼jni層叫做android_media_MediaPlayer.cpp

 

由於native_init是一個本地方法,那麼我們就到android_media_MediaPlayer.cpp找到native_init的對應方法吧

 

static voidandroid_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;    }}

對應上面的代碼,如果你對java中的反射理解得很透徹的話,其實很好理解,首先找到java層的MediaPlayer的Class對象,jclass是java層Class在native層的代碼,然後分別儲存mNaviceContext欄位,postEventFromNative方法,mNativeSurfaceTexture欄位。

 

 

其實這裡我最想說明的是另外一個問題,就是MediaPlayer中的native_init方法時如何跟android_media_MediaPlayer.cpp中的android_media_MediaPlayer_native_init對應起來的,因為我們知道如果使用javah自動產生的標頭檔,那麼在jni層的名字應該是java_android_media_MediaPlayer_native_linit。其實這裡涉及到一個動態註冊的過程。

 

其實在java層代用System.loadLibrary成功後,就會調用jni檔案中的JNI_onLoad方法,android_media_MediaPlayer.cpp中的JNI_onLoad方法如下(截取部分)

 

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);        goto bail;    }    assert(env != NULL);    if (register_android_media_MediaPlayer(env) < 0) {        ALOGE(ERROR: MediaPlayer native registration failed);        goto bail;    }      /* success -- return valid version number */    result = JNI_VERSION_1_4;bail:    return result;}
這裡有一個方法叫做register_android_media_MediaPlayer,我們進入此方法,看看註冊了什麼

 

 

static int register_android_media_MediaPlayer(JNIEnv *env){    return AndroidRuntime::registerNativeMethods(env,                android/media/MediaPlayer, gMethods, NELEM(gMethods));}

這裡就是調用了AndroidRuntime提供的registerNativeMethods方法,這裡涉及到一個gMethods的變數,它其實是一個結構體

 

 

typedef struct {const char* name;const char* signature;void* fnPtr;} JNINativeMethod;

name:就是在java層方法名稱

 

signature:就是方法在簽名

fnPtr:在jni層對應的函數名稱

,那麼我們找到native_init在gMethods對應的值吧

 

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},    ...};
接下來,我們看看AndroidRuntime中的registerNativeMethods做了什麼吧

 

 

/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,    const char* className, const JNINativeMethod* gMethods, int numMethods){    return jniRegisterNativeMethods(env, className, gMethods, numMethods);}


調用了jniRegisterNativeMethods

 

 

extern C int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,    const JNINativeMethod* gMethods, int numMethods){    JNIEnv* e = reinterpret_cast(env);    ALOGV(Registering %s natives, className);    scoped_local_ref 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;}

最終調用了env的RegisterNativers完成了註冊。

 

 

其實寫到這裡,我們已經知道了java層和jni是如何聯絡起來的,接下來我想說的是jni是如何將java層和native聯絡起來的,還是用MediaPlayer為例吧,我們進入MediaPlayer的建構函式。

 

    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(this));    }

這裡建立了一個mEventHandler對象,並調用了native_setup方法,我們進入到android_media_MediaPlayer.cpp的對應方法看看

 

 

static voidandroid_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this){    ALOGV(native_setup);    sp mp = new MediaPlayer();    if (mp == NULL) {        jniThrowException(env, java/lang/RuntimeException, Out of memory);        return;    }    // create new listener and give it to MediaPlayer    sp 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);}

這裡建立了一個本地MediaPlayer對象,並且設定了listener,(如果做過播放器的同學應該知道這個listener應該知道幹啥,不知道也沒關係),最後調用了setMediaPlayer方法,這個才是我們需要關注的。

 

 

static sp setMediaPlayer(JNIEnv* env, jobject thiz, const sp& player){    Mutex::Autolock l(sLock);    sp 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;}
其實就是先拿到fields.context的對應的值,還記得這個這個值是什麼嗎,不記得的可以回到上面看看

 

 

fields.context = env->GetFieldID(clazz, mNativeContext, I);

其實就是java層mNativeContext對應的值,就是將本地MediaPlayer的地址存放到mNativeContext中。

 

 

現在加入我們要播放一個本地Mp4視頻,那麼使用如下代碼即可

 

mediaPlayer.setDataSource(/mnt/sdcard/a.mp4);   mediaPlayer.setDisplay(surface1.getHolder());  mediaPlayer.prepare();  mediaPlayer.start();  

其實這裡調用的 幾個都是本地方法,這裡我就是用prepare方法為例,講解MediaPlaeyr.java和MediaPlayer.cpp的互動

 

當在java層調用prepare方法時,在jni層會調用如下方法

 

static voidandroid_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz){    sp 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 st = getVideoSurfaceTexture(env, thiz);    mp->setVideoSurfaceTexture(st);    process_media_player_call( env, thiz, mp->prepare(), java/io/IOException, Prepare failed. );}
這裡通過getMediaPlayer方法拿到本地的MediaPlayer對象,調用調用本地方法process_media_player_call,並將本地MediaPlayer調用parepare方法的結果傳遞給此方法。

 

 

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 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);            }        }    }}
在這個裡面根據prepare返回的狀態,如果exception==null 並且prepare執行失敗,測試不拋異常,而是調用本地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 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);    }}


 

做過播放器的同學應該對上面幾個訊息都不陌生吧,由於剛才調用prepare方法失敗了,所以這裡應該執行MEDIA_ERROR分支,最後調用listener的notify代碼,這個listener就是在native_setup中設定的

 

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方法,也就是說如果播放出錯了,那麼就通過調用postEventFromNative方法來告訴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);        }    }

這個時間最終通過mEventHandler處理,也就是在我們app進程中處理這個錯誤。

 

 

寫到這裡,相信你應該對java層和native層的互動有了導致的瞭解。


 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.