上一节了解了MediaPlayer api的使用,这一节就我们将会了解MediaPlayer的生命周期与api使用细节。
MediaPlayer.java 一开始有对生命周期的描述,这里对这些内容进行翻译:
Idle
状态,release 调用后进入到 End
状态,这两个状态之间就是 MediaPlayer 对象的生命周期;Idle
状态,但是它们仍然是有区别的。getDuration 等方法在Idle
状态下调用会出现error,如果是new之后调用,虽然会返回error,但是error并不是由内部播放器引擎上抛的,播放器状态仍然保持在Idle
状态;如果刚好是在reset之后调用getDuration方法,那么内部播放器引擎会上抛error事件,并且把播放器状态置为Error
;End
状态,MediaPlayer对象将不能再被使用,并且不能再被切换到其他状态,如果仍要使用需要重新new一个对象,进入Idle
状态。Error
状态并且上抛事件,如果要继续使用当前MediaPlayer对象,可以调用reset使其进入到Idle
状态;Idle
进入Loaded
状态,如果在其他状态调用则会抛出IllegalStateException
错误;Loaded
状态进入到Prepared
状态,如果调用的是prepareAsync,则会先进入中间状态Preparing
;在除了Loaded
和Stopped
状态外调用prepare方法都是非法的,会抛出IllegalStateException
错误;Prepared
状态进入到Started
状态,可以用isPlaying判断播放器当前状态是否在Started
状态;如果已经进入到Started
状态,则再调用start不会有任何影响;Paused
状态,从Started
进入到Paused
或者反过来的过程都是异步的;如果已经进入到Paused
状态,则再调用pause不会有任何影响;在Paused
状态下重新调用start将会恢复播放,进入到Started
状态;Started
、Paused
、Prepared
、PlaybackCompleted
状态进入到Stopped
状态,一旦进入到Stopped
状态,那么必须要调用prepare重新进入Prepared
状态才能够重新进行播放;同样的,如果已经进入到Stopped
状态,则再调用stop不会有任何影响;Started
、Paused
、Prepared
、PlaybackCompleted
状态下调用;Started
状态,如果setLooping设置为false,那么播放器将会进入到PlaybackCompleted
状态;在PlaybackCompleted
状态下,调用start方法将会从头开始重新播放,并且进入到Started
状态。从上面我们可以了解到,MediaPlayer维护了一套状态机,并且调用MediaPlayer方法时会检查当前调用是否是非法的,这套状态机机制在 mediaplayer.cpp 中。 MediaPlayer 定义了如下状态:
enum media_player_states {
MEDIA_PLAYER_STATE_ERROR = 0,
MEDIA_PLAYER_IDLE = 1 << 0,
MEDIA_PLAYER_INITIALIZED = 1 << 1,
MEDIA_PLAYER_PREPARING = 1 << 2,
MEDIA_PLAYER_PREPARED = 1 << 3,
MEDIA_PLAYER_STARTED = 1 << 4,
MEDIA_PLAYER_PAUSED = 1 << 5,
MEDIA_PLAYER_STOPPED = 1 << 6,
MEDIA_PLAYER_PLAYBACK_COMPLETE = 1 << 7
};
要注意的是,End状态表示native MediaPlayer对象已经销毁了,所以它并没有真正的 End 状态。
MediaPlayer.java 中还有一段关于函数在什么状态下调用是有效的,什么状态下调用是无效的的表格,这里挑出一些进行翻译。
序号 | 方法 | 有效状态 | 无效状态下调用 |
---|---|---|---|
1 | getAudioSessionId | any | |
2 | getCurrentPosition | Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted | Error |
3 | getDuration | Prepared, Started, Paused, Stopped, PlaybackCompleted | Error |
4 | getVideoHeight | any | |
5 | isPlaying | any | |
6 | pause | Started, Paused, PlaybackCompleted | Error |
7 | prepare | Initialized, Stopped | 异常 |
8 | prepareAsync | Initialized, Stopped | 异常 |
9 | release | any | |
10 | reset | any | |
11 | seekTo | Prepared, Started, Paused, PlaybackCompleted | Error |
12 | setDataSource | Idle | 异常 |
13 | setDisplay | any | |
15 | setSurface | any | |
16 | setVideoScalingMode | Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted | |
17 | setLooping | Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted | |
18 | start | Prepared, Started, Paused, PlaybackCompleted | Error |
19 | stop | Prepared, Started, Stopped, Paused, PlaybackCompleted | Error |
从以上表格我们可以发现,非法状态下调用prepare
、prepareAsync
、setDataSource
会抛异常中止程序运行;调用start
、pause
等方法会将状态置为 Error
,具体程序如何反应需要看我们的OnErrorListener是如何处理的:
case MEDIA_ERROR:
Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
boolean error_was_handled = false;
OnErrorListener onErrorListener = mOnErrorListener;
if (onErrorListener != null) {
error_was_handled = onErrorListener.onError(mMediaPlayer, msg.arg1, msg.arg2);
}
{
mOnCompletionInternalListener.onCompletion(mMediaPlayer);
OnCompletionListener onCompletionListener = mOnCompletionListener;
if (onCompletionListener != null && ! error_was_handled) {
onCompletionListener.onCompletion(mMediaPlayer);
}
}
stayAwake(false);
return;
相关代码路径:
在看正式的 new MediaPlayer 流程前,我们先要注意MediaPlayer.java中的如下代码段:
static {
System.loadLibrary("media_jni");
native_init();
}
它会加载media_jni.so,并执行native函数 android_media_MediaPlayer_native_init 。native_init会获取java类中的postEventFromNative
方法ID,mNativeContext
、mNativeSurfaceTexture
等字段的ID,获取到的ID会存储在静态变量fields_t
中,后期可以通过这些ID找到Java对象中对应的成员,获取其存储的值或者向其中存储值。
static fields_t fields;
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V");
fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "J");
接下来看new MediaPlayer创建一个对象会做哪些事情:
private MediaPlayer(int sessionId) {
...
try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
native_setup(new WeakReference<MediaPlayer>(this), attributionSourceState.getParcel());
}
}
核心是调用native_setup方法,需要将自身的弱引用对象作为参数向下传递:
static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jobject jAttributionSource)
{
Parcel* parcel = parcelForJavaObject(env, jAttributionSource);
android::content::AttributionSourceState attributionSource;
attributionSource.readFromParcel(parcel);
// 创建MediaPlayer native对象
sp<MediaPlayer> mp = sp<MediaPlayer>::make(attributionSource);
// 创建并注册Callback对象
sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
mp->setListener(listener);
// 将MediaPlayer对象存储到Java对象中
setMediaPlayer(env, thiz, mp);
}
native_setup干了3件事:
来看看如何存储MediaPlayer native指针的:
static sp<MediaPlayer> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer>& player)
{
Mutex::Autolock l(sLock);
sp<MediaPlayer> old = (MediaPlayer*)env->GetLongField(thiz, fields.context);
// 手动增加强引用计数
if (player.get()) {
player->incStrong((void*)setMediaPlayer);
}
// 检查mNativeContext中存储的MediaPlayer对象
if (old != 0) {
old->decStrong((void*)setMediaPlayer);
}
// 将MediaPlayer的地址存储到mNativeContext
env->SetLongField(thiz, fields.context, (jlong)player.get());
return old;
}
这里有3个步骤:
mNativeContext
中存储的地址是否为NULL,如果不为NULL则需要销毁存储的MediaPlayer对象;mNativeContext
中。虽然我们会把MediaPlayer对象存储到Java字段中,但是其引用计数在setMediaPlayer中仍为1,如不增加引用计数,出了当前作用域native对象将自动销毁。
reset的作用是重置当前MediaPlayer对象,使其恢复到new的状态。我们来看下reset实现:
status_t MediaPlayer::reset_l()
{
mLoop = false;
if (mCurrentState == MEDIA_PLAYER_IDLE) return NO_ERROR;
// 1.阻止 MEDIA_PREPARED 事件上抛
mPrepareSync = false;
if (mPlayer != 0) {
// 2.调用内部实现的reset
status_t ret = mPlayer->reset();
if (ret != NO_ERROR) {
ALOGE("reset() failed with return code (%d)", ret);
mCurrentState = MEDIA_PLAYER_STATE_ERROR;
} else {
// 2.调用内部实现的disconnect
mPlayer->disconnect();
mCurrentState = MEDIA_PLAYER_IDLE;
}
// 3.销毁内部实现
mPlayer = 0;
return ret;
}
// 4.清除设置
clear_l();
return NO_ERROR;
}
reset主要会做3件事情:
Preparing
,那么会将 mPrepareSync 置为fasle,阻止 MEDIA_PREPARED 事件上抛,具体内部如何操作后续再研究;release会直接销毁掉MediaPlayer native对象,一旦调用release,当前MediaPlayer java对象将不能够再被使用:
MediaPlayer::~MediaPlayer()
{
ALOGV("destructor");
if (mAudioAttributesParcel != NULL) {
delete mAudioAttributesParcel;
mAudioAttributesParcel = NULL;
}
AudioSystem::releaseAudioSessionId(mAudioSessionId, (pid_t)-1);
disconnect();
IPCThreadState::self()->flushCommands();
}
MediaPlayer 的析构函数同样也会调用内部实例的 disconnect 方法断开连接,之后会销毁 MediaPlayer 内部实例,这两点和reset是相同的。我们在想要结束activity,可以先调用reset,再调用release,也可以直接调用release来释放资源。