转自以下两篇 《深入理解Android:卷III A》一一3.4AudioFocus机制的实现 AudioFocus的申请与释放



  • 场景:当听音乐并且在刷朋友圈或者微博的时候,我们想点击一个视频看,此时音乐会暂停只保留视频播放,然后退出视频后音乐又重新播放;
    在Android2.2时引入了AudioFocus机制来对Audio资源的竞争进行管理与协调;需要Audio资源双方都实现该机制才能完成当一方失去或者得到焦点时,另一方能够进行反应:停止播放或者降低音量的效果;
  • 获取AudioManager实例:
    AudioManager magager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
  • 请求音频的焦点
    requestAudioFocus (AudioManager.OnAudioFocusChangeListener l,int streamType,int durationHint)



参数一:申请成功后对AudioFocus的监听;
参数二:音频类型
STREAM_ALARM :手机闹铃, STREAM_MUSIC :手机音乐
STREAM_RING :电话铃声, STREAM_SYSTEAM :手机系统
STREAM_DTMF :音调, STREAM_NOTIFICATION :系统提示
STREAM_VOICE_CALL :语音电话
参数三:
AudioManager.AUDIOFOCUS_GAIN :申请者希望长时间获得AudioFocus
AudioManager.AUDIOFOCUS_LOSS :  当前的使用者将长时间失去了Audio Focus,需要停止Audio的播放,并且释放Media资源。为了避免再次自动获得AudioFocus而继续播放,不然突然冒出来的声音会让用户感觉莫名其妙,直接放弃AudioFocus,如果需要再次播放,用户要在界面上点击开始播放,才重新初始化Media,进行播放。
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT :暂时失去AudioFocus,并会很快再次获得。必须停止Audio的播放,但是因为是暂时失去AudioFocus,可以不释放Media资源;
AUDIOFOCUS_GAIN_TRANSIENT :暂时获取焦点 适用于短暂的音频
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK :应用跟其他应用共用焦点但播放的时候其他音频会降低音量
返回值:
AUDIOFOCUS_REQUEST_GRANTED:申请成功;
AUDIOFOCUS_REQUEST_FAILED:申请失败;

  • 放弃音频的焦点
    abandonAudioFocus (AudioManager.OnAudioFocusChangeListenerl)
  • AudioFocus被抢占与再次获取的时序
  • Client通过requestAudioFocus()获取AudioFocus,在获得AudioFocus之后,开始播放Audio;
  • 其它程序(Other App)也通过requestAudioFocus()获取同类AudioStream的AudioFocus
  • Client失去了AudioFocus,在onAudioFocusChanged()中,根据focusChange的值,做相应的处理(暂停or播放);
  • 其它程序(Other App)获取AudioFocus之后,开始播放Audio;
  • 其它程序(Other App)使用Audio之后,通过abandonAudioFocus()归还AudioFocus;
  • Client重新获得了Audio Focus,可做进一步的处理

特殊情景:

AudioFocus的含义可以和Windows的窗口焦点机制做类比,只不过我们的焦点对象是音频的回放实例。在同一时间,只能有一个音频回放实例拥有焦点。每个回放实例开始播放前, 必须向AudioService申请获取AudioFocus,只有申请成功才允许开始回放。在回放实例播放结束后,要求释放AudioFocus。在回放实例播放的过程中,AudioFocus有可能被 其他回放实例抢走,这时,被抢走AudioFocus的回放实例需要根据情况采取暂停、静音或降低音量的操作,以突出拥有AudioFocus的回放实例的播放。当AudioFocus被还回来时, 回放实例可以恢复被抢走之前的状态,继续播放。 总体上来说,AudioFocus是一个没有优先级概念的抢占式的机制。在一般情况下后一个申请者都能从前一个申请者的手中获取AudioFocus。不过只有一个例外,就是通话。通话作 为手机的首要功能,同时也是一种音频的播放过程,所以从来电铃声开始到通话结束这个过程,Telephony相关的模块也会申请AudioFocus,但是它的优先级是最高的。Telephony 可以从所有人手中抢走AudioFocus,但是任何人无法从它手中将其夺回。这在后面的代码分析中可以看到。 值得一提的是,AudioFocus机制完全是一个建议性而不是强制性的机制。也就是说,上述的行为是建议回放实例遵守,而不是强制的。所以,市面上仍有一些带有音频播放功能的应 用没有采用这套机制。

例子1:

AudioFocus相关的API一共有三个,分别是requestAudioFocus()、abandonAudioFocus()以及AudioFocusListener回调接口,它们分别负责AudioFocus的申请、释放以及变化通知。
为了让大家了解AudioFocus的实现原理,我们先来看一个AudioFocus使用方法。下面是一个播放器的部分代码

public void play(){
    // 在开始播放前,先申请AudioFocus,注意传入的参数
    int result = mAudioManager.requestAudioFocus(
                                 mAudioFocusListener,AudioManager.STREAM_MUSIC,
                                 AudioManager.AUDIOFOCUS_GAIN);
    // 只有成功申请到AudioFocus之后才能开始播放
    if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
         mMediaPlayer.start();
    else // 申请失败,如果系统没有问题,这一定是正在通话过程中,所以,还是不要播放了
        showMessageCannotStartPlaybackDuringACall(); //提示用户暂时无法播放
}

public void stop() {
     // 停止播放时,需要释放AudioFocus
     mAudioManager.abandonAudioFocus(mAudioFocusListener);
}

在开始播放前,应用首先要申请AudioFocus。申请AudioFocus时传入了3个参数。
mAudioFocusListener:参数名为l。AudioFocus变化通知回调。当AudioFocus被其他AudioFocus使用者抢走或归还时将通过这个回调对象进行通知。
AudioManager.STREAM_MUSIC: 参数名为streamType。这个参数表明了申请者即将使用哪种流类型进行播放。目前这个参数仅提供一项信息而已,对AudioFocus的机制没有任何影响,不过Android在后续的升级中可能会使用此参数。
AudioManager.AUDIOFOCUS_GAIN: 参数名为durationHint。这个参数指明了申请者将拥有这个AudioFocus多长时间。例子中传入的AUDIOFOCUS_GAIN表明了申请者将长期占用这个AudioFocus。另外还有两个可能的取值,它们是AUDIOFOCUS_GAIN_TRANSIENT和AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK。这两个取值的含义都表明申请者将暂时占用AudioFocus,不同的是,后者还指示了即将被申请者抢走AudioFocus的使用者不需要暂停,只要降低一下音量就可以了(这就是“DUCK”的意思)。
在停止播放时,需要调用abandonAudioFocus释放AudioFocus,会将其归还给之前被抢 走AudioFocus的那个使用者。
接下来,我们看一下mAudioFocusListener是如何实现的。

private onAudioFocusChangeListenermAudioFocusListener =
                                                            newOnAudioFocusChangeListener(){
     // 当AudioFocus发生变化时,这个函数将会被调用。其中参数focusChange指示发生了什么变化
    publicvoidonAudioFocusChange(int focusChange){
        switch(focusChange) {
            // AudioFocus被长期夺走,需要中止播放,并释放AudioFocus
            // 这种情况对应于抢走AudioFocus的申请者使用了AUDIOFOCUS_GAIN
            case AUDIOFOCUS_LOSS:
                 stop();
                 break;
           // AudioFocus被临时夺走,不久就会被归还,只需要暂停,AudioFocus被归还后再恢
              复播放
           // 这对应于抢走AudioFocus的申请者使用了AUDIOFOCUS_GAIN_TRANSIENT
           case AUDIOFOCUS_LOSS_TRANSIENT:
                saveCurrentPlayingState();
                pause();
                break;
           // AudioFocus被临时夺走,允许不暂停,所以降低音量
           // 这对应于抢走AudioFocus的回放实例使用了AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
           case AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                saveCurrentPlayingState();
                setVolume(getVolume()/2);
                break;
           // AudioFocus被归还,这时需要恢复被夺走前的播放状态
           case AUDIOFOCUS_GAIN:
                restorePlayingState();
                break;
           }
}
};

从这里能够看出,AudioFocus机制的逻辑是完整而清晰的。理解上述例子以后,相信AudioFocus的工作原理已经浮现在脑海里了吧?如果有兴趣,可以不用着急阅读下面的分析,先思考一下AudioFocus可能的工作原理,然后再与Android的实现进行比较。
从调用requestAudioFocus()进行申请到abandonAudioFocus()释放的这段时间内,只能说这个使用者参与了AudioFocus机制,不能保证一直拥有AudioFocus。

释放焦点

private void abandonAudioFocus() {
    if (audioFocusState == AUDIO_FOCUS_STATE_NO_FOCUS) {
      return;
    }
    if (Util.SDK_INT >= 26) {
      abandonAudioFocusV26();
    } else {
      abandonAudioFocusDefault();
    }
    setAudioFocusState(AUDIO_FOCUS_STATE_NO_FOCUS);
  }

注意:app进程杀死之后,在AudioService中通过监听Binder mCallback也会自动释放焦点