Analysis of the Use of JNI in Android framework Layer

Keywords: MediaPlayer Java Android JDK

Reprint address: http://blog.csdn.net/yuanzeyao/article/details/42418977

JNI technology for many Java Developed friends believe that it is not unfamiliar, that is( Java native interface), the local call interface, has the following two main functions:

1. Calling C/C++ Layer Code in java Layer

2. C/C++ Layer Calls java Layer Code


Some people may think that JNI technology destroys the cross-platform nature of Java language. Maybe this idea is because you don't understand java well enough. If you look at the source code of jdk, you will find that JNI technology is widely used in jdk, and the Java virtual machine is written in local language, so jvm can't cross-platform. So the cross-platform nature of Java is not 100% cross-platform. Instead, you should see the advantages of using Jni:

1. Because C/C++ language was born earlier than java language, so many library codes are written in C/C++. With Jni, we can use it directly without repeating the wheel.

2. It is undeniable that C/C++ is more efficient than java. For some functions that require efficiency, C/C++ must be used.


As a result of intentional research Android How the java layer and native layer are connected, so I want to study the JNI Technology in Android (before reading, it's better to know the basic knowledge in jni, such as data type in jni, signature format, otherwise it may seem a little laborious). Because the work is related to MediaPlayer, let's use MediaPlayer as an example. .

Here's a diagram that shows how jni connects the Java layer to the local layer.


When our app wants to play video, we use the MediaPlayer class in the java layer. Let's go to MediaPlayer.java and have a look. (Reminder: I use source code 4.1 here)

There are two main points to be paid attention to:

1. Static code block:

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

2. Signature of native_init:

  1. private static native final void native_init();  

After seeing the static code block, we can see that the JNI layer code corresponding to MediaPlayer is in the Media_jni.so library.


The so library corresponding to the local layer is libmedia.so, so MediaPlayer.java interacts with Media_jni.so and MediaPlayer.cpp(libmedia.so).


Let's go into the details. But before going into details, let me tell you a rule. In Android, the names of the Java layer and jni layer are usually related as follows. Take MediaPlayer as an example. The Java layer is called android.media.MediaPlayer.java, and the jni layer is called android_media_MediaPlayer.cpp.


Since native_init is a local method, let's go to android_media_MediaPlayer.cpp and find the corresponding method for 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. }  

Corresponding to the above code, if you have a thorough understanding of reflection in java, you can actually understand it very well. First, find the lass object of MediaPlayer in the Java layer. jclass is the code of Java layer Class in the native layer. Then save the mNaviceContext field, postEventFromNative method and mNativeFaceTexture field respectively.


In fact, what I want to illustrate here is another question, how does the native_init method in MediaPlayer correspond to android_media_MediaPlayer.cpp's android_media_MediaPlayer_native_init, because we know that if we use the header file generated automatically by javah, the name in the jni layer should be java_and Roid_media_MediaPlayer_native_linit. In fact, this involves a dynamic registration process.


In fact, when the java layer succeeds in replacing System. load Library, the JNI_onLoad method in the jni file will be called, and the JNI_onLoad method in android_media_MediaPlayer.cpp will be as follows (interception part)

  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. }  
Here's a method called register_android_media_MediaPlayer. Let's go into this method and see what's registered.

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

Here's a call to the registerNativeMethods method provided by Android Runtime, which involves a variable of gMethods, which is actually a structure.

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

Name: is the method name at the java layer

signature: means that the method is signing

fnPtr: The name of the function corresponding to the jni layer

So let's find the value of native_init in 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. };  
Next, let's look at what registerNative Methods in Android Runtime has done.

  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. }  


Called jniRegister Native Methods

  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. }  

Eventually, the Register Nativers of env was invoked to complete the registration.


As a matter of fact, we already know how the java layer and jni are connected. Next, I want to talk about how jni connects the java layer and native. Let's take MediaPlayer as an example. Let's go into the constructor of 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. }  

Here we create an mEventHandler object and call the native_setup method. Let's go to the corresponding method of 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. }  

Here we create a local MediaPlayer object and set up a listener. (If a former player knows what the listener should know, it doesn't matter.) Finally, we call the setMediaPlayer method, which is what we need to pay attention to.

  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. }  
In fact, we get the corresponding value of fields.context first. Do you remember what this value is? If you don't remember, you can go back and see it.

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

In fact, the value corresponding to the mNativeContext in the java layer is to store the address of the local MediaPlayer in the mNativeContext.


Now join us to play a local Mp4 video, then use the following code

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

In fact, several of the methods invoked here are local methods. Here I use the prepare d method as an example to explain the interaction between MediaPlaeyr.java and MediaPlayer.cpp.

When the prepare method is invoked at the java layer, the following method is invoked at the jni layer

  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. }  
Here we get the local MediaPlayer object through the getMediaPlayer method, call the local method process_media_player_call, and pass the result of the local MediaPlayer calling the pare method to this method.

  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. }  
In this case, according to the status returned by prepares, if exception==null and prepares fail to execute, the test does not throw an exception, but calls the notify method of the local MediaPlayer.

  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. }  


Students who have played the player should be familiar with the above several messages. Because the call to prepare method failed just now, the MEDIA_ERROR branch should be executed here. Finally, the notify code of listener should be called. This listener is set up in 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. }  


Remember what fields.post_event saves?

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

It's the postEventFromNative method of the java layer MediaPlayer, that is, if the playback is wrong, then call the postEventFromNative method to tell the java layer 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. }  

This time is finally handled by mEventHandler, which is to handle this error in our app process.


As I write here, I believe you should have a good understanding of the interaction between the java layer and the native layer.



Posted by dannau on Sat, 13 Jul 2019 11:31:24 -0700