Analysis on the use of jni In the Android framework layer
JNI technology is no stranger to many java developers, namely (java native interface). It calls interfaces locally and has the following functions:
1. java layer calls C/C ++ Layer Code
2. C/C ++ layer calls java Layer Code
Some people may think that the jni technology breaks the cross-platform nature of the Java language. This may be because you do not have a deep understanding of java. If you look at the jdk source code, you will find that jni technology is widely used in jdk, and Java virtual machines are written in local languages. As a result, jvm cannot be cross-platform, therefore, java's cross-platform nature is not 100% cross-platform. On the contrary, you should see the advantages of using Jni:
1. Because the C/C ++ language was born earlier than the java language, many library codes are written in C/C ++, with Jni, we can use it directly without repeating the wheel.
2. It is undeniable that the execution efficiency of C/C ++ is higher than that of java. For some functions that require efficiency, C/C ++ must be used.
I plan to study how the java layer and native layer are connected in Android, so I want to study the jni technology in Android (before reading this article, I 'd better understand the basic knowledge in jni, for example, in jni, the Data Type and signature format may seem a little difficult.) because the work is related to MediaPlayer, use MediaPlayer as an example.
When our app wants to play a video, we use the java-layer MediaPlayer class. Let's go to MediaPlayer. java (Note: I am using source code 4.1)
Note the following two points:
1. Static code block:
static { System.loadLibrary(media_jni); native_init(); }
2. native_init signature:
private static native final void native_init();
After seeing the static code block, we can know that the jni Layer Code corresponding to the MediaPlayer is in the Media_jni.so library.
The so library corresponding to the local layer is libmedia. so MediaPlayer. java interacts with Media_jni.so through MediaPlayer. cpp (libmedia. so ).
Let's go into the details below. However, before going into the details, I want to tell you a rule. In Android, the names of java-layer classes and jni-layer classes are usually related as follows. Take MediaPlayer as an example, the java layer is called android. media. mediaPlayer. java, the jni layer is called android_media_MediaPlayer.cpp.
Since native_init is a local method, we will find the corresponding method of native_init in android_media_MediaPlayer.cpp.
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; }}
Corresponding to the above Code, if you have a thorough understanding of reflection in java, it is actually a good understanding, first find the Class Object of the java-layer MediaPlayer, jclass is the code of the java-layer Class at the native layer, and then stores the mNaviceContext field, postEventFromNative method, and mNativeSurfaceTexture fields respectively.
In fact, what I want to explain most is how the native_init method in MediaPlayer corresponds to android_mediaplayer_native_init in android_media_MediaPlayer_native_init, because we know that if we use the header file automatically generated by javah, the name of the jni layer should be java_android_media_MediaPlayer_native_linit. In fact, this involves a dynamic registration process.
In fact, after successfully replacing System. loadLibrary with the java layer, the JNI_onLoad method in the jni file will be called. The JNI_onLoad method in android_media_MediaPlayer.cpp is as follows (Part truncation)
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;}
Here is a method called register_android_media_MediaPlayer. Let's go to this method and see what is registered.
static int register_android_media_MediaPlayer(JNIEnv *env){ return AndroidRuntime::registerNativeMethods(env, android/media/MediaPlayer, gMethods, NELEM(gMethods));}
The registerNativeMethods method provided by AndroidRuntime is called. A gMethods variable is involved here. It is actually a struct.
typedef struct {const char* name;const char* signature;void* fnPtr;} JNINativeMethod;
Name: indicates the method name at the java layer.
Signature: The method is in the signature
FnPtr: name of the function corresponding to the jni Layer
So we can find the value of native_init in 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}, ...};
Next, let's take a look at what registerNativeMethods has done in AndroidRuntime.
/*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;}
Finally, RegisterNativers of env is called to complete registration.
As a matter of fact, we already know how the java layer is connected with jni. Next I want to talk about how jni associates the java layer with native, take MediaPlayer as an example. Let's enter the MediaPlayer constructor.
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)); }
Here we created a mEventHandler object and called the native_setup method. Let's go to the corresponding method of 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);}
A local MediaPlayer object is created and a listener is set. (If a player is a player, you should know what the listener is doing. It doesn't matter if you do not know what it is.) The setMediaPlayer method is called, this is what we need to pay attention.
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;}
In fact, we get the corresponding value of fields. context first. Do you still remember this value?
fields.context = env->GetFieldID(clazz, mNativeContext, I);
It is actually the value corresponding to mNativeContext in the java layer, that is, the address of the local MediaPlayer is stored in mNativeContext.
Now, to play a local Mp4 video, use the following code:
mediaPlayer.setDataSource(/mnt/sdcard/a.mp4); mediaPlayer.setDisplay(surface1.getHolder()); mediaPlayer.prepare(); mediaPlayer.start();
In fact, some of the calls here are local methods. Here I use the prepare method as an example to explain the interaction between MediaPlaeyr. java and MediaPlayer. cpp.
When the prepare method is called at the java layer, the following method is called at the jni layer:
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. );}
Here, the getMediaPlayer method is used to obtain the local MediaPlayer object, call the local method process_media_player_call, and pass the result of the local MediaPlayer calling the parepare method to this method.
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); } } }}
In this example, the status returned by prepare is returned. If exception = null and prepare fails to be executed, the test does not throw an exception, but calls the notify method of the local MediaPlayer.
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); }}
Those who have worked on the player should be familiar with the above messages. As the call to the prepare method failed, the MEDIA_ERROR branch should be executed here, and finally the mongoy code of listener should be called, this listener is set in 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(); }}
Do you remember what fields. post_event saved?
fields.post_event = env->GetStaticMethodID(clazz, postEventFromNative, (Ljava/lang/Object;IIILjava/lang/Object;)V);
It is the postEventFromNative method of the java-layer MediaPlayer. That is to say, if an error occurs during playback, the java-layer MediaPlayer is notified by calling the postEventFromNative method.
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); } }
This time is finally handled by mEventHandler, that is, the error is handled in our app process.
Here, I believe you should have an understanding of the interaction between the java layer and the native layer.