android4.0 MediaPlayer的notify監聽機制的全面剖析

 本文將貫穿android的整個體系,深入剖析MediaPlayer的notify監聽機制的前世今生。
歡迎來到本博客,此爲原創文章,轉載請註明出處http://fangli.blog.51cto.com/
本文主要闡述內容介紹:
一.java應用層上Listener監聽機制的使用方式
二.java框架層中MediaPlayer類的notify機制的分析
三.jni層中java和c++代碼中notify機制如何交互
四.c++層的MediaPlayer類中notify機制的分析
五.服務端MediaPlayerService中notify機制的分析
六.具體子服務MediaPlayer的notify機制的分析
七.子服務MediaPlayer的實例Nuplayer中notify機制的分析
 
 
一.java應用層上Listener監聽機制的使用方式
關於如何使用MediaPlayer,可以參考android 的sdk文檔,寫的很詳細。
這裏簡要介紹一下它怎麼創建使用。
第一種方法,使用MediaPlayer.create()這個靜態方法來創建,然後哦啓動它。
 
MediaPlayer mp = MediaPlayer.create(context, R.raw.sound_file_1);
    mp.start();
第二種方法,是new一個MediaPlayer的對象,通過setDataSource來設置播放的內容,接着調用prepare方法對真正的打開數據源準備播放。
 
MediaPlayer mp = new MediaPlayer();
    mp.setDataSource(PATH_TO_FILE);
    mp.prepare();
    mp.start();
使用上面的兩種方法創建好MediaPlayer,接着就是通過start方法來啓動對媒體的播放。
我們先來看一下MediaPlayer的狀態圖
從狀態圖可以看出在創建到播放的過程中可能會出現各種狀態和問題,比如,無法找到播放文件,播放數據錯誤,播放的準備進度等,在這種情況下,android爲我們提供了一種事件的通知機制,來適時的給用戶各種狀態和錯誤的反饋。
android通過設置事件監聽來對這些狀態和錯誤來處理反饋。
比較常見的事件監聽包括:
1.BufferingUpdate
 
public void setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener)
設置一個監聽當播放網絡的數據流的buffer發生變化的時候發出通知。
 
它的參數是一個接口
 
    public interface OnBufferingUpdateListener
    {
        void onBufferingUpdate(MediaPlayer mp, int percent);
    }
 
mp是調用這個接口的MediaPlayer對象,percent是數據緩存的百分比。
 
2.Completion
public void setOnCompletionListener (MediaPlayer.OnCompletionListener listener)
設置一個監聽,當一個媒體是播放完畢的時候發出通知。
    public interface OnCompletionListener
    {
        void onCompletion(MediaPlayer mp);
    }
mp是調用這個接口的MediaPlayer對象
 

3.Error
public void setOnErrorListener (MediaPlayer.OnErrorListener listener)
設置一個監聽,當使用異步操作出現錯誤時發送的通知。
 
使用prepare方法是啓動同步方式,當prepare方法需要全部執行完後才能返回。
使用prepareAsync方法是啓動異步方式,調用prepareAsync方法後直接返回,後臺用在另一個線程中完成對prepare的準備工作。
這個error的錯誤監聽的通知就是在異步的方式下才會發出的。
    public interface OnErrorListener
    {
        boolean onError(MediaPlayer mp, int what, int extra);
    }
mp是調用這個接口的MediaPlayer對象,what是錯誤的類型,extra是針對what錯誤的額外的代碼
錯誤類型包括了:
MEDIA_ERROR_UNKNOWN:未指定的錯誤,一般沒有使用。
MEDIA_ERROR_SERVER_DIED:媒體的後臺服務掛了。
MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:播放發生錯誤,或者視頻本身有問題,例如視頻的索引不在文件的開始部分。
 
 
返回true表示有錯誤發生,當返回false,或者沒有調用這個監聽時,將會調用OnCompletionListener。


4.Info
public void setOnInfoListener (MediaPlayer.OnInfoListener listener)
 
設置一個監聽,當有信息或者警告的時候發出通知。
    public interface OnInfoListener
    {
        boolean onInfo(MediaPlayer mp, int what, int extra);
    }
mp是調用這個接口的MediaPlayer對象,what是消息的類型,extra是針對what消息的額外的代碼
我們來看看都有哪些的消息
MEDIA_INFO_UNKNOWN:未指點消息,一般很少使用。從android源碼中看,沒人使用。
MEDIA_INFO_VIDEO_TRACK_LAGGING:從它的英文解釋來看,我覺得是解碼器認爲這個視頻太負責了不能足夠快速的解碼出視頻幀的時候發出的,當收到這個消息的時候我們可以讓應用程序只播放音頻而不去播放視頻會更好點。這個是我個人觀點。
MEDIA_INFO_BUFFERING_START:通知你要暫停一下播放,去進行一下buffer緩存,以便更好的播放。
MEDIA_INFO_BUFFERING_END:這條消息和上面那個MEDIA_INFO_BUFFERING_START上相對的,當buffer的緩存夠的時候,通知你一下,你就可以接着去播放視頻了。
MEDIA_INFO_METADATA_UPDATE:當有一組新的元數據有效的時候發出的通知。,什麼是元數據啊,我不知道,以後知道了會更新。
MEDIA_INFO_BAD_INTERLEAVING:一個正常的媒體文件中,音頻數據和視頻數據因該是交錯依次排列的,這樣這個媒體才能被正常的播放,但是如果音頻數據和視頻數據沒有正常交錯排列,那裏就會發出這個消息。
MEDIA_INFO_NOT_SEEKABLE:媒體不能被定位的時候發出的消息,這個時候可能這個媒體是在線流
 
5.Prepare
public void setOnPreparedListener (MediaPlayer.OnPreparedListener listener)
設置一個監聽,當準備完成的時候發出通知
 
    public interface OnPreparedListener
    {
        void onPrepared(MediaPlayer mp);
    }
mp是調用這個接口的MediaPlayer對象,
 
異步prepare的時候會收到這個監聽,然後可以在監聽裏調用start方法來啓動播放。
 
public class MyService extends Service implements MediaPlayer.OnPreparedListener {
    private static final ACTION_PLAY = "com.example.action.PLAY";
    MediaPlayer mMediaPlayer = null;
    public int onStartCommand(Intent intent, int flags, int startId) {
        ...
        if (intent.getAction().equals(ACTION_PLAY)) {
            mMediaPlayer = ... // initialize it here
            mMediaPlayer.setOnPreparedListener(this);
            mMediaPlayer.prepareAsync(); // prepare async to not block main thread
        }
    }
    /** Called when MediaPlayer is ready */
    public void onPrepared(MediaPlayer player) {
        player.start();
    }
}
例如寫一個service,在其中調用prepareAsync方法使用異步prepare,然後在設置的OnPrepareListener中使用start方法啓動媒體的播放。
 
 
6.SeekComplete
 
public void setOnSeekCompleteListener (MediaPlayer.OnSeekCompleteListener listener)
設置一個監聽,當seek定位操作完成後發送通知。
 
 
    public interface OnSeekCompleteListener
    {
        public void onSeekComplete(MediaPlayer mp);
    }
mp是調用這個接口的MediaPlayer對象
 
 
7.VideoSizeChanged
 
public void setOnVideoSizeChangedListener (MediaPlayer.OnVideoSizeChangedListener listener)
設置一個監聽,當視頻的大小第一次被知道或者發生改變時發出通知。
 
 
    public interface OnVideoSizeChangedListener
    {
        public void onVideoSizeChanged(MediaPlayer mp, int width, int height);
    }
mp是調用這個接口的MediaPlayer對象,witdh爲視頻的寬,height爲視頻的高。


8.TimedText
 
 
public void setOnTimedTextListener(OnTimedTextListener listener)
設置一個監聽,當媒體的時間數據需要被顯示時發送通知。這個監聽屬於android的內部監聽,sdk好像沒有提供哦。
 
 
    public interface OnTimedTextListener
    {
        public void onTimedText(MediaPlayer mp, TimedText text);
    }
mp是調用這個接口的MediaPlayer對象,text是需要顯示的時候,和這個時候的顯示格式。
上面我們瞭解了一下在android的應用程序中如何使用監聽機制。現在進入主題,我們將進入android的框架源碼中來對這個監聽機制進行深入的剖析。
 
 
二.java框架層中MediaPlayer類的notify機制的分析
 
首先我們進入框架的java層,
請看代碼MeidaPlayer.java@frameworks/base/meida/java/android/media/目錄
上面這些監聽寫完後是怎麼被系統調用的呢?
我們從源碼中來尋找答案。
在MediaPlayer類的內部私有類EventHandler中找到了答案。
 
public void handleMessage(Message msg) {
            if (mMediaPlayer.mNativeContext == 0) {
                Log.w(TAG, "mediaplayer went away with unhandled events");
                return;
            }
            switch(msg.what) {
            case MEDIA_PREPARED:
                if (mOnPreparedListener != null)
                    mOnPreparedListener.onPrepared(mMediaPlayer);
                return;
            case MEDIA_PLAYBACK_COMPLETE:
                if (mOnCompletionListener != null)
                    mOnCompletionListener.onCompletion(mMediaPlayer);
                stayAwake(false);
                return;
            case MEDIA_BUFFERING_UPDATE:
                if (mOnBufferingUpdateListener != null)
                    mOnBufferingUpdateListener.onBufferingUpdate(mMediaPlayer, msg.arg1);
                return;
            case MEDIA_SEEK_COMPLETE:
              if (mOnSeekCompleteListener != null)
                  mOnSeekCompleteListener.onSeekComplete(mMediaPlayer);
              return;
            case MEDIA_SET_VIDEO_SIZE:
              if (mOnVideoSizeChangedListener != null)
                  mOnVideoSizeChangedListener.onVideoSizeChanged(mMediaPlayer, msg.arg1, msg.arg2);
              return;
            case MEDIA_ERROR:
                // For PV specific error values (msg.arg2) look in
                // opencore/pvmi/pvmf/include/pvmf_return_codes.h
                Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
                boolean error_was_handled = false;
                if (mOnErrorListener != null) {
                    error_was_handled = mOnErrorListener.onError(mMediaPlayer, msg.arg1, msg.arg2);
                }
                if (mOnCompletionListener != null && ! error_was_handled) {
                    mOnCompletionListener.onCompletion(mMediaPlayer);
                }
                stayAwake(false);
                return;
            case MEDIA_INFO:
                if (msg.arg1 != MEDIA_INFO_VIDEO_TRACK_LAGGING) {
                    Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")");
                }
                if (mOnInfoListener != null) {
                    mOnInfoListener.onInfo(mMediaPlayer, msg.arg1, msg.arg2);
                }
                // No real default action so far.
                return;
            case MEDIA_TIMED_TEXT:
                if (mOnTimedTextListener != null) {
                    if (msg.obj == null) {
                        mOnTimedTextListener.onTimedText(mMediaPlayer, null);
                    } else {
                        if (msg.obj instanceof byte[]) {
                            TimedText text = new TimedText((byte[])(msg.obj));
                            mOnTimedTextListener.onTimedText(mMediaPlayer, text);
                        }
                    }
                }
                return;
            case MEDIA_NOP: // interface test message - ignore
                break;
            default:
                Log.e(TAG, "Unknown message type " + msg.what);
                return;
            }
        }
 
EventHandler繼承至Handler類,這個Handler類是個什麼。說簡單點就是android提供用來跨線程發送和接收消息的。
很多時候爲了保證ui的流暢,一些比較耗時間的,以及需要與硬件交互的內容就單獨開一個線程來處理,都是在處理的過程中需要ui進行更新,就用到了這個hander的處理類。
一般的用法就是繼承這個Handler
 
    private class EventHandler extends Handler
    {
        private MediaPlayer mMediaPlayer;
        public EventHandler(MediaPlayer mp, Looper looper) {
            super(looper);
            mMediaPlayer = mp;
        }
        @Override
        public void handleMessage(Message msg) {
              ...
        }
    }
然後重寫這個handleMessage的方法。
 
這個方法主要負責對收到的消息的處理。我們看見在這個handleMessage中上面的各種監聽的消息進行了相應的處理,調用了用戶設置的監聽處理方法。
還可以看出沒有專門的完成播放的消息,是由error的消息來調用完成播放的監聽方法的,做法就是上面提到的如果沒有錯誤,就是在error的監聽方法中用戶返回false,則調用completion的監聽方法。
 
那麼這些消息是從哪裏發送出來的呢???
 
讓我們走進科學,來揭開這個未解之謎。
 
    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 (mp.mEventHandler != null) {
            Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
            mp.mEventHandler.sendMessage(m);
        }
    }
 
我們這源碼中發現了postEventFromNative這個方法,其中使用了Hander類提供的obtianMessage方法和sendMessage方法。
obtianMessage方法是用來從消息隊列中得到一個Meassage信號並對這個消息進行填充,然後通過sendMeaage方法方法到消息隊列中,然後我們的 handleMessage方法就會被調用去處理這個消息。那麼handleMessage方法是怎麼被調用的,這個涉及到了android提供的另一個封裝類Looper類。這個類我們在這裏先不展開說了,有機會我會補充這部分的內容。這裏只想說一下,這個Looper類就相當與一個隊列,負責對消息的分發和處理。每個apk創建的時候都會默認創建一個ui的主Looper供程序使用。好了不廢話了,我們接着往下剖析。
在MediaPlayer.java源碼中我們會發現沒有人調用這個postEventFromNative方法,那麼這個方法是怎麼詭異的被調用的呢。。。哪位天使,哪位大哥,你們在後面搗亂啊。。。都給我現行啊。。。
 
“借我借我一雙慧眼吧,讓我把這紛擾看得清清楚楚明明白白真真切切”
我們現在看看這個java的MediaPlayer類的靜態初始化塊。
    static {
        System.loadLibrary("media_jni");
        native_init();
    }
哈哈,原來使用了上古神器jni啊。。。。。。。
什麼不明白什麼是jni!!!,這個可是sun當年搞出來傑作啊。通過它可以讓java的程序調用c,c++,以及其他語言編寫好的模塊,擴大了java了使用面。
這段歷史的內容太多,以後文中會慢慢道來。
 
 
 
三.jni層中java和c++代碼中notify機制如何交互 
我們先來看static{},在java中表現靜態初始化塊,會在調用類的構造函數前先運行,因此當我們使用如下語句時
MediaPlayer mp = new MediaPlayer();
java虛擬機先執行這部分代碼。
它先調用了System.loadLibrary("media_jni");
System.loadLibrary是java提供調用jni動態庫的方法,它的參數是jni庫的名字,我們這裏是media_jni。一般在linux底下,會在這個名字前加上lib,名字後加上.so,來查找動態庫。因此這個類導入的jni動態庫就是libmedia_jin.so。我們在android系統查找,會發現它的位置位於/system/lib目錄下
圖中還有一個libmedia.so是幹什麼用的呢?這裏不說,後面會遇到。
那這個libmedia_jin.so對應的代碼是在哪裏呢??
找啊找找朋友,找到一個好基友。。哈哈,說笑了。
我們找到它的代碼在android_media_MediaPlayer.cpp@frameworks/base/media/jni目錄中。(*^__^*) 嘻嘻……,從現在開始我們進行了c++的懷抱了。。
一個jni的庫有兩種註冊方式,靜態註冊和動態註冊,關於兩種方式如何使用將在將來補倉。
我們這裏使用的是動態註冊方式,動態註冊的標誌就是編寫JNI_OnLoad方法,java虛擬機在找到jni庫後,會去庫中查找這個方法,如果有就調用。
我們看一下這個libmedia_jni.so的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) {
        LOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);
    if (register_android_media_MediaPlayer(env) < 0) {
        LOGE("ERROR: MediaPlayer native registration failed\n");
        goto bail;
    }
    if (register_android_media_MediaRecorder(env) < 0) {
        LOGE("ERROR: MediaRecorder native registration failed\n");
        goto bail;
    }
    ...
    if (register_android_mtp_MtpServer(env) < 0) {
        LOGE("ERROR: MtpServer native registration failed");
        goto bail;
    }
    /* success -- return valid version number */
    result = JNI_VERSION_1_4;
bail:
    return result;
}
這個方法中調用了很多和media有關的註冊方法。
和我們目前這個MediaPlayer.java有關的是register_android_media_MediaPlayer方法。來看看它都有什麼東東。
static int register_android_media_MediaPlayer(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env,
                "android/media/MediaPlayer", gMethods, NELEM(gMethods));
}

調用了android提供的jni幫助類來註冊native方法。要使用android的幫助類,需要包括JNIHelp.h這個頭文件。這個gMethods是個數組,它的內容是
static JNINativeMethod gMethods[] = {
     ...
    {"native_init",         "()V",                              (void *)android_media_MediaPlayer_native_init},
    {"native_setup",        "(Ljava/lang/Object;)V",            (void *)android_media_MediaPlayer_native_setup},
    {"native_finalize",     "()V",                              (void *)android_media_MediaPlayer_native_finalize},
    ...
};
 
registerNativeMethods方法的第一個參數是虛擬機的環境env,及上下文,它是由JNI_OnLoad方法傳入的JavaVM類型的參數vm中得到的
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
第二個參數是要註冊給哪個java的類,我們這裏是android.media.MediaPlayer,
第三個參數是註冊的native方法的數組,裏面就是c++層提供給java層調用的原生方法,JNINativeMethod是個註冊jni的結構,
typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;
name是在java層中調用的方法的名稱,signature是這個 方法的簽名。fnPtr是對應的c++的方法的指針。
而在java程序需要這些方法進行簽名。
    ...
    private static native final void native_init();
    private native final void native_setup(Object mediaplayer_this);
    ...

可以看出它們和java自己的方法相比,多了一個關鍵字native,其他是一樣的。
我們現在回到開頭的那個靜態初始化塊中,看到它調用了一個jni的native方法native_init();
它對應的C++方法
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.post_event = env->GetStaticMethodID(clazz, "postEventFromNative","(Ljava/lang/Object;IIILjava/lang/Object;)V");看到了嗎? 
在native_init方法中把postEventFromNative方法的method id存在fields.post_event中。
現在我們就要找一下是水調用了這個method id哦。
void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2, const Parcel *obj)
{
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    if (obj && obj->dataSize() > 0) {
        jbyteArray jArray = env->NewByteArray(obj->dataSize());
        if (jArray != NULL) {
            jbyte *nArray = env->GetByteArrayElements(jArray, NULL);
            memcpy(nArray, obj->data(), obj->dataSize());
            env->ReleaseByteArrayElements(jArray, nArray, 0);
            env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
                    msg, ext1, ext2, jArray);
            env->DeleteLocalRef(jArray);
        }
    } else {
        env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
                msg, ext1, ext2, NULL);
    }
}
正主出來了。原來是JNIMediaPlayerListener類的notify方法,它通過jni調用java靜態方法的CallStaticVoidMethod方法來執行postEventFromNative方法。
那這個notify是誰調用的呢?我們在native_setup方法找到一些蛛絲馬跡。
在java端的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));
    }
調用了native_setup方法,它是個native方法,對應的c++的方法是android_media_MediaPlayer_native_setup
static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
    LOGV("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);
}
這裏new了一個c++端的MediaPlayer的對象mp。同時new了一個JNIMediaPlayerListener對象listener,notify方法就包含在這個中。
status_t MediaPlayer::setListener(const sp<MediaPlayerListener>& listener)
{
    LOGV("setListener");
    Mutex::Autolock _l(mLock);
    mListener = listener;
    return NO_ERROR;
}
這個listener被賦值給了mp的成員mListener。
緊接着我們找到了process_media_player_call方法
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!
    ...
    }
}
通過getMediaPlayer方法得到我們剛纔new出來的c++端的MediaPlayer的對象mp,然後調用MediaPlayer類的notify方法。
此notify非彼notify
先進入MediaPlayer.cpp@framworks/base/media/libmedia目錄
看看這個notify的真正面目
void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj)
{
   ...
    switch (msg) {
    case MEDIA_NOP: // interface test message
        break;
     ...
    default:
        LOGV("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);
        LOGV("callback application");
        listener->notify(msg, ext1, ext2, obj);
        LOGV("back from callback");
    }
}
哦。原來是這樣啊,這個notify方法中最後調用了我們前面賦值給mListener的JNIMediaPlayerListener類中notify。這樣流程就通順了。。。

啊。發生了什麼事啊。。。。。這個notify好像只能發送error的通知。。這到底是怎麼回事啊。。。。
 
  四.c++層的MediaPlayer類中notify機制的分析   
未完待續。。。。。。。。
 
 
 
 
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章