最近发现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 mp = getMediaPlayer(env, thiz);
if (mp == NULL ) {
//关键点:mp 为空,则抛出异常
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return;
}
//.......
}
从上面代码来看,获取MediaPlayer*
指针对象为空,导致抛出IllegalStateException异常。
接下来看下该方法:
static sp getMediaPlayer(JNIEnv* env, jobject thiz)
{
Mutex::Autolock l(sLock);
MediaPlayer* const p = (MediaPlayer*)env->GetLongField(thiz, fields.context);
return sp(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 setMediaPlayer(JNIEnv* env, jobject thiz, const sp& player)
{
Mutex::Autolock l(sLock);
sp 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 mp = sp::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 mp = setMediaPlayer(env, thiz, 0);
//....
}
在上面的代码中会对mNativeContext释放为空的操作,因此判断,问题是调用了该方法导致的。
接下来,查看下该方法被哪里调用。
static const JNINativeMethod gMethods[] = {
{"_release", "()V", (void *)android_media_MediaPlayer_release}
}
在java 层的MediaPlayer中release()
中会调用到native层的_release()
。
基本上可以推断出,导致该问题是原因是调用release()
释放资源后,又调用start()
导致异常。
查看下项目中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;
}
}
}
});
}