JNI技術對於多java開發的朋友相信並不陌生,即(java native interface),本地調用接口,主要功能有如下兩點:
一、java層調用C/C++層代碼
二、C/C++層調用java層代碼
可能有些人會以爲jni技術破壞了Java語言的跨平臺性,有這種想法多是由於你對java理解得還不夠深,若是你看看jdk源碼,你會發如今jdk裏面大量使用了jni技術,並且java虛擬機就是用本地語言寫的,因此致使jvm並不能跨平臺性,因此說java的跨平臺性並非100%的跨平臺的。相反你應該看到使用Jni的優點:
一、由於C/C++語言原本機比java語言誕生早,因此不少庫代碼都是使用C/C++寫的,有了Jni咱們就能夠直接使用了,不用重複造輪子。
二、不能否認,C/C++執行效率比java 高,對於一些對效率有要求的功能,必須使用C/C++.
因爲打算研究Android 中java層和native層是如何鏈接起來的,因此想研究一下Android中的jni技術(在閱讀以前,最好了解jni中的基本知識,如jni中數據類型,簽名格式,否則看起來可能有些吃力),因爲工做和MediaPlayer有關,這裏就使用MediaPlayer爲例吧。
當咱們的app要播放視頻的時候,咱們使用的是java層的MediaPlayer類,咱們進入到MediaPlayer.java看看(提醒:我這裏使用的是源碼4.1)
主要注意的有兩點:
一、靜態代碼塊:
static {
System.loadLibrary(media_jni);
native_init();
}
二、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
20
extern C int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
JNIEnv* e = reinterpret_cast<jnienv*>(env);
ALOGV(Registering %s natives, className);
scoped_local_ref<jclass> 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;
}</jclass></jnienv*>
最終調用了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<mediaplayer>(this));
}</mediaplayer>
這裏建立了一個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<mediaplayer> mp = new MediaPlayer();
if (mp == NULL) {
jniThrowException(env, java/lang/RuntimeException, Out of memory);
return;
}
// create new listener and give it to MediaPlayer
sp<jnimediaplayerlistener> 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);
}</jnimediaplayerlistener></mediaplayer>
這裏建立了一個本地MediaPlayer對象,而且設置了listener,(若是作過播放器的同窗應該知道這個listener應該知道幹啥,不知道也不要緊),最後調用了setMediaPlayer方法,這個纔是咱們須要關注的。
static sp<mediaplayer> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<mediaplayer>& player)
{
Mutex::Autolock l(sLock);
sp<mediaplayer> 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;
}</mediaplayer></mediaplayer></mediaplayer>
其實就是先拿到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<mediaplayer> 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<isurfacetexture> st = getVideoSurfaceTexture(env, thiz);
mp->setVideoSurfaceTexture(st);
process_media_player_call( env, thiz, mp->prepare(), java/io/IOException, Prepare failed. );
}</isurfacetexture></mediaplayer>
這裏經過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<mediaplayer> 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);
}
}
}
}</mediaplayer>
在這個裏面根據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<mediaplayerlistener> 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);
}
}</mediaplayerlistener>
作過播放器的同窗應該對上面幾個消息都不陌生吧,因爲剛纔調用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