尊重原创:http://blog.csdn.net/yuanzeyao/article/details/42418977

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为例吧。

下面给出一张图,通过此图,我们简要说明一下jni是如何连接Java层和本地层的。


当我们的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\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;}
这里有一个方法叫做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层的交互有了导致的了解。



更多相关文章

  1. Android(安卓)基于Retrofit+Rxjava搭建的简单易用的网络架构
  2. 【Android】Could not find XXX.apk!的解决方法
  3. 可以在Android上发定时短信
  4. Android(安卓)TabActivity中的子Tab Activity 无法正常 bindServ
  5. Android(安卓)8.1 关机充电动画(二)Uboot模式
  6. android 支持webrtc
  7. Android(安卓)4.4 webview 架构
  8. 设置背景图时防止图片拉伸的解决方法
  9. APK的安装过程及原理详解

随机推荐

  1. 记一次MySQL更新语句update的踩坑
  2. MySQL的Flush-List和脏页的落盘机制
  3. Sysbench对Mysql进行基准测试过程解析
  4. 解决myBatis中删除条件的拼接问题
  5. Mac下mysql 8.0.22 找回密码的方法
  6. mysql 8.0.22 winx64安装配置图文教程
  7. mysql 8.0.22.0 下载安装配置方法图文教
  8. CenOS6.7下mysql 8.0.22 安装配置方法图
  9. 推荐几款MySQL相关工具
  10. Windows10下mysql 8.0.22 安装配置方法图