MediaPlayer的notify监听机制

转自:http://fangli.blog.51cto.com/6272355/1083583

一.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。这样流程就通顺了。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值