Android MediaPlayer IllegalStateException源码分析定位

1.源码分析,定位报错点

最近发现Bugly上存在一个长期的bug:

java.lang.IllegalStateExceptionandroid.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;
                        }
                    }
                }
            });
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值