Android JNI的使用浅析

介绍JNI的好文章:

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、静态代码块:

 

  1. static {  
  2.        System.loadLibrary("media_jni");  
  3.        native_init();  
  4.    }  


2、native_init的签名:

  1. 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的对应方法吧

 

  1. static void  
  2. android_media_MediaPlayer_native_init(JNIEnv *env)  
  3. {  
  4.     jclass clazz;  
  5.   
  6.     clazz = env->FindClass("android/media/MediaPlayer");  
  7.     if (clazz == NULL) {  
  8.         return;  
  9.     }  
  10.   
  11.     fields.context = env->GetFieldID(clazz, "mNativeContext", "I");  
  12.     if (fields.context == NULL) {  
  13.         return;  
  14.     }  
  15.   
  16.     fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",  
  17.                                                "(Ljava/lang/Object;IIILjava/lang/Object;)V");  
  18.     if (fields.post_event == NULL) {  
  19.         return;  
  20.     }  
  21.   
  22.     fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "I");  
  23.     if (fields.surface_texture == NULL) {  
  24.         return;  
  25.     }  
  26. }  


对 应上面的代码,如果你对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方法如下(截取部分)

 

  1. jint JNI_OnLoad(JavaVM* vm, void* reserved)  
  2. {  
  3.     JNIEnv* env = NULL;  
  4.     jint result = -1;  
  5.   
  6.     if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
  7.         ALOGE("ERROR: GetEnv failed\n");  
  8.         goto bail;  
  9.     }  
  10.     assert(env != NULL);  
  11.   
  12.     if (register_android_media_MediaPlayer(env) < 0) {  
  13.         ALOGE("ERROR: MediaPlayer native registration failed\n");  
  14.         goto bail;  
  15.     }  
  16.   
  17.     
  18.   
  19.     /* success -- return valid version number */  
  20.     result = JNI_VERSION_1_4;  
  21.   
  22. bail:  
  23.     return result;  
  24. }  

这里有一个方法叫做register_android_media_MediaPlayer,我们进入此方法,看看注册了什么

  1. static int register_android_media_MediaPlayer(JNIEnv *env)  
  2. {  
  3.     return AndroidRuntime::registerNativeMethods(env,  
  4.                 "android/media/MediaPlayer", gMethods, NELEM(gMethods));  
  5. }  


这里就是调用了AndroidRuntime提供的registerNativeMethods方法,这里涉及到一个gMethods的变量,它其实是一个结构体

  1. typedef struct {  
  2. const char* name;  
  3. const char* signature;  
  4. void* fnPtr;  
  5. } JNINativeMethod;  


name:就是在java层方法名称

signature:就是方法在签名

 

fnPtr:在jni层对应的函数名称

 

,那么我们找到native_init在gMethods对应的值吧

 

  1. static JNINativeMethod gMethods[] = {  
  2.     {  
  3.         "_setDataSource",  
  4.         "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",  
  5.         (void *)android_media_MediaPlayer_setDataSourceAndHeaders  
  6.     },  
  7.   
  8.     ....  
  9.     {"native_init",         "()V",                              (void *)android_media_MediaPlayer_native_init},  
  10.     ...  
  11. };  

接下来,我们看看AndroidRuntime中的registerNativeMethods做了什么吧

  1. /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,  
  2.     const char* className, const JNINativeMethod* gMethods, int numMethods)  
  3. {  
  4.     return jniRegisterNativeMethods(env, className, gMethods, numMethods);  
  5. }  



调用了jniRegisterNativeMethods

  1. extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,  
  2.     const JNINativeMethod* gMethods, int numMethods)  
  3. {  
  4.     JNIEnv* e = reinterpret_cast<JNIEnv*>(env);  
  5.   
  6.     ALOGV("Registering %s natives", className);  
  7.   
  8.     scoped_local_ref<jclass> c(env, findClass(env, className));  
  9.     if (c.get() == NULL) {  
  10.         ALOGE("Native registration unable to find class ‘%s‘, aborting", className);  
  11.         abort();  
  12.     }  
  13.   
  14.     if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {  
  15.         ALOGE("RegisterNatives failed for ‘%s‘, aborting", className);  
  16.         abort();  
  17.     }  
  18.   
  19.     return 0;  
  20. }  


最终调用了env的RegisterNativers完成了注册。

 

 

其实写到这里,我们已经知道了java层和jni是如何联系起来的,接下来我想说的是jni是如何将java层和native联系起来的,还是用MediaPlayer为例吧,我们进入MediaPlayer的构造函数。

 

  1. public MediaPlayer() {  
  2.   
  3.     Looper looper;  
  4.     if ((looper = Looper.myLooper()) != null) {  
  5.         mEventHandler = new EventHandler(this, looper);  
  6.     } else if ((looper = Looper.getMainLooper()) != null) {  
  7.         mEventHandler = new EventHandler(this, looper);  
  8.     } else {  
  9.         mEventHandler = null;  
  10.     }  
  11.   
  12.     /* Native setup requires a weak reference to our object. 
  13.      * It‘s easier to create it here than in C++. 
  14.      */  
  15.     native_setup(new WeakReference<MediaPlayer>(this));  
  16. }  


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

  1. static void  
  2. android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)  
  3. {  
  4.     ALOGV("native_setup");  
  5.     sp<MediaPlayer> mp = new MediaPlayer();  
  6.     if (mp == NULL) {  
  7.         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");  
  8.         return;  
  9.     }  
  10.   
  11.     // create new listener and give it to MediaPlayer  
  12.     sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);  
  13.     mp->setListener(listener);  
  14.   
  15.     // Stow our new C++ MediaPlayer in an opaque field in the Java object.  
  16.     setMediaPlayer(env, thiz, mp);  
  17. }  


这里创建了一个本地MediaPlayer对象,并且设置了listener,(如果做过播放器的同学应该知道这个listener应该知道干啥,不知道也没关系),最后调用了setMediaPlayer方法,这个才是我们需要关注的。

  1. static sp<MediaPlayer> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer>& player)  
  2. {  
  3.     Mutex::Autolock l(sLock);  
  4.     sp<MediaPlayer> old = (MediaPlayer*)env->GetIntField(thiz, fields.context);  
  5.     if (player.get()) {  
  6.         player->incStrong(thiz);  
  7.     }  
  8.     if (old != 0) {  
  9.         old->decStrong(thiz);  
  10.     }  
  11.     env->SetIntField(thiz, fields.context, (int)player.get());  
  12.     return old;  
  13. }  

其实就是先拿到fields.context的对应的值,还记得这个这个值是什么吗,不记得的可以回到上面看看

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


其实就是java层mNativeContext对应的值,就是将本地MediaPlayer的地址存放到mNativeContext中。

 

 

现在加入我们要播放一个本地Mp4视频,那么使用如下代码即可

 

  1. mediaPlayer.setDataSource("/mnt/sdcard/a.mp4");     
  2. mediaPlayer.setDisplay(surface1.getHolder());    
  3. mediaPlayer.prepare();    
  4. mediaPlayer.start();    


其实这里调用的 几个都是本地方法,这里我就是用prepare方法为例,讲解MediaPlaeyr.java和MediaPlayer.cpp的交互

当在java层调用prepare方法时,在jni层会调用如下方法

 

  1. static void  
  2. android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz)  
  3. {  
  4.     sp<MediaPlayer> mp = getMediaPlayer(env, thiz);  
  5.     if (mp == NULL ) {  
  6.         jniThrowException(env, "java/lang/IllegalStateException", NULL);  
  7.         return;  
  8.     }  
  9.   
  10.     // Handle the case where the display surface was set before the mp was  
  11.     // initialized. We try again to make it stick.  
  12.     sp<ISurfaceTexture> st = getVideoSurfaceTexture(env, thiz);  
  13.     mp->setVideoSurfaceTexture(st);  
  14.   
  15.     process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." );  
  16. }  

这里通过getMediaPlayer方法拿到本地的MediaPlayer对象,调用调用本地方法process_media_player_call,并将本地MediaPlayer调用parepare方法的结果传递给此方法。

  1. static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message)  
  2. {  
  3.     if (exception == NULL) {  // Don‘t throw exception. Instead, send an event.  
  4.         if (opStatus != (status_t) OK) {  
  5.             sp<MediaPlayer> mp = getMediaPlayer(env, thiz);  
  6.             if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0);  
  7.         }  
  8.     } else {  // Throw exception!  
  9.         if ( opStatus == (status_t) INVALID_OPERATION ) {  
  10.             jniThrowException(env, "java/lang/IllegalStateException", NULL);  
  11.         } else if ( opStatus == (status_t) PERMISSION_DENIED ) {  
  12.             jniThrowException(env, "java/lang/SecurityException", NULL);  
  13.         } else if ( opStatus != (status_t) OK ) {  
  14.             if (strlen(message) > 230) {  
  15.                // if the message is too long, don‘t bother displaying the status code  
  16.                jniThrowException( env, exception, message);  
  17.             } else {  
  18.                char msg[256];  
  19.                 // append the status code to the message  
  20.                sprintf(msg, "%s: status=0x%X", message, opStatus);  
  21.                jniThrowException( env, exception, msg);  
  22.             }  
  23.         }  
  24.     }  
  25. }  

在这个里面根据prepare返回的状态,如果exception==null 并且prepare执行失败,测试不抛异常,而是调用本地MediaPlayer的notify方法。

  1. void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj)  
  2. {  
  3.     ALOGV("message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2);  
  4.     bool send = true;  
  5.     bool locked = false;  
  6.   
  7.    ...  
  8.   
  9.     switch (msg) {  
  10.     case MEDIA_NOP: // interface test message  
  11.         break;  
  12.     case MEDIA_PREPARED:  
  13.         ALOGV("prepared");  
  14.         mCurrentState = MEDIA_PLAYER_PREPARED;  
  15.         if (mPrepareSync) {  
  16.             ALOGV("signal application thread");  
  17.             mPrepareSync = false;  
  18.             mPrepareStatus = NO_ERROR;  
  19.             mSignal.signal();  
  20.         }  
  21.         break;  
  22.     case MEDIA_PLAYBACK_COMPLETE:  
  23.         ALOGV("playback complete");  
  24.         if (mCurrentState == MEDIA_PLAYER_IDLE) {  
  25.             ALOGE("playback complete in idle state");  
  26.         }  
  27.         if (!mLoop) {  
  28.             mCurrentState = MEDIA_PLAYER_PLAYBACK_COMPLETE;  
  29.         }  
  30.         break;  
  31.     case MEDIA_ERROR:  
  32.         // Always log errors.  
  33.         // ext1: Media framework error code.  
  34.         // ext2: Implementation dependant error code.  
  35.         ALOGE("error (%d, %d)", ext1, ext2);  
  36.         mCurrentState = MEDIA_PLAYER_STATE_ERROR;  
  37.         if (mPrepareSync)  
  38.         {  
  39.             ALOGV("signal application thread");  
  40.             mPrepareSync = false;  
  41.             mPrepareStatus = ext1;  
  42.             mSignal.signal();  
  43.             send = false;  
  44.         }  
  45.         break;  
  46.     case MEDIA_INFO:  
  47.         // ext1: Media framework error code.  
  48.         // ext2: Implementation dependant error code.  
  49.         if (ext1 != MEDIA_INFO_VIDEO_TRACK_LAGGING) {  
  50.             ALOGW("info/warning (%d, %d)", ext1, ext2);  
  51.         }  
  52.         break;  
  53.     case MEDIA_SEEK_COMPLETE:  
  54.         ALOGV("Received seek complete");  
  55.         if (mSeekPosition != mCurrentPosition) {  
  56.             ALOGV("Executing queued seekTo(%d)", mSeekPosition);  
  57.             mSeekPosition = -1;  
  58.             seekTo_l(mCurrentPosition);  
  59.         }  
  60.         else {  
  61.             ALOGV("All seeks complete - return to regularly scheduled program");  
  62.             mCurrentPosition = mSeekPosition = -1;  
  63.         }  
  64.         break;  
  65.     case MEDIA_BUFFERING_UPDATE:  
  66.         ALOGV("buffering %d", ext1);  
  67.         break;  
  68.     case MEDIA_SET_VIDEO_SIZE:  
  69.         ALOGV("New video size %d x %d", ext1, ext2);  
  70.         mVideoWidth = ext1;  
  71.         mVideoHeight = ext2;  
  72.         break;  
  73.     case MEDIA_TIMED_TEXT:  
  74.         ALOGV("Received timed text message");  
  75.         break;  
  76.     default:  
  77.         ALOGV("unrecognized message: (%d, %d, %d)", msg, ext1, ext2);  
  78.         break;  
  79.     }  
  80.   
  81.     sp<MediaPlayerListener> listener = mListener;  
  82.     if (locked) mLock.unlock();  
  83.   
  84.     // this prevents re-entrant calls into client code  
  85.     if ((listener != 0) && send) {  
  86.         Mutex::Autolock _l(mNotifyLock);  
  87.         ALOGV("callback application");  
  88.         listener->notify(msg, ext1, ext2, obj);  
  89.         ALOGV("back from callback");  
  90.     }  
  91. }  


做过播放器的同学应该对上面几个消息都不陌生吧,由于刚才调用prepare方法失败了,所以这里应该执行MEDIA_ERROR分支,最后调用listener的notify代码,这个listener就是在native_setup中设置的

 

  1. void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2, const Parcel *obj)  
  2. {  
  3.     JNIEnv *env = AndroidRuntime::getJNIEnv();  
  4.     if (obj && obj->dataSize() > 0) {  
  5.         jobject jParcel = createJavaParcelObject(env);  
  6.         if (jParcel != NULL) {  
  7.             Parcel* nativeParcel = parcelForJavaObject(env, jParcel);  
  8.             nativeParcel->setData(obj->data(), obj->dataSize());  
  9.             env->CallStaticVoidMethod(mClass, fields.post_event, mObject,  
  10.                     msg, ext1, ext2, jParcel);  
  11.         }  
  12.     } else {  
  13.         env->CallStaticVoidMethod(mClass, fields.post_event, mObject,  
  14.                 msg, ext1, ext2, NULL);  
  15.     }  
  16.     if (env->ExceptionCheck()) {  
  17.         ALOGW("An exception occurred while notifying an event.");  
  18.         LOGW_EX(env);  
  19.         env->ExceptionClear();  
  20.     }  
  21. }  



还记得fields.post_event保存的是什么吗

 

  1. fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",  
  2.                                                "(Ljava/lang/Object;IIILjava/lang/Object;)V");  


就是java层MediaPlayer的postEventFromNative方法,也就是说如果播放出错了,那么就通过调用postEventFromNative方法来告诉java层的MediaPlayer。

  1. private static void postEventFromNative(Object mediaplayer_ref,  
  2.                                         int what, int arg1, int arg2, Object obj)  
  3. {  
  4.     MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref).get();  
  5.     if (mp == null) {  
  6.         return;  
  7.     }  
  8.   
  9.     if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) {  
  10.         // this acquires the wakelock if needed, and sets the client side state  
  11.         mp.start();  
  12.     }  
  13.     if (mp.mEventHandler != null) {  
  14.         Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);  
  15.         mp.mEventHandler.sendMessage(m);  
  16.     }  
  17. }  


这个时间最终通过mEventHandler处理,也就是在我们app进程中处理这个错误。

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。