Android中的Audio播放:竞争Audio之Audio Focus的应用

来源:互联网 发布:李连杰 黄飞鸿 知乎 编辑:程序博客网 时间:2024/05/11 21:43

Android是多任务系统,Audio系统是竞争资源。Android2.2之前,没有内建的机制来解决多个程序竞争Audio的问题,2.2引入了称作AudioFocus的机制来管理对Audio资源的竞争的管理与协调。本文主要讲解AudioFocus的使用。

按照AudioFocus的机制,在使用Audio之前,需要申请AudioFocus,在获得AudioFocus之后才可以使用Audio;如果有别的程序竞争你正在使用的Audio,你的程序需要在收到通知之后做停止播放或者降低声音的处理。值得指出的是,这种机制是需要合作完成的,需要所有使用Audio资源的程序都按照这种机制来做,而如果有程序在它失去AudioFocus的时候仍然在使用Audio,AudioFocus拿它也没办法。而这一点对于开放系统的Android来说很致命的:用户可能安装没遵守这种机制的程序,或者版本太老还没引入这种机制的程序,这最终会导致很差的用户体验。

对于手机方案公司来说,要做的能做的事情就是教育和培训团队成员以保证自己内建的程序遵守机制没问题,这包括了Android原生的程序、自己开发的程序,以及适配第三方的程序。

 

一、AudioFocus的申请与释放

下面看与AudioFocus的相关的类:

AudioFocus AudioManager

获取/放弃AudioFocus的方法都在android.media.AudioManager中,获取AudioFocus用requestAudioFocus();用完之后,放弃AudioFocus,用abandonAudioFocus()

其中,参数

  •  streamType是《Android中的Audio播放:音量和远程播放控制》中说明的AudioStream,其值取决于AudioManager中的STREAM_xxx,在AudioStream的裁决机制中并未有什么实际意义;
  •  durationHint是持续性的指示:
    • AUDIOFOCUS_GAIN指示申请得到的Audio Focus不知道会持续多久,一般是长期占有;
    • AUDIOFOCUS_GAIN_TRANSIENT指示要申请的AudioFocus是暂时性的,会很快用完释放的;
    • AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK不但说要申请的AudioFocus是暂时性的,还指示当前正在使用AudioFocus的可以继续播放,只是要“duck”一下(降低音量)。
  •  AudioManager.OnAudioFocusChangeListener是申请成功之后监听AudioFocus使用情况的Listener,后续如果有别的程序要竞争AudioFocus,都是通过这个Listener的onAudioFocusChange()方法来通知这个Audio Focus的使用者的。

返回值,可能是:

  •  AUDIOFOCUS_REQUEST_GRANTED:申请成功;
  •  AUDIOFOCUS_REQUEST_FAILED:申请失败。

 

二、AudioFocus被抢占与重新获得

由上节中知道,申请/释放AudioFocus时传入了AudioManager.OnAudioFocusChangeListener这个参数,其onAudioFocusChange()方法是Audio Focus被抢占与再次获得通知的地方。所以,每个要使用AudioFocus的程序都要小心实现这个函数,保证AudioFocus实现的一致性。

onAudioFocusChange()方法的focusChange参数指示了该AudioFocus的竞争者对AudioFocus的拥有情况,取值如下:

  •  AUDIOFOCUS_GAIN:获得了Audio Focus;
  •  AUDIOFOCUS_LOSS:失去了Audio Focus,并将会持续很长的时间。这里因为可能会停掉很长时间,所以不仅仅要停止Audio的播放,最好直接释放掉Media资源。而因为停止播放Audio的时间会很长,如果程序因为这个原因而失去AudioFocus,最好不要让它再次自动获得AudioFocus而继续播放,不然突然冒出来的声音会让用户感觉莫名其妙,感受很不好。这里直接放弃AudioFocus,当然也不用再侦听远程播放控制【如下面代码的处理】。要再次播放,除非用户再在界面上点击开始播放,才重新初始化Media,进行播放。
  •  AUDIOFOCUS_LOSS_TRANSIENT:暂时失去Audio Focus,并会很快再次获得。必须停止Audio的播放,但是因为可能会很快再次获得AudioFocus,这里可以不释放Media资源;
  •  AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:暂时失去AudioFocus,但是可以继续播放,不过要在降低音量。

下面是onAudioFocusChange()方法处理的代码片段:

[java] view plaincopy
  1. OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {  
  2.     public void onAudioFocusChange(int focusChange) {  
  3.         if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT  
  4.             // Pause playback  
  5.         } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {  
  6.             am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);  
  7.             am.abandonAudioFocus(afChangeListener);  
  8.             // Stop playback  
  9.         } else if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {  
  10.             // Lower the volume  
  11.         } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {  
  12.             // Resume playback or Raise it back to normal  
  13.         }  
  14.     }  
  15. };  

 

三、典型的应用AudioFocus的场景

下面的时序图描述了AudioFocus被抢占与再次获取的典型场景:

AudioFocus Sequence

Audio Focus被抢占与再次获取的时序图

注意为了描述简单,此图中除了两个竞争Audio Focus的App之外,只用AudioManager表征了Android的AudioFocus机制中内部参与的对象,实际AudioManager只是外部的表象,内部参与的对象很多,回调函数也并非简单的直接由AudioManager调用,其中还包含了复杂的IPC机制。

图中:

  • AudioFocus Client通过requestAudioFocus()获取AudioFocus,在获得AudioFocus之后,开始播放Audio[Step#1 ~ #2];
  • 其它程序(Other App)也通过requestAudioFocus()获取AudioFocus [Step#3]
  • AudioFocus Client失去了Audio Focus,在onAudioFocusChanged()中,根据focusChange【focusChange的值与Other App申请时的durationHint相反,即focusChange = -1*durationHint】的值,做第二节中所描述的处理[Step#4];
  • 其它程序(Other App)获取Audio Focus之后,开始播放Audio[Step#5];
  • 其它程序(Other App)使用Audio之后,通过abandonAudioFocus()归还AudioFocus [Step#6];
  • AudioFocus Client重新获得了Audio Focus,可做进一步的处理 [Step#7]

 

小结

Audio Focus机制要参与各方充分理解并统一遵照施行,有没有遵照者或者实现有误的程序存在就可能打破这一机制,带来糟糕的用户体验。在保证Built-in程序没问题的前提下,如果进入AndroidMarket之前的程序都严格执行了AudioFocus相关的测试,应该也没问题。

使用Audio的程序要做到:

  • 使用前,用requestAudioFocus()申请AudioFocus,并根据应用的实际选取恰当的durationHint值;
  • 正确的在AudioManager.OnAudioFocusChangeListener中响应AudioFocus失去和重新获取事件;
  • Audio使用结束,用abandonAudioFocus()归还AudioFocus。

 

问题点以及进一步的探讨

  • 内部裁决机制怎样的?
  • 申请的不同Audio Stream之间是不存在竞争的吗?

 【更新2012-04-05】

1. AudioFocus中虽然把AudioStream作为参数,但是AudioFocus的内部裁决机制并未针对AudioStream做什么特别的处理。AudioFocus的处理针对所有的申请者来说的,除了它自身内部作为Alert的申请者有点特殊外,其它一律平等。所以文中,去掉AudioStream的描述。

2. 阅读AudioFocus内部实现机制后,对一些描述更加明确化。


举例

打电话进来试试看,然后不接,把电话挂了。这是为了模拟AudioFocus的获取,丢失与重新获取。
【1】Sample Code
public class MainActivity extends Activity{
    
    public AudioManager mAudioManager;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mAudioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
    }


    @Override
    protected void onResume() {
        // TODO Auto-generated method stub
        super.onResume();
        
        Button button1 = (Button)this.findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            
            public void onClick(View v) {
                // TODO Auto-generated method stub
                mAudioManager.requestAudioFocus(mAudioFocusListener,
                        AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN);
                Log.d("Test", "requestAudioFocus: onClick");
            }
        });
        
        Button button2 = (Button)this.findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            
            public void onClick(View v) {
                // TODO Auto-generated method stub
                mAudioManager.abandonAudioFocus(mAudioFocusListener);
                Log.d("Test", "abandonAudioFocus: onClick");
            }
        });
        
        
    }
    
    public AudioManager.OnAudioFocusChangeListener mAudioFocusListener = new AudioManager.OnAudioFocusChangeListener() {
        public void onAudioFocusChange(int focusChange) {
            // AudioFocus is a new feature: focus updates are made verbose on purpose
            switch (focusChange) {
                case AudioManager.AUDIOFOCUS_LOSS:
                    Log.d("Test", "AudioFocus: received AUDIOFOCUS_LOSS");
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                    Log.d("Test", "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                    Log.d("Test", "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
                    break;
                case AudioManager.AUDIOFOCUS_GAIN:
                    Log.d("Test", "AudioFocus: received AUDIOFOCUS_GAIN");
                    break;
                default:
                    Log.d("Test", "Unknown audio focus change code");
            }
        }
    };
}


【3】那么你看的Log应该是:电话打进来的时候出现: AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT电话挂掉的时候出现:AudioFocus: received AUDIOFOCUS_GAIN
【4】所以,你现在应该理解了。至于具体它是如何在另外一个App释放的时候,自己重新获取的,需要去看AudioManger的原代码。在你不了解它为何会这样的时候,写个小的程序验证一下,就知道了。


原创粉丝点击