您的位置:首页 > 移动开发 > Android开发

android4.0 MediaPlayer的notify监听机制的全面剖析

2014-04-18 00:14 316 查看
本文将贯穿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()这个静态方法来创建,然后哦启动它。
MediaPlayermp=MediaPlayer.create(context,R.raw.sound_file_1);
mp.start();
第二种方法,是new一个MediaPlayer的对象,通过setDataSource来设置播放的内容,接着调用prepare方法对真正的打开数据源准备播放。
MediaPlayermp=newMediaPlayer();
mp.setDataSource(PATH_TO_FILE);
mp.prepare();
mp.start();
使用上面的两种方法创建好MediaPlayer,接着就是通过start方法来启动对媒体的播放。我们先来看一下MediaPlayer的状态图从状态图可以看出在创建到播放的过程中可能会出现各种状态和问题,比如,无法找到播放文件,播放数据错误,播放的准备进度等,在这种情况下,android为我们提供了一种事件的通知机制,来适时的给用户各种状态和错误的反馈。android通过设置事件监听来对这些状态和错误来处理反馈。比较常见的事件监听包括:1.BufferingUpdate
publicvoidsetOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListenerlistener)
设置一个监听当播放网络的数据流的buffer发生变化的时候发出通知。它的参数是一个接口
publicinterfaceOnBufferingUpdateListener
{
voidonBufferingUpdate(MediaPlayermp,intpercent);
}
mp是调用这个接口的MediaPlayer对象,percent是数据缓存的百分比。2.Completion
publicvoidsetOnCompletionListener(MediaPlayer.OnCompletionListenerlistener)
设置一个监听,当一个媒体是播放完毕的时候发出通知。
publicinterfaceOnCompletionListener
{
voidonCompletion(MediaPlayermp);
}
mp是调用这个接口的MediaPlayer对象3.Error
publicvoidsetOnErrorListener(MediaPlayer.OnErrorListenerlistener)
设置一个监听,当使用异步操作出现错误时发送的通知。使用prepare方法是启动同步方式,当prepare方法需要全部执行完后才能返回。使用prepareAsync方法是启动异步方式,调用prepareAsync方法后直接返回,后台用在另一个线程中完成对prepare的准备工作。这个error的错误监听的通知就是在异步的方式下才会发出的。
publicinterfaceOnErrorListener
{
booleanonError(MediaPlayermp,intwhat,intextra);
}
mp是调用这个接口的MediaPlayer对象,what是错误的类型,extra是针对what错误的额外的代码错误类型包括了:MEDIA_ERROR_UNKNOWN:未指定的错误,一般没有使用。MEDIA_ERROR_SERVER_DIED:媒体的后台服务挂了。MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:播放发生错误,或者视频本身有问题,例如视频的索引不在文件的开始部分。返回true表示有错误发生,当返回false,或者没有调用这个监听时,将会调用OnCompletionListener。4.Info
publicvoidsetOnInfoListener(MediaPlayer.OnInfoListenerlistener)
设置一个监听,当有信息或者警告的时候发出通知。
publicinterfaceOnInfoListener
{
booleanonInfo(MediaPlayermp,intwhat,intextra);
}
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
publicvoidsetOnPreparedListener(MediaPlayer.OnPreparedListenerlistener)
设置一个监听,当准备完成的时候发出通知
publicinterfaceOnPreparedListener
{
voidonPrepared(MediaPlayermp);
}
mp是调用这个接口的MediaPlayer对象,异步prepare的时候会收到这个监听,然后可以在监听里调用start方法来启动播放。
publicclassMyServiceextendsServiceimplementsMediaPlayer.OnPreparedListener{
privatestaticfinalACTION_PLAY="com.example.action.PLAY";
MediaPlayermMediaPlayer=null;
publicintonStartCommand(Intentintent,intflags,intstartId){
...
if(intent.getAction().equals(ACTION_PLAY)){
mMediaPlayer=...//initializeithere
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.prepareAsync();//prepareasynctonotblockmainthread
}
}
/**CalledwhenMediaPlayerisready*/
publicvoidonPrepared(MediaPlayerplayer){
player.start();
}
}
例如写一个service,在其中调用prepareAsync方法使用异步prepare,然后在设置的OnPrepareListener中使用start方法启动媒体的播放。6.SeekComplete
publicvoidsetOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListenerlistener)
设置一个监听,当seek定位操作完成后发送通知。
publicinterfaceOnSeekCompleteListener
{
publicvoidonSeekComplete(MediaPlayermp);
}
mp是调用这个接口的MediaPlayer对象7.VideoSizeChanged
publicvoidsetOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListenerlistener)
设置一个监听,当视频的大小第一次被知道或者发生改变时发出通知。
publicinterfaceOnVideoSizeChangedListener
{
publicvoidonVideoSizeChanged(MediaPlayermp,intwidth,intheight);
}
mp是调用这个接口的MediaPlayer对象,witdh为视频的宽,height为视频的高。8.TimedText
publicvoidsetOnTimedTextListener(OnTimedTextListenerlistener)
设置一个监听,当媒体的时间数据需要被显示时发送通知。这个监听属于android的内部监听,sdk好像没有提供哦。
publicinterfaceOnTimedTextListener
{
publicvoidonTimedText(MediaPlayermp,TimedTexttext);
}
mp是调用这个接口的MediaPlayer对象,text是需要显示的时候,和这个时候的显示格式。上面我们了解了一下在android的应用程序中如何使用监听机制。现在进入主题,我们将进入android的框架源码中来对这个监听机制进行深入的剖析。二.java框架层中MediaPlayer类的notify机制的分析首先我们进入框架的java层,请看代码MeidaPlayer.java@frameworks/base/meida/java/android/media/目录上面这些监听写完后是怎么被系统调用的呢?我们从源码中来寻找答案。在MediaPlayer类的内部私有类EventHandler中找到了答案。
publicvoidhandleMessage(Messagemsg){
if(mMediaPlayer.mNativeContext==0){
Log.w(TAG,"mediaplayerwentawaywithunhandledevents");
return;
}
switch(msg.what){
caseMEDIA_PREPARED:
if(mOnPreparedListener!=null)
mOnPreparedListener.onPrepared(mMediaPlayer);
return;
caseMEDIA_PLAYBACK_COMPLETE:
if(mOnCompletionListener!=null)
mOnCompletionListener.onCompletion(mMediaPlayer);
stayAwake(false);
return;
caseMEDIA_BUFFERING_UPDATE:
if(mOnBufferingUpdateListener!=null)
mOnBufferingUpdateListener.onBufferingUpdate(mMediaPlayer,msg.arg1);
return;
caseMEDIA_SEEK_COMPLETE:
if(mOnSeekCompleteListener!=null)
mOnSeekCompleteListener.onSeekComplete(mMediaPlayer);
return;
caseMEDIA_SET_VIDEO_SIZE:
if(mOnVideoSizeChangedListener!=null)
mOnVideoSizeChangedListener.onVideoSizeChanged(mMediaPlayer,msg.arg1,msg.arg2);
return;
caseMEDIA_ERROR:
//ForPVspecificerrorvalues(msg.arg2)lookin
//opencore/pvmi/pvmf/include/pvmf_return_codes.h
Log.e(TAG,"Error("+msg.arg1+","+msg.arg2+")");
booleanerror_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;
caseMEDIA_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);
}
//Norealdefaultactionsofar.
return;
caseMEDIA_TIMED_TEXT:
if(mOnTimedTextListener!=null){
if(msg.obj==null){
mOnTimedTextListener.onTimedText(mMediaPlayer,null);
}else{
if(msg.objinstanceofbyte[]){
TimedTexttext=newTimedText((byte[])(msg.obj));
mOnTimedTextListener.onTimedText(mMediaPlayer,text);
}
}
}
return;
caseMEDIA_NOP://interfacetestmessage-ignore
break;
default:
Log.e(TAG,"Unknownmessagetype"+msg.what);
return;
}
}
EventHandler继承至Handler类,这个Handler类是个什么。说简单点就是android提供用来跨线程发送和接收消息的。很多时候为了保证ui的流畅,一些比较耗时间的,以及需要与硬件交互的内容就单独开一个线程来处理,都是在处理的过程中需要ui进行更新,就用到了这个hander的处理类。一般的用法就是继承这个Handler
privateclassEventHandlerextendsHandler
{
privateMediaPlayermMediaPlayer;
publicEventHandler(MediaPlayermp,Looperlooper){
super(looper);
mMediaPlayer=mp;
}
@Override
publicvoidhandleMessage(Messagemsg){
...
}
}
然后重写这个handleMessage的方法。这个方法主要负责对收到的消息的处理。我们看见在这个handleMessage中上面的各种监听的消息进行了相应的处理,调用了用户设置的监听处理方法。还可以看出没有专门的完成播放的消息,是由error的消息来调用完成播放的监听方法的,做法就是上面提到的如果没有错误,就是在error的监听方法中用户返回false,则调用completion的监听方法。那么这些消息是从哪里发送出来的呢???让我们走进科学,来揭开这个未解之谜。
privatestaticvoidpostEventFromNative(Objectmediaplayer_ref,
intwhat,intarg1,intarg2,Objectobj)
{
MediaPlayermp=(MediaPlayer)((WeakReference)mediaplayer_ref).get();
if(mp==null){
return;
}
if(mp.mEventHandler!=null){
Messagem=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中表现静态初始化块,会在调用类的构造函数前先运行,因此当我们使用如下语句时
MediaPlayermp=newMediaPlayer();
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方法
jintJNI_OnLoad(JavaVM*vm,void*reserved){JNIEnv*env=NULL;jintresult=-1;if(vm->GetEnv((void**)&env,JNI_VERSION_1_4)!=JNI_OK){LOGE("ERROR:GetEnvfailed\n");gotobail;}assert(env!=NULL);if(register_android_media_MediaPlayer(env)<0){LOGE("ERROR:MediaPlayernativeregistrationfailed\n");gotobail;}if(register_android_media_MediaRecorder(env)<0){LOGE("ERROR:MediaRecordernativeregistrationfailed\n");gotobail;}...if(register_android_mtp_MtpServer(env)<0){LOGE("ERROR:MtpServernativeregistrationfailed");gotobail;}/*success--returnvalidversionnumber*/result=JNI_VERSION_1_4;bail:returnresult;}
这个方法中调用了很多和media有关的注册方法。
和我们目前这个MediaPlayer.java有关的是register_android_media_MediaPlayer方法。来看看它都有什么东东。
staticintregister_android_media_MediaPlayer(JNIEnv*env){returnAndroidRuntime::registerNativeMethods(env,"android/media/MediaPlayer",gMethods,NELEM(gMethods));}
调用了android提供的jni帮助类来注册native方法。要使用android的帮助类,需要包括JNIHelp.h这个头文件。这个gMethods是个数组,它的内容是
staticJNINativeMethodgMethods[]={...{"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:GetEnvfailed\n");gotobail;}
第二个参数是要注册给哪个java的类,我们这里是android.media.MediaPlayer,
第三个参数是注册的native方法的数组,里面就是c++层提供给java层调用的原生方法,JNINativeMethod是个注册jni的结构,
typedefstruct{constchar*name;constchar*signature;void*fnPtr;}JNINativeMethod;
name是在java层中调用的方法的名称,signature是这个方法的签名。fnPtr是对应的c++的方法的指针。
而在java程序需要这些方法进行签名。
...privatestaticnativefinalvoidnative_init();privatenativefinalvoidnative_setup(Objectmediaplayer_this);...
可以看出它们和java自己的方法相比,多了一个关键字native,其他是一样的。
我们现在回到开头的那个静态初始化块中,看到它调用了一个jni的native方法native_init();
它对应的C++方法
staticvoidandroid_media_MediaPlayer_native_init(JNIEnv*env){jclassclazz;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方法的methodid存在fields.post_event中。
现在我们就要找一下是水调用了这个methodid哦。
voidJNIMediaPlayerListener::notify(intmsg,intext1,intext2,constParcel*obj){JNIEnv*env=AndroidRuntime::getJNIEnv();if(obj&&obj->dataSize()>0){jbyteArrayjArray=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的构造函数中
publicMediaPlayer(){Looperlooper;if((looper=Looper.myLooper())!=null){mEventHandler=newEventHandler(this,looper);}elseif((looper=Looper.getMainLooper())!=null){mEventHandler=newEventHandler(this,looper);}else{mEventHandler=null;}/*Nativesetuprequiresaweakreferencetoourobject.*It'seasiertocreateitherethaninC++.*/native_setup(newWeakReference<MediaPlayer>(this));}
调用了native_setup方法,它是个native方法,对应的c++的方法是android_media_MediaPlayer_native_setup
staticvoidandroid_media_MediaPlayer_native_setup(JNIEnv*env,jobjectthiz,jobjectweak_this){LOGV("native_setup");sp<MediaPlayer>mp=newMediaPlayer();if(mp==NULL){jniThrowException(env,"java/lang/RuntimeException","Outofmemory");return;}//createnewlistenerandgiveittoMediaPlayersp<JNIMediaPlayerListener>listener=newJNIMediaPlayerListener(env,thiz,weak_this);mp->setListener(listener);//StowournewC++MediaPlayerinanopaquefieldintheJavaobject.setMediaPlayer(env,thiz,mp);}
这里new了一个c++端的MediaPlayer的对象mp。同时new了一个JNIMediaPlayerListener对象listener,notify方法就包含在这个中。
status_tMediaPlayer::setListener(constsp<MediaPlayerListener>&listener){LOGV("setListener");Mutex::Autolock_l(mLock);mListener=listener;returnNO_ERROR;}
这个listener被赋值给了mp的成员mListener。
紧接着我们找到了process_media_player_call方法
staticvoidprocess_media_player_call(JNIEnv*env,jobjectthiz,status_topStatus,constchar*exception,constchar*message){if(exception==NULL){//Don'tthrowexception.Instead,sendanevent.if(opStatus!=(status_t)OK){sp<MediaPlayer>mp=getMediaPlayer(env,thiz);if(mp!=0)mp->notify(MEDIA_ERROR,opStatus,0);}}else{//Throwexception!...}}
通过getMediaPlayer方法得到我们刚才new出来的c++端的MediaPlayer的对象mp,然后调用MediaPlayer类的notify方法。
此notify非彼notify
先进入MediaPlayer.cpp@framworks/base/media/libmedia目录
看看这个notify的真正面目
voidMediaPlayer::notify(intmsg,intext1,intext2,constParcel*obj){...switch(msg){caseMEDIA_NOP://interfacetestmessagebreak;...default:LOGV("unrecognizedmessage:(%d,%d,%d)",msg,ext1,ext2);break;}sp<MediaPlayerListener>listener=mListener;if(locked)mLock.unlock();//thispreventsre-entrantcallsintoclientcodeif((listener!=0)&&send){Mutex::Autolock_l(mNotifyLock);LOGV("callbackapplication");listener->notify(msg,ext1,ext2,obj);LOGV("backfromcallback");}}
哦。原来是这样啊,这个notify方法中最后调用了我们前面赋值给mListener的JNIMediaPlayerListener类中notify。这样流程就通顺了。。。啊。发生了什么事啊。。。。。这个notify好像只能发送error的通知。。这到底是怎么回事啊。。。。
四.c++层的MediaPlayer类中notify机制的分析
未完待续。。。。。。。。
原文在http://fangli.blog.51cto.com/6272355/1083583
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: