Android MediaPlayer IllegalStateException源码分析定位
Posted 新根
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android MediaPlayer IllegalStateException源码分析定位相关的知识,希望对你有一定的参考价值。
1.源码分析,定位报错点
最近发现Bugly上存在一个长期的bug:
java.lang.IllegalStateException:
android.media.MediaPlayer._start(Native Method)
android.media.MediaPlayer.startImpl(MediaPlayer.java:1373)
android.media.MediaPlayer.start(MediaPlayer.java:1345)
org.appplay.lib.SoundPlayer$1.onPrepared(SoundPlayer.java:46)
android.media.MediaPlayer$EventHandler.handleMessage(MediaPlayer.java:3453)
android.os.Handler.dispatchMessage(Handler.java:106)
android.os.Looper.loop(Looper.java:224)
android.app.ActivityThread.main(ActivityThread.java:7087)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:604)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:928)
从上面的报错看,只能根据源码走向,推断报错点了。
从MediaPlayer#startImpl()
方法开始追踪:
frameworks/base/media/java/android/media/MediaPlayer.java:
private void startImpl()
//...
_start();// 通过jni 调用native 层的开始方法
接下来找到java 中_start() 对应的native层方法 ,media 对应的jni 类一般都是放在frameworks/base/media/jni
中。
frameworks/base/media/jni/android_media_MediaPlayer.cpp:
static const JNINativeMethod gMethods[] =
"_start", "()V", (void *)android_media_MediaPlayer_start
在该类中全局检索_start , 找对对应的jni 方法android_media_MediaPlayer_start()
。
接下来看下该方法:
static void
android_media_MediaPlayer_start(JNIEnv *env, jobject thiz)
ALOGV("start");
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
if (mp == NULL )
//关键点:mp 为空,则抛出异常
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return;
//.......
从上面代码来看,获取MediaPlayer*
指针对象为空,导致抛出IllegalStateException异常。
接下来看下该方法:
static sp<MediaPlayer> getMediaPlayer(JNIEnv* env, jobject thiz)
Mutex::Autolock l(sLock);
MediaPlayer* const p = (MediaPlayer*)env->GetLongField(thiz, fields.context);
return sp<MediaPlayer>(p);
jobject 对象是java层中MediaPlayer类对象,env->GetLongField(thiz, fields.context)
是反射获取MediaPlayer 类中某个属性,也就是该属性为空导致异常的。
全局检索下field.context
相关信息:
struct fields_t
jfieldID context;
//......
;
fields_t
是c++中结构体,类似java 中实体类,专门存储不同数据类型的数据。context 又是哪个属性的field呢?
全局检索下,找到该方法:
static void
android_media_MediaPlayer_native_init(JNIEnv *env)
jclass clazz;
clazz = env->FindClass("android/media/MediaPlayer");
if (clazz == NULL)
return;
//反射获取MediaPlayer中mNativeContext
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL)
return;
filelds.context
是mNativeContext的field ,即反射获取MediaPlayer中mNativeContext
属性为空,导致抛出异常。
在Java 层MediaPlayer 中检索mNativeContext
的赋值操作并没有找到。
在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);
if (old != 0)
old->decStrong((void*)setMediaPlayer);
// context 进行赋值操作
env->SetLongField(thiz, fields.context, (jlong)player.get());
return old;
进一步检索发现有两处调用setMediaPlayer()
。
第一处是:
static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jobject jAttributionSource)
ALOGV("native_setup");
//.....
//构建出MediaPlayer*
sp<MediaPlayer> mp = sp<MediaPlayer>::make(attributionSource);
if (mp == NULL)
jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
return;
//...........
// 将MediaPlayer* 设置给mNativeContext
setMediaPlayer(env, thiz, mp);
上面代码是java 层MediaPlayer 初始化调用的native 层方法,会对mNativeContext
进行赋值操作。该上述逻辑,并不是关键。
第二处是:
static void
android_media_MediaPlayer_release(JNIEnv *env, jobject thiz)
ALOGV("release");
decVideoSurfaceRef(env, thiz);
// 进行释放操作
sp<MediaPlayer> mp = setMediaPlayer(env, thiz, 0);
//....
在上面的代码中会对mNativeContext释放为空的操作,因此判断,问题是调用了该方法导致的。
接下来,查看下该方法被哪里调用。
static const JNINativeMethod gMethods[] =
"_release", "()V", (void *)android_media_MediaPlayer_release
在java 层的MediaPlayer中release()
中会调用到native层的_release()
。
基本上可以推断出,导致该问题是原因是调用release()
释放资源后,又调用start()
导致异常。
2.解决方式
查看下项目中MediaPlayer相关逻辑,会首先调用stop有关的操作(释放old MediaPlayer 资源),接着new MediaPlayer
,调用prepareAsync()
异步加载,在OnPreparedListener
回调器中又调用start()
。这里可能是短时间多次播放,存在覆盖问题,导致错误调用已经释放的MediaPlayer,又调用start()。
解决办法如下:
public void playByUrl(String url, boolean isLoop)
// 如果在播放,先停止
stop();
mMediaPlayer = new MediaPlayer();
try
//.......
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener()
@Override
public void onPrepared(MediaPlayer mp)
if (mp!=mMediaPlayer)
// 因异加载的回调,可能导致短时间多个MediaPlayer覆盖问题
return ;
// 装载完毕 开始播放流媒体
if (mMediaPlayer != null)
mMediaPlayer.start();
if (!isLoadOk)
isLoadOk = true;
);
以上是关于Android MediaPlayer IllegalStateException源码分析定位的主要内容,如果未能解决你的问题,请参考以下文章
Android:MediaPlayer.setDataSource(FileDescriptor fd) vs MediaPlayer.setDataSource(FileDescriptor fd,
android的MediaPlayer.create方法怎么用