• Android JNI 交互浅析(转自:http://www.2cto.com/kf/201501/367222.html)


    由于打算研究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

    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 void android_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 void android_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层的交互有了导致的了解。

  • 相关阅读:
    Java调度实现
    关于《报表》的实际运用案例
    mybaits错误解决:There is no getter for property named 'parentId ' in class 'java.lang.String'
    Java Eclipse进行断点调试
    切割时间工具类
    JavaWeb开发技术基础概念回顾篇
    解决无线网络连接出现黄色感叹号---win10
    登录界面Demo
    MD5加密Demo
    java.lang.NullPointerException&com.cb.action.LoginAction.execute(LoginAction.java:48)
  • 原文地址:https://www.cnblogs.com/shaoke123/p/4502735.html
Copyright © 2020-2023  润新知