Android MediaPlayer IllegalStateException源码分析定位

Posted 新根

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
                        
                    
                
            );

以上是关于Android MediaPlayer IllegalStateException源码分析定位的主要内容,如果未能解决你的问题,请参考以下文章

Android:MediaPlayer.setDataSource(FileDescriptor fd) vs MediaPlayer.setDataSource(FileDescriptor fd,

android的MediaPlayer.create方法怎么用

Android开发---MediaPlayer简单音乐播放器

Android MediaPlayer错误码整理

Android:MediaPlayer 未发布就完成了

Android -- MediaPlayer内部实现简析