音频焦点 Android Audio Focus_android 音频焦点
Android 音频焦点详解
音频焦点(Audio Focus)是 Android 系统用于协调多个应用同时访问音频输出的机制。当多个应用需要播放音频时,音频焦点确保用户听到的内容不会混乱(如多个音乐应用同时播放)。
一、音频焦点的核心概念
-
音频焦点的类型
- 永久性焦点:长时间占用焦点(如音乐播放器)。
- 短暂性焦点:临时占用焦点(如导航提示音)。
- Ducking:短暂降低其他应用音量(如通知音)。
-
焦点请求类型
AUDIOFOCUS_GAIN
:请求长期焦点,其他应用需停止播放。AUDIOFOCUS_GAIN_TRANSIENT
:短暂占用焦点,其他应用需暂停。AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
:短暂占用焦点,其他应用降低音量(Ducking)。AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
:短暂独占焦点(如语音录制)。
-
requestAudioFocus 返回值
- AUDIOFOCUS_REQUEST_GRANTED:请求成功
- AUDIOFOCUS_REQUEST_FAILED:请求失败
- AUDIOFOCUS_REQUEST_DELAYED:焦点延迟授予(需设置 AUDIOFOCUS_FLAG_DELAY_OK)
-
焦点丢失处理
当其他应用请求焦点时,当前应用需根据情况暂停播放、停止播放或降低音量。
二、代码实现与分析
1. 请求音频焦点
使用 AudioManager
请求焦点,并监听焦点变化。
2. 音频焦点变化返回值
在播放结束或应用暂停时释放焦点:
当焦点状态变化时,系统通过此回调通知应用:
AUDIOFOCUS_LOSS
:永久失去焦点(应停止播放并释放资源)。AUDIOFOCUS_LOSS_TRANSIENT
:暂时失去焦点(应暂停播放)。AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
:暂时失去焦点,但可降低音量继续播放。AUDIOFOCUS_GAIN
:重新获得焦点(恢复播放)。
// 初始化 AudioManagerAudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);// 创建焦点变化监听器AudioManager.OnAudioFocusChangeListener afChangeListener = new AudioManager.OnAudioFocusChangeListener() { @Override public void onAudioFocusChange(int focusChange) { switch (focusChange) { case AudioManager.AUDIOFOCUS_GAIN: // 重新获得焦点,恢复播放 mediaPlayer.start(); mediaPlayer.setVolume(1.0f, 1.0f); break; case AudioManager.AUDIOFOCUS_LOSS: // 永久丢失焦点,停止播放并释放资源 mediaPlayer.stop(); audioManager.abandonAudioFocus(afChangeListener); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: // 暂时丢失焦点,暂停播放 mediaPlayer.pause(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: // 短暂降低音量 mediaPlayer.setVolume(0.2f, 0.2f); break; } }};//requestAudioFocus//作用:应用通过此方法请求音频焦点,表明它希望播放音频//参数://OnAudioFocusChangeListener:焦点变化时的回调监听器//streamType:音频流类型(如 STREAM_MUSIC)//durationHint:焦点持有类型(如 AUDIOFOCUS_GAIN 表示长期占用)//返回值://AUDIOFOCUS_REQUEST_GRANTED:请求成功//AUDIOFOCUS_REQUEST_FAILED:请求失败//AUDIOFOCUS_REQUEST_DELAYED:焦点延迟授予(需设置 AUDIOFOCUS_FLAG_DELAY_OK)// 请求音频焦点(以 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 为例)int result = audioManager.requestAudioFocus( afChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { // 焦点获取成功,开始播放 mediaPlayer.start();} else { // 焦点获取失败,处理逻辑}
三、实际使用
一、通话打断音乐的流程
-
电话应用的优先级
通话属于高优先级音频场景,系统会强制其他应用让出音频焦点。当来电时,电话应用会请求AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
类型的焦点(短暂独占),以确保通话音频的独占性。 -
音乐播放器的响应
音乐播放器在失去焦点时,会通过注册的OnAudioFocusChangeListener
收到AUDIOFOCUS_LOSS
或AUDIOFOCUS_LOSS_TRANSIENT
回调,此时需暂停播放。 -
通话结束后的恢复
当通话结束时,电话应用释放焦点,音乐播放器可能重新获得焦点(需主动重新请求),恢复播放。
四、framwork 跟 native
1. AudioFocus 机制的关键角色
- AudioManager:
- 应用与 AudioFocus 系统交互的主要入口点。
- 提供
requestAudioFocus()
/abandonAudioFocus()
/requestAudioFocusRequest()
等 API。 - 持有
IAudioService
的代理(Binder 对象),用于跨进程调用AudioService
。
- AudioService:
- 系统服务 (
com.android.server.audio.AudioService
),运行于system_server
进程。 - 管理整个系统的音频状态、策略、路由和焦点。
- 通过 Binder (
IAudioService
) 接收来自应用的请求。 - 内部核心组件:
MediaFocusControl
- 实际管理焦点栈、处理请求和分发焦点变更通知的逻辑中心。
- 系统服务 (
- MediaFocusControl:
AudioService
的内部类,是 AudioFocus 机制的“大脑”。- 维护焦点栈 (
mFocusStack
): 一个Stack
,栈顶元素拥有当前音频焦点。栈大小有限制(MAX_STACK_SIZE=100)。 - 处理请求: 接收
requestAudioFocus()
和abandonAudioFocus()
请求。 - 分发通知: 当焦点状态变化时(如新应用获得焦点、原有应用丢失焦点),通知相应的应用。
- 协调特殊状态: 如电话状态 (
mRingOrCallActive
)。
- FocusRequester:
- 表示一个音频焦点请求者。
- 可参考代码
//创建新的焦点请求对象final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb, clientId, afdh, callingPackageName, uid, this, sdk, mEventLogger);
- IAudioFocusDispatcher:
- 一个 AIDL 接口 (
android.media.IAudioFocusDispatcher
)。 - 客户端实现 (
AudioManager.mAudioFocusDispatcher
): 一个Stub
对象,运行在应用进程。当MediaFocusControl
需要通知应用焦点变化时,通过 Binder 调用它的dispatchAudioFocusChange(int focusChange, String clientId)
方法。 - 服务端持有 (
FocusRequester.mFocusDispatcher
):MediaFocusControl
通过FocusRequester
持有该接口的代理,指向客户端的Stub
。
- 一个 AIDL 接口 (
- PlaybackActivityMonitor:
AudioService
的内部类,负责跟踪系统中所有活跃的音频播放器 (AudioTrack
和MediaPlayer
)。- 维护播放器列表 (
mPlayers
): 一个Map
,key 是播放器 ID (piid
),value 是播放器状态信息。 - 强制执行策略: 在特定场景下(如电话呼入时),直接操作播放器实例(如调用
setVolume(0f)
静音)来实现系统级行为,即使应用没有正确处理焦点丢失。
- AudioAttributes / StreamType:
- AudioAttributes (推荐): Android 5.0 (API 21+) 引入的更强大的方式,描述音频的使用场景 (
usage
)、内容类型 (contentType
)、标志等。更精确地指导音频路由和焦点行为。博客强调: 在 Android 9.0+ 的requestAudioFocusRequest()
中,AudioAttributes
用于确定底层播放通路。 - StreamType (旧方式): 传统的分类方式(如
STREAM_MUSIC
,STREAM_RING
)。在旧的requestAudioFocus(listener, streamType, focusGain)
方法中使用。博客强调: 此方法中的streamType
参数用于确定底层播放通路。
- AudioAttributes (推荐): Android 5.0 (API 21+) 引入的更强大的方式,描述音频的使用场景 (
2. 焦点变更回调 (onAudioFocusChange)
当应用因他人请求焦点或自己释放焦点而导致其焦点状态改变时,通过注册的 OnAudioFocusChangeListener
会收到回调。参数 focusChange
表示具体的变更:
AUDIOFOCUS_GAIN
: 应用获得了(或重新获得了)焦点。应开始播放或恢复播放(如从暂停状态恢复,或提高之前被 Ducked 的音量)。AUDIOFOCUS_LOSS
: 应用永久丢失了焦点。通常是因为另一个应用请求了AUDIOFOCUS_GAIN
。应立即停止播放并释放相关资源(如MediaPlayer
)。之后如果用户操作需要播放,应重新请求焦点。AUDIOFOCUS_LOSS_TRANSIENT
: 应用暂时丢失了焦点。通常是因为另一个应用请求了AUDIOFOCUS_GAIN_TRANSIENT
或AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
。应暂停播放,但保留资源(不释放MediaPlayer
),准备在焦点回来时快速恢复。AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
: 应用暂时丢失了焦点,并被允许(或要求)降低音量(Ducking)。通常是因为另一个应用请求了AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
。应将音量降低到一个较低水平(通常是原音量的 1/3 或更低)。在焦点回来时恢复原音量。- (较少见)
AUDIOFOCUS_REQUEST_FAILED
/AUDIOFOCUS_REQUEST_DELAYED
: 请求焦点时返回的状态码,表示请求失败或被外部策略延迟,通常不会在onAudioFocusChange
中收到。
3. 源码流程详解
- 请求焦点 (
requestAudioFocus
/requestAudioFocusRequest
):- App -> AudioManager: App 调用
AudioManager
方法。 - AudioManager -> AudioService (Binder):
AudioManager
通过IAudioService
Binder 调用AudioService.requestAudioFocus()
。 - AudioService -> MediaFocusControl:
AudioService
将请求委托给MediaFocusControl.requestAudioFocus()
。 MediaFocusControl
处理逻辑 (synchronized(mAudioFocusLock)
):- 检查焦点栈大小: 超过
MAX_STACK_SIZE
(100) 则失败返回。 - 处理电话状态 (
enteringRingOrCall
): 检查是否为系统电话请求 (clientId == AudioSystem.IN_VOICE_COMM_FOCUS_ID
),并设置mRingOrCallActive
。 - 注册死亡监听 (
AudioFocusDeathHandler
): 监听客户端 Binder 死亡,以便在其崩溃时清理焦点。 - 移除同
clientId
的旧焦点:removeFocusStackEntry(clientId, false, false)
。关键点: 确保栈中同一clientId
只存在一个焦点请求,避免栈溢出,提倡OnAudioFocusChangeListener
复用。 - 创建
FocusRequester
(nfr
): 封装请求信息。 - 处理焦点栈顶变更: 如果栈非空(即有应用正在持有焦点),调用
propagateFocusLossFromGain_syncAf()
。此方法遍历栈(主要是当前栈顶元素),调用其handleFocusLossFromGain()
->handleFocusLoss()
。 - 新焦点入栈:
mFocusStack.push(nfr)
。 - 通知新焦点获得者:
nfr.handleFocusGainFromRequest(AUDIOFOCUS_REQUEST_GRANTED)
。 - 强制执行电话静音: 如果是电话请求 (
enteringRingOrCall
),调用runAudioCheckerForRingOrCallAsync(true)
(延迟执行),最终在PlaybackActivityMonitor.mutePlayersForCall()
中遍历mPlayers
,将匹配的播放器 (如USAGE_MEDIA
,USAGE_GAME
) 音量设为 0 (apc.getPlayerProxy().setVolume(0.0f)
),并记录在mMutedPlayers
中。关键点: 这是系统级强制行为,独立于应用对焦点丢失的回调处理。
- 检查焦点栈大小: 超过
FocusRequester
通知原焦点丢失者: 在步骤 4 的handleFocusLoss()
中,通过mFocusDispatcher.dispatchAudioFocusChange(focusLoss, clientId)
(Binder 调用) 通知原焦点持有者的AudioManager.mAudioFocusDispatcher
。AudioManager
回调 App:mAudioFocusDispatcher
收到调用后,根据请求时注册的Handler
(或默认的主线程Handler
),向该 Handler 发送消息。最终在该 Handler 的线程中,调用 App 注册的OnAudioFocusChangeListener.onAudioFocusChange(focusLoss)
。关键点:Handler
的选择决定了回调线程,默认在主线程,可通过AudioFocusRequest.Builder.setHandler()
指定工作线程 Handler 避免主线程阻塞。
- App -> AudioManager: App 调用
- 释放焦点 (
abandonAudioFocus
/abandonAudioFocusRequest
):
播放结束后调用- App -> AudioManager: App 调用
AudioManager
方法。 - AudioManager -> AudioService (Binder):
AudioManager
通过IAudioService
Binder 调用AudioService.abandonAudioFocus()
。 - AudioService -> MediaFocusControl:
AudioService
将请求委托给MediaFocusControl.abandonAudioFocus()
。 MediaFocusControl
处理逻辑 (synchronized(mAudioFocusLock)
):- 处理退出电话状态 (
exitingRingOrCall
): 类似请求逻辑。 - 移除焦点:
removeFocusStackEntry(clientId, true, true)
。关键点:signal=true
表示移除后需要通知新的栈顶。 - 解除电话静音: 如果是电话释放 (
exitingRingOrCall
),调用runAudioCheckerForRingOrCallAsync(false)
,最终在PlaybackActivityMonitor.unmutePlayersForCall()
中遍历mMutedPlayers
,恢复对应播放器的音量 (apc.getPlayerProxy().setVolume(1.0f)
) 并清空列表。
- 处理退出电话状态 (
- 通知新的栈顶获得者: 在
removeFocusStackEntry()
中,如果signal=true
且栈非空,调用notifyTopOfAudioFocusStack()
->mFocusStack.peek().handleFocusGain(AUDIOFOCUS_GAIN)
->fd.dispatchAudioFocusChange(AUDIOFOCUS_GAIN, mClientId)
(Binder 调用)。 AudioManager
回调新焦点获得者: 过程同请求流程的第 6 步,最终调用新栈顶应用注册的onAudioFocusChange(AUDIOFOCUS_GAIN)
。
- App -> AudioManager: App 调用
- 播放器跟踪 (
AudioTrack
/MediaPlayer
创建):- 创建
AudioTrack
/MediaPlayer
时,在构造函数中会调用父类PlayerBase.baseRegisterPlayer()
。 PlayerBase
-> AudioService (Binder): 通过IAudioService
调用AudioService.trackPlayer()
。- AudioService -> PlaybackActivityMonitor:
AudioService
委托给mPlaybackMonitor.trackPlayer()
。 PlaybackActivityMonitor
记录播放器: 生成新的piid
,创建AudioPlaybackConfiguration(apc)
对象,并将其放入mPlayers
Map (mPlayers.put(newPiid, apc)
)。关键点: 这使得系统能够全局跟踪所有活跃播放器,为强制执行策略(如电话静音)提供了基础。
- 创建
4. 成员关系
clientId
与OnAudioFocusChangeListener
复用:clientId
由getIdForAudioFocusListener(listener)
生成。强烈建议复用同一个OnAudioFocusChangeListener
实例。 原因:- 避免在栈中创建多个相同逻辑的焦点请求,浪费栈空间。
- 焦点栈大小有限 (100),复用
listener
能确保同一个应用的多次请求只占一个栈位,防止栈满导致后续请求失败 (AUDIOFOCUS_REQUEST_FAILED
)。 - 便于统一管理焦点状态。
Handler
的重要性 (Android 9.0+): 使用AudioFocusRequest.Builder.setHandler(mHandler)
指定回调的Handler
。- 默认行为: 若不指定,回调使用与主线程
Looper
绑定的Handler
,即在主线程执行。 - 风险: 如果主线程繁忙(如处理复杂 UI 或 I/O),焦点变更回调可能被延迟,导致应用响应不及时(如暂停/恢复延迟),甚至 ANR。
- 最佳实践: 创建一个与工作线程
Looper
绑定的Handler
并传入,确保焦点回调在非主线程及时执行。
- 默认行为: 若不指定,回调使用与主线程
- 电话焦点的特殊性 (
AUDIOFOCUS_FLAG_LOCK
):- 独占性: 只有系统电话应用(拥有
MODIFY_PHONE_STATE
权限)才能通过requestAudioFocusForCall()
请求电话焦点 (clientId = AudioSystem.IN_VOICE_COMM_FOCUS_ID
)。 - 强制静音: 当电话激活 (
mRingOrCallActive = true
) 时,PlaybackActivityMonitor
会直接强制将所有非电话相关的活跃播放器(如USAGE_MEDIA
,USAGE_GAME
)静音 (setVolume(0f)
),无论这些播放器所属的应用是否正确处理了焦点丢失回调。这是系统级别的强制策略。 - 抢占失败: 在电话激活期间,其他应用的常规
requestAudioFocus()
请求会失败。 - 最佳实践: 应用应正确处理
AUDIOFOCUS_LOSS
回调(停止播放),但也要意识到在电话场景下,系统会直接静音,应用可能需要额外逻辑(如在onAudioFocusChange
中检查AudioManager.isInCommunicationCall()
或监听电话状态广播)来处理恢复或 UI 更新。
- 独占性: 只有系统电话应用(拥有
PlaybackActivityMonitor
的作用: 它是系统掌握全局音频播放状态的关键。通过跟踪所有AudioTrack
/MediaPlayer
实例 (mPlayers
),系统能够:- 强制执行电话静音/恢复。
- 实现其他高级音频策略(如音频闪避 Ducking 的底层支持、音量控制、音频路由决策等)。
- 焦点栈管理:
MediaFocusControl
使用栈 (mFocusStack
) 管理焦点请求。遵循 LIFO (后进先出) 原则,栈顶拥有当前焦点。移除栈中元素时:- 移除栈顶 (
signal=true
):会通知新的栈顶元素获得焦点 (AUDIOFOCUS_GAIN
)。 - 移除非栈顶 (
signal=false
):不会触发焦点变更通知。
- 移除栈顶 (
- 及时释放焦点: 当应用不再需要播放音频时(如播放完成、用户暂停、界面销毁),必须调用
abandonAudioFocus()
/abandonAudioFocusRequest()
释放焦点。否则:- 该应用会一直占据焦点栈位置。
- 其他应用可能无法正常获得焦点播放声音。
- 用户期望听到的声音(如新启动的音乐 App、通知音)可能被错误地抑制。
5. 重点
Android AudioFocus 机制是一个复杂但精妙设计的系统服务组件,它通过 AudioService
(特别是 MediaFocusControl
和 PlaybackActivityMonitor
) 协调所有应用的音频播放行为。理解其核心概念(焦点类型、变更回调)、关键角色交互(Binder, Handler)、核心源码流程(请求、释放、电话强制静音)以及最佳实践(clientId
/Listener
复用、指定 Handler
、及时释放)对于开发健壮、符合用户期望的音频应用至关重要。虽然遵循 AudioFocus 不是强制的(电话除外),但它是确保应用在各种音频场景下表现一致且良好的基石。
焦点请求队列与优先级
- LIFO(后进先出):最新请求焦点的应用优先获得焦点。
- 优先级规则:
- 系统应用(如电话)拥有最高优先级。
- 前台应用优先于后台应用。
- 用户交互中的应用优先于非交互应用。
五、相关类与函数
AudioPlaybackConfiguration(简称apc)
AudioPlaybackConfiguration 与 PlayerProxy
在AudioPlaybackConfiguration类中,有一个getPlayerProxy()方法,该方法返回一个PlayerProxy对象。这个PlayerProxy对象是在AudioPlaybackConfiguration构造时传入的,它指向了一个PlayerBase的PlayerProxy内部类实例。
PlayerBase
而PlayerBase是Android音频系统中播放器类的基类,例如AudioTrack、MediaPlayer、SoundPool等都继承自PlayerBase。
PlaybackActivityMonitor
当我们创建一个播放器(比如AudioTrack)时,在PlayerBase的构造方法中,会创建一个PlayerProxy对象,并将其设置给AudioPlaybackConfiguration。同时,这个播放器也会在PlaybackActivityMonitor中注册(通过PlayerBase的构造方法中的registerPlayer()方法),这样PlaybackActivityMonitor才能管理这些播放器。
apc.getPlayerProxy()
现在,当我们调用apc.getPlayerProxy().applyVolumeShaper(…)时,实际上是通过PlayerProxy对象调用了对应播放器(例如AudioTrack)的applyVolumeShaper方法。
具体流程如下:
IPlayer
PlayerProxy的applyVolumeShaper方法会通过一个IPlayer接口(这是一个Binder接口)调用到实际播放器端的applyVolumeShaper方法。
在播放器端(例如AudioTrack),因为它是PlayerBase的子类,而PlayerBase内部有一个BaseHandler用于处理来自PlayerProxy的请求。
在PlayerBase中,有一个内部类PlayerProxy,它实现了IPlayer接口。当PlayerProxy的applyVolumeShaper方法被调用时,它会通过PlayerBase的mWeakThis弱引用获取到PlayerBase实例,然后调用PlayerBase的applyVolumeShaper方法。
在PlayerBase的applyVolumeShaper方法中,会调用具体的播放器实现(如AudioTrack)的applyVolumeShaper方法。对于AudioTrack,它会调用native_setVolumeShaperParameters和native_applyVolumeShaper等JNI方法,最终通过AudioFlinger进行实际的音频音量调整。
调用链:
apc.getPlayerProxy().applyVolumeShaper(…)
-> 通过PlayerProxy(实现了IPlayer接口)跨进程调用(如果播放器在另一个进程)或直接调用(同一进程)到对应播放器的PlayerBase的applyVolumeShaper方法
-> 在PlayerBase的applyVolumeShaper方法中,会调用具体播放器子类(如AudioTrack)的applyVolumeShaper方法
-> 然后进入JNI层,最终通过AudioFlinger处理
注意:在同一个应用进程内,可能不会走Binder跨进程调用,因为PlayerProxy和PlayerBase都在同一个进程。但是,PlaybackActivityMonitor是在系统进程(system_server)中,而播放器在应用进程,所以这里实际上是跨进程调用。
详细说明跨进程过程:
在应用进程创建播放器(如AudioTrack)时,会通过PlayerBase的构造方法向PlaybackActivityMonitor注册。注册过程是通过IAudioService(即AudioService)的trackPlayer方法,将播放器的IPlayer代理(即PlayerProxy)传递到AudioService所在的系统进程。
在系统进程中,PlaybackActivityMonitor会保存这个IPlayer代理(即应用进程的播放器代理)。
当系统进程需要让某个播放器降音时,通过之前保存的IPlayer代理调用applyVolumeShaper方法,这个调用就会跨进程到应用进程的播放器,应用进程的播放器就会执行降音操作。
因此,apc.getPlayerProxy()返回的PlayerProxy对象实际上是一个IPlayer的代理,通过这个代理,系统进程可以控制应用进程的播放器行为。
所以,最终调用会到达应用进程中的播放器对象(如AudioTrack)的applyVolumeShaper方法,然后通过JNI调用到native层进行实际的音量调整。
六、源码解析
requestAudioFocus
1.注意
mRingOrCallActive: 系统处于电话/响铃状态
flag: AudioManager.AUDIOFOCUS_FLAG_DELAY_OK
protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb, IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName, int flags, int sdk, boolean forceDuck, int testUid, boolean permissionOverridesCheck) { new MediaMetrics.Item(mMetricsId) .setUid(Binder.getCallingUid()) .set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName) .set(MediaMetrics.Property.CLIENT_NAME, clientId) .set(MediaMetrics.Property.EVENT, \"requestAudioFocus\") .set(MediaMetrics.Property.FLAGS, flags) .set(MediaMetrics.Property.FOCUS_CHANGE_HINT, AudioManager.audioFocusToString(focusChangeHint)) //.set(MediaMetrics.Property.SDK, sdk) .record(); // when using the test API, a fake UID can be injected (testUid is ignored otherwise) // note that the test on flags is not a mask test on purpose, AUDIOFOCUS_FLAG_TEST is // supposed to be alone in bitfield final int uid = (flags == AudioManager.AUDIOFOCUS_FLAG_TEST) ? testUid : Binder.getCallingUid(); mEventLogger.enqueue((new EventLogger.StringEvent( \"requestAudioFocus() from uid/pid \" + uid + \"/\" + Binder.getCallingPid() + \" AA=\" + aa.usageToString() + \"/\" + aa.contentTypeToString() + \" clientId=\" + clientId + \" callingPack=\" + callingPackageName + \" req=\" + focusChangeHint + \" flags=0x\" + Integer.toHexString(flags) + \" sdk=\" + sdk)) .printLog(TAG)); // we need a valid binder callback for clients // 如果 pingBinder(),不存在了,就没有必要入栈了 if (!cb.pingBinder()) { Log.e(TAG, \" AudioFocus DOA client for requestAudioFocus(), aborting.\"); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } // mAudioFocusLock 同步锁 synchronized(mAudioFocusLock) { // check whether a focus freeze is in place and filter if (isFocusFrozenForTest()) { int focusRequesterUid; if ((flags & AudioManager.AUDIOFOCUS_FLAG_TEST) == AudioManager.AUDIOFOCUS_FLAG_TEST) { focusRequesterUid = testUid; } else { //获取uid focusRequesterUid = Binder.getCallingUid(); } if (isFocusFrozenForTestForUid(focusRequesterUid)) { Log.i(TAG, \"requestAudioFocus: focus frozen for test for uid:\" + focusRequesterUid); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } Log.i(TAG, \"requestAudioFocus: focus frozen for test but uid:\" + focusRequesterUid + \" is exempt\"); }//stack 有大小限制 if (mFocusStack.size() > MAX_STACK_SIZE) { Log.e(TAG, \"Max AudioFocus stack size reached, failing requestAudioFocus()\"); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; }// mRingOrCallActive 系统处于电话/响铃状态 boolean enteringRingOrCall = !mRingOrCallActive & (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0); if (enteringRingOrCall) { mRingOrCallActive = true; } final AudioFocusInfo afiForExtPolicy; if (mFocusPolicy != null) { // construct AudioFocusInfo as it will be communicated to audio focus policy afiForExtPolicy = new AudioFocusInfo(aa, uid, clientId, callingPackageName, focusChangeHint, 0 /*lossReceived*/, flags, sdk); } else { afiForExtPolicy = null; } // handle delayed focus boolean focusGrantDelayed = false; // 判断当前系统是否允许重新分配焦点,若返回 false,表示系统无法立即分配焦点 if (!canReassignAudioFocus()) { if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) { return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } else { // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can\'t be // granted right now, so the requester will be inserted in the focus stack // to receive focus later focusGrantDelayed = true; } } // external focus policy? if (mFocusPolicy != null) { if (notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, fd, cb)) { // stop handling focus request here as it is handled by external audio // focus policy (return code will be handled in AudioManager) return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY; } else { // an error occured, client already dead, bail early return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } } // handle the potential premature death of the new holder of the focus // (premature death == death before abandoning focus) // Register for client death notification // 用于监听客户端 Binder 死亡事件 AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb); try { cb.linkToDeath(afdh, 0); } catch (RemoteException e) { // client has already died! Log.w(TAG, \"AudioFocus requestAudioFocus() could not link to \"+cb+\" binder death\"); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; }// 焦点栈非空(!mFocusStack.empty())且栈顶焦点持有者(mFocusStack.peek())与当前请求的客户端ID一致, 表明当前应用此前已成功获取焦点且未释放 if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) { // if focus is already owned by this client and the reason for acquiring the focus // hasn\'t changed, don\'t do anything final FocusRequester fr = mFocusStack.peek(); if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) { // unlink death handler so it can be gc\'ed. // linkToDeath() creates a JNI global reference preventing collection. cb.unlinkToDeath(afdh, 0); notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(), AudioManager.AUDIOFOCUS_REQUEST_GRANTED); //音乐播放器的重复请求 //应用已通过 requestAudioFocus() 获取永久焦点(如 AUDIOFOCUS_GAIN)并播放音乐。 //播放过程中因内部状态刷新(如切歌、界面重建)重复发起相同参数的焦点请求。 //系统检测到参数未变化,直接复用现有焦点,避免冗余操作,打印日志并返回 AUDIOFOCUS_REQUEST_GRANTED。 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } // the reason for the audio focus request has changed: remove the current top of // stack and respond as if we had a new focus owner if (!focusGrantDelayed) { mFocusStack.pop(); // the entry that was \"popped\" is the same that was \"peeked\" above fr.release(); } } // focus requester might already be somewhere below in the stack, remove it removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/); final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb, clientId, afdh, callingPackageName, uid, this, sdk, mEventLogger); if (mMultiAudioFocusEnabled && (focusChangeHint == AudioManager.AUDIOFOCUS_GAIN)) { if (enteringRingOrCall) { if (!mMultiAudioFocusList.isEmpty()) { for (FocusRequester multifr : mMultiAudioFocusList) { multifr.handleFocusLossFromGain(focusChangeHint, nfr, forceDuck); } } } else { boolean needAdd = true; if (!mMultiAudioFocusList.isEmpty()) { for (FocusRequester multifr : mMultiAudioFocusList) { if (multifr.getClientUid() == Binder.getCallingUid()) { needAdd = false; break; } } } if (needAdd) { mMultiAudioFocusList.add(nfr); } nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), AudioManager.AUDIOFOCUS_REQUEST_GRANTED); return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } } if (focusGrantDelayed) { // focusGrantDelayed being true implies we can\'t reassign focus right now // which implies the focus stack is not empty. final int requestResult = pushBelowLockedFocusOwnersAndPropagate(nfr); if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) { notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult); } return requestResult; } else { // propagate the focus change through the stack propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck); // push focus requester at the top of the audio focus stack mFocusStack.push(nfr); nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); } notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), AudioManager.AUDIOFOCUS_REQUEST_GRANTED); if (ENFORCE_MUTING_FOR_RING_OR_CALL & enteringRingOrCall) { runAudioCheckerForRingOrCallAsync(true/*enteringRingOrCall*/); } }//synchronized(mAudioFocusLock) return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; }
focusOwner.hasSameUid(uid)
用于检查音频焦点所有者是否属于特定的用户 ID(UID)
1 返回值
true:焦点所有者的 UID 与传入的 UID 相同
false:UID 不同