Android语音消息播放(MediaPlayer) 踩坑

引言

最近项目中的IM模块收到反映,语音消息点了之后正在播放却没有声音,有时甚至直接ANR异常,因项目中的IM采用的是网易的云信,所以第一时间请教了云信的技术人员,得到的回复是他们的SDK播放语音是直接封装调用了系统的Api,没有做任何处理。既然这样,那就只好自己研究下问题啦

问题定位

首先从IM的SDK中的语音播放类入手,发现确实是调用了Android的系统语音播放。

 

IM的SDK源码


那么我们去看一下Android的媒体播放类MediaPlayer的这几个方法的源码,分析一下问题,先看一下MediaPlayer的setDataSource方法,

 

setDataSource

通过注释可以看到,这个方法是支持传递本地文件路径或者是一个网络路径的,猜测是否是因为在ui线程加载网络资源,导致了anr,我们接着往下看

 


setDataSource的重载方法里对传入的数据来源做了区分,最后调用了native的setDataSource方法。

 

然后我们看一下prepare方法

从注释可以看到,prepare方法还有另外一个prepareAsync方法,

 


根据注释可以看到,prepareAsync方法是异步的去准备资源,基本验证了我们之前的猜想,因为他们最终都是调用了c++层的代码,这里我们直接去看一下他们的源码

 

源码位置frameworks/av/media/libmedia/mediaplayer.cpp

status_t MediaPlayer::prepare()
{
    ALOGV("prepare");
    Mutex::Autolock _l(mLock);
    mLockThreadId = getThreadId();
    if (mPrepareSync) {
        mLockThreadId = 0;
        return -EALREADY;
    }
    mPrepareSync = true;
    status_t ret = prepareAsync_l();
    if (ret != NO_ERROR) {
        mLockThreadId = 0;
        return ret;
    }

    if (mPrepareSync) {
        mSignal.wait(mLock);  // wait for prepare done
        mPrepareSync = false;
    }
    ALOGV("prepare complete - status=%d", mPrepareStatus);
    mLockThreadId = 0;
    return mPrepareStatus;
}
status_t MediaPlayer::prepareAsync()
{
    ALOGV("prepareAsync");
    Mutex::Autolock _l(mLock);
    return prepareAsync_l();
}

可以看到,不管是prepare还是prepareAsync方法,最终都是会调用prepareAsync_l(),但是prepare方法中多了这一段,

    if (mPrepareSync) {
        mSignal.wait(mLock);  // wait for prepare done
        mPrepareSync = false;
    }

在这里调用了wait方法进行了等待,所以使得java层达到同步调用的效果,然后在prepare完成之后会调用notify方法唤醒它,代码如下

void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj)
{
    ...
    case MEDIA_PREPARED:
        ALOGV("prepared");
        mCurrentState = MEDIA_PLAYER_PREPARED;
        if (mPrepareSync) {
            ALOGV("signal application thread");
            mPrepareSync = false;
            mPrepareStatus = NO_ERROR;
            mSignal.signal();
        }
        break;
}

解决方法

通过看源码,果然可以确定是因为prepare方法会直接在当前线程去读取资源,即使资源文件是一个网络资源,当网络条件比较差即弱网情况下时,那么发生ANR的几率就会十分高了,而且如果请求中断或者文件不完整,也会导致播放失败,解决方法之一的话可以采用下面的方式去播放一个语音

       MediaPlayer mediaPlayer = new MediaPlayer();
       mediaPlayer.setDataSource(url);
       mediaPlayer.prepareAsync();
       mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
           @Override
           public void onPrepared(MediaPlayer mp) {
               mp.start();
           }
       });

但是为了使对网络资源下载的过程可控,还是推荐大家自己做判断,使用自己的网络下载方式去下载资源文件然后再将其的本地路径交由MediaPlayer播放。

由于项目中的IM使用的是云信的SDK,所以我们也不好改动他们的代码,就只好在调用sdk的方法前自己先做判断,若是网络资源则先下载好才去调用sdk的方法,然后也向云信反映了这个问题,他们也表示应该做容错处理,应该会在后续版本改进吧。



作者:EoniJJ
链接:https://www.jianshu.com/p/8635e50858f4
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值