JNI

Posted Gaomeng814

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 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;
    }
}

对应上面的代码,如果你对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


20
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;
}</jclass></jnienv*>

最终调用了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<mediaplayer>(this));
}</mediaplayer>

这里创建了一个mEventHandler对象,并调用了native_setup方法,我们进入到android_media_MediaPlayer.cpp的对应方法看看

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);
}</jnimediaplayerlistener></mediaplayer>

这里创建了一个本地MediaPlayer对象,并且设置了listener,(如果做过播放器的同学应该知道这个listener应该知道干啥,不知道也没关系),最后调用了setMediaPlayer方法,这个才是我们需要关注的。
 
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;
}</mediaplayer></mediaplayer></mediaplayer>
其实就是先拿到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 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. );
}</isurfacetexture></mediaplayer>
这里通过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<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);
            }
        }
    }
}</mediaplayer>
在这个里面根据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<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);
    }
}</mediaplayerlistener>


 

做过播放器的同学应该对上面几个消息都不陌生吧,由于刚才调用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进程中处理这个错误。

以上是关于JNI的主要内容,如果未能解决你的问题,请参考以下文章

你应该了解的JNI知识——Java与JNI互相调用

Android JNI编程—JNI基础

Android JNI 学习:JNI 简介

Android JNI——NDK与JNI基础

Android JNI 学习:JNI 设计概述

转Android JNI编程—JNI基础