概要

Android系统允许多个应用同时播放音频,这种特性有利有弊。例如当我们正在听音乐的时候突然点开了一个视频,如果我们发现音乐的声音和视频的的声音混合了在一起,这显然让我们非常不爽。而如果我们在播放音乐的时候,突然来了一条信息,这个时候,我们既希望能听到短信的提示音,又不希望音乐此刻停止,而是希望音乐可以降低音量播放以使我们能听清楚短信提示音,之后再恢复音量。Android引入音频焦点(audio focus)这一特性,可以让我们方便管理音频焦点,从而让app可以应对各种情况。

音频焦点具有协调型,也就是说在同一时刻,只有一个app可以获取音频焦点。当我们在开发一款Music应用时,如果我们想播放一首歌曲,最好先获取音频焦点,但是这并不是系统强制行为,然而如果我们不这样做,这可能就是个糟糕的应用,用户基本上都会卸载它。

如果我们获取了焦点,但是这并不意味着你能一直拥有它,其他应用也此时也可以请求焦点,那么你的应用可能会失去焦点,那么我们就应该做出相应的处理,例如暂停播放音乐,等到焦点再次获取焦点后,继续播放音乐。

设计原则

前面我们提到,如果你的App表现的很糟糕,用户就会卸载你的App,那么对于音频焦点的处理流程,大致可以归纳为一下几点:

  1. 在使用音频前(例如播放音乐),需要先获取焦点,如果成功获取到焦点,才应该正式开始工作。
  2. 当其他app请求了焦点,而此时我们的app失去焦点,我们需要做相应的处理。例如,如果是短暂型失去焦点,我们可以暂停工作,如果是永久性失去焦点,我们就停止工作。
  3. 当完成工作后,我们需要手动释放音频焦点。

版本兼容

从Android 8.0(O版本,API 26)开始,音频焦点的请求方式以及系统管理有了细微的变化,下面我分两部分来说明。

8.0 之前的实现

当你想要录音或者播放一首歌曲的时候,最好(注意是最好,不是强制)先请求音频焦点,这个时候你需要调用AudioManager.requestAudioFocus()方法,函数原型如下

public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)

第一个参数用于监听焦点的变化,例如你的app在工作期间可能失去焦点。

第二个参数表明请求的音频焦点影响的是那种类型流,例如,如果我们录音,我们肯定是要影响Music这一类型的音频流,因此可以选择AudioManager.STREAM_MUSIC。当然还有许多类型,例如与铃声有关的AudioManager.STREAM_RING,与通话有关的是AudioManager.STREAM_VOICE_CALL

第三个参数用于表明音频焦点的持续时间,这个很关键,它也有许多种类型,下面一一列出。

  1. AudioManager.AUDIOFOCUS_GAIN: API文档说请求的这类音频焦点持续时间是未知的,通常用来表示长时间获取焦点,可以用来播放音乐,录音等等。
  2. AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: 表明请求的音频焦点持续时间比较短,通常用来播放导航路线的声音,或者播放通知声音。
  3. AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: 这个也是表明请求的音频焦点持续时间比较短,但是在这段时间内,它希望其他应用以较低音量继续播放。例如,我们在使用导航的时候可以听音乐,当出现导航语音的时候,音乐音量会降低以便我们能听清楚导航的语音,当导航语音播放完毕后,音乐恢复音量,继续播放。
  4. AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: 这个也是表明音请求的音频焦点持续时间比较短,但是在这段时间内,不希望任何应用(包括系统应用)来做任何与音频相关的事情,就算是降低音量播放音乐也是不被希望的。例如当我们进行录音或者语音识别的时候,我们不希望其他的声音出现干扰。

AudioManager.requestAudioFocus()的返回值表明请求的结果,AudioManager.AUDIOFOCUS_REQUEST_FAILED表明请求焦点失败,AudioManager.AUDIOFOCUS_REQUEST_GRANTED表明请求焦点成功。

当我们成功请求焦点后,就可以做一些与音频有关的事情,例如播放音乐,录音,或者语音识别。当完成这些工作后,我们必须调用AudioManager.abandonAudioFocus(onAudioFocusChangeListener l)释放音频焦点。

8.0 之后实现

从Android 8.0开始(API 26),请求音频焦点的方式以及系统对音频焦点变化的管理有些微妙的变化。

首先,对音频焦点变化的管理的变化体现在两个方面,延迟获取焦点和自动降低音量。

音频焦点变化管理

延迟获取焦点


在Android 8.0之前,当我们请求音频焦点的时候,只会返回两种结果,要么请求成功(AUDIOFOCUS_REQUEST_GRANTED),要么请求失败(AUDIOFOCUS_REQUEST_FAILED)。

而从Android 8.0开始,还有一种结果,延迟成功请求(AUDIOFOCUS_REQUEST_DELAYED),这个也是成功的请求,但是这个请求具有延迟性。例如当我们处于通话状态的时候,我们很显然不希望任何app来获取到音频焦点来做些事,例如播放音乐。

然而只有设置过AudioFocusRequest.Builder.setAcceptsDelayedFocusGain(true)才能获取到这种结果,这个我们后面会讲到。那么我们怎么知道什么时候获取到了音频焦点呢,当然还需要设置AudioManager.OnAudioFocusChangeListener这个音频焦点变化的监听器,通过回调确认何时获取到了音频焦点。

自动降低音量


在Android 8.0之前,如果请求焦点使用了AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK参数,它表明希望拥有了音频焦点的其他应用降低音量来使用音频,然而并不是所有的应用都会这样做(有可能开发者忘记了优化),因为这并不是系统强制的。But, 从Android 8.0开始,这个降低音量的工作,就是系统默认行为了,可以说是一个良心的优化。

如果我不希望系统自动给我降低音量,而是想自己暂停音频相关的工作,那咋办?这个可以通过AudioFocusRequest.Builder.setWillPauseWhenDucked(true)方法取消系统的默认行为,然后通过监听音频焦点变化,来自己处理,这个我们后面也会讲到。

音频焦点请求方式

请求和释放音频焦点的方法名没有改变,都是用AudioManager.requestAudioFocus()请求焦点,用AudioManager.releaseAudioFocus()来释放焦点,但是这两个方法在Android 8.0后需要传入一个AudioFocusRequest对象,这个对象的构造方式使用的是Builder模式来设置参数,下面看下一些设置参数的方法:

  1. setFocusGain(): 只有这个方法是必须的,而传入的参数与8.0之前使用AudioManager.requestAudioFocus()传入的第三个参数一样,都是用来表示持续时间。
  2. setAudioAttributes(): 这个方法是用来描述app的使用情况。这方法需要传入一个AudioAttributes对象,这个对象也是使用Builder模式来构造,例如使用AudioAttributes.Builder.setUsage()来描述使用这个音频来干什么,我们可以传入一个AudioAttributes.USAGE_MEDIA来表明用这个音频来作为媒体文件来播放,也可以传入一个AudioAttributes.USAGE_ALARM来表明用这个来作为闹铃。
  3. setWillPauseWhenDucked(): 这个前面说过,是为了覆盖系统默认降低音量的行为,但是必须要设置AudioManager.OnAudioFocusChangeListener才能自己处理这类情况。
  4. setAcceptsDelayedFocusGain(): 这个前面也说过,这个是为了能够延迟获取到焦点的必须条件,但是同时也必须要设置AudioManager.OnAudioFocusChangeListener才能得知何时获取到焦点。
  5. setOnAudioFocusChangeListener(): 音频焦点变化监听器。值得一提的是这个方法有个重载的方法,有一个重载方法有两个参数,第二个参数为Handler对象,看到Handler应该明白了,是为了使用它的消息队列来顺序处理这个回调。

监听音频焦点的变化

在请求音频焦点的时候,我们需要监听焦点的变化,以便做出正确的行为,我们需要设置并实现OnAudioFocusChangeListener接口。

    public interface OnAudioFocusChangeListener {public void onAudioFocusChange(int focusChange);}

根据参数int focusChange的值,我们需要做出合适的行为,而这些值大致可以分为两种类型,获取焦点和失去焦点。获取焦点比较简单,它的值是AudioManager.AUDIOFOCUS_GAIN。而失去焦点又分为两种类型,一种是短暂失去焦点,另一种是永久失去焦点。

短暂失去焦点


这种类型包括AudioManager.AUDIOFOCUS_LOSS_TRANSIENTAudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK。如果是AudioManager.AUDIOFOCUS_LOSS_TRANSIENT,我们应该暂停音乐的播放,而如果是AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK,我们应该降低音量继续播放,当然根据工作内容的不同,相应的处理也会有所不同。

如果我们还想恢复状态继续工作,那么在短暂失去音频焦点后,应该持续监听音频焦点的变化,当我们再次获取焦点的时候,就恢复正常的工作状态,例如调整音量播放以你月,或者恢复播放暂停的音乐。就如同我们前面举个导航和音乐的例子,当有导航声音的时候,音乐需要降低音量播放,当导航声音播放完毕,音乐需要恢复音量。

永久失去焦点


这种类型是指AudioManager.AUDIOFOCUS_LOSS,当遇到这种功能情况,我们需要立即暂停,甚至有时候需要停止正在进行的工作,因为我们将不会再获取到焦点。那么为了恢复正常工作,需要用户去手动做这些事。例如,当我们正在听音乐,我们此时打开了一个视频App,这个时候音乐App会失去音频焦点并暂停播放,当我们关闭视频后,音乐App并不会再次获取到音频焦点,因此需要用户手动去点击播放。

代码实现

我在项目中处理录音的时候,需要去请求焦点,于是我做了一个简单的封装,代码如下

public class AudioFocusManager implements AudioManager.OnAudioFocusChangeListener {private AudioManager mAudioManager;private AudioFocusRequest mFocusRequest;private AudioAttributes mAudioAttributes;private onRequestFocusResultListener mOnRequestFocusResultListener;private OnAudioFocusChangeListener mAudioFocusChangeListener;public AudioFocusManager(Context context) {mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);}/*** Request audio focus.*/public void requestFocus() {int result;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {if (mFocusRequest == null) {if (mAudioAttributes == null) {mAudioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build();}mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).setAudioAttributes(mAudioAttributes).setWillPauseWhenDucked(true).setOnAudioFocusChangeListener(this).build();}result = mAudioManager.requestAudioFocus(mFocusRequest);} else {result = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);}if (mOnRequestFocusResultListener != null) {mOnRequestFocusResultListener.onHandleResult(result);}}@Overridepublic void onAudioFocusChange(int focusChange) {if (mAudioFocusChangeListener != null) {mAudioFocusChangeListener.onAudioFocusChange(focusChange);}}/*** Release audio focus.*/public void releaseAudioFocus() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {mAudioManager.abandonAudioFocusRequest(mFocusRequest);} else {mAudioManager.abandonAudioFocus(this);}}/*** Handle the result of audio focus.*/public interface onRequestFocusResultListener {void onHandleResult(int result);}public void setOnHandleResultListener(onRequestFocusResultListener listener) {mOnRequestFocusResultListener = listener;}/*** Same as AudioManager.OnAudioFocusChangeListener.*/public interface OnAudioFocusChangeListener {void onAudioFocusChange(int focusChange);}public void setOnAudioFocusChangeListener(OnAudioFocusChangeListener listener) {mAudioFocusChangeListener = listener;}}

如果你是为了播放音频,还需要做一些相应的改变,可以参考前面讲过的理论知识。

参考

https://developer.android.google.cn/guide/topics/media-apps/audio-focus

Android音频焦点处理相关推荐

  1. Android音频焦点申请处理

    为了便于理解,我们以android的8.0以前的版本为例,8.0以后有一定改动,但是基本思路一样. 关于管理音频焦点(8.0以前和更高版本)的官方文档:https://developer.androi ...

  2. Android 音频焦点(Audio Focus)

    原址 CONTENTS 引子 音频焦点 一个简单的示例 注意: 引子 说 Audio Focus 前先说个很简单需求:来电时暂停正在播放的音乐,电话结束时恢复播放. 看到这个需求,第一反应肯定是:监听 ...

  3. Android音频焦点

    因为系统中可能会有多个应用程序会播放音频,所以需要考虑他们之间该如何交互,为了避免多个应用程序同时播放 音乐,Android 系统使用音频焦点来进行统一管理,即只有获得了音频焦点的应用程序才可以播放音 ...

  4. Android音频焦点AudioFocus使用

    Android开发中免不了需要播放视频,音频文件,但是手机上可能有其他的一些软件,在后台播放音频的时候,这个时候另外的软件也需要播放音频,这个时候就会出现俩个音频同时播放的问题,在Android2.2 ...

  5. android音频焦点Audio Focus

    为了便于理解,我们以android的8.0以前的版本为例,8.0以后有一定改动,但是基本思路一样. 关于管理音频焦点(8.0以前和更高版本)的官方文档:https://developer.androi ...

  6. Android音频焦点及混音策略

    1.前言 1.1 音频焦点官方解读 两个或两个以上的 Android 应用可同时向同一输出流播放音频.系统会将所有音频流混合在一起.虽然这是一项出色的技术,但却会给用户带来很大的困扰.为了避免所有音乐 ...

  7. Android 音频焦点处理

    刚开始的时候,认为在智能机中,每个 APP 都是各自管各自的,媒体播放也是这样子的:然而对比同类产品,发现同类产品可以放到播放自如,体验很好,通过对比研究,根源就在于音频焦点处理上. 一.引言 在功能 ...

  8. Android 9.0失去音频焦点后不能再获取到焦点

    问题 在9.0版本上,A先申请音频焦点,B先申请再释放音频焦点后,A也不能收到onAudioFocusChange回调,导致不能继续播放. 但是在7.1版本,这种情况是会正常播放的. 分析过程 查看源 ...

  9. Android官方开发文档Training系列课程中文版:管理音频播放之管理音频焦点

    原文地址:http://android.xsoftlab.net/training/managing-audio/audio-focus.html 因为可能会存在多个APP播放音频,所以考虑它们之间的 ...

最新文章

  1. 支付宝 php rsa算法,:PHP支付宝接口RSA验证
  2. jquery 和js 还有 jq 挂事件
  3. POJ 1321 棋盘问题 题解
  4. 微信开发--one.微信平台验证
  5. React Native的安装和初始化(android /ios)
  6. Kafka集群部署详细步骤(包含zookeeper安装步骤)
  7. 爱因斯坦诞辰140周年:带你走近一个真实的爱神
  8. python 整数逆位运算_Python 进制转换、位运算
  9. DPDK服务核心(coremask)
  10. 《TCP/IP详解卷1》学习小结(一)------链接层
  11. 印地语自由对话语音识别数据库-200人
  12. linux系统下安装游戏,在Linux系统下安装GameHub来管理所有游戏
  13. 计算机辅助英语教学电子版,计算机辅助英语教学探究.pdf
  14. 火狐无法安装扩展_立即安装的前5个Firefox扩展
  15. libmodbus之嵌入式Linux使用及测试
  16. css直角线_CSS秘密花园:折角效果
  17. 种草平台--持续更新
  18. Hanselminutes Podcast 244-Benjamin van der Veen的Kayak,OWIN,开源Web服务器等
  19. 还有比元宇宙更牛的商业模式吗?
  20. 容器内存溢出排障思路

热门文章

  1. 像科学家一样思考python_像……像造句(精选50句)
  2. 兰州理工大学计算机专业课,兰州理工大学计算机专业复试科目
  3. 发现生活中的肖特基二极管
  4. 一人之下合鸿蒙技巧,一人之下:碧游村马大姐竟然会三个八奇技,网友:怪不得这么强!...
  5. 点击li任意项下拉隐藏-toggle
  6. 小程序毕设作品之微信美食菜谱小程序毕业设计成品(4)开题报告
  7. 企业微信H5唤起(打开)微信小程序
  8. c生万物【第一章 初识c语言】
  9. 用移动硬盘安装win7(制作启动盘)
  10. python系统自学_如何系统地自学 Python?