音频焦点(AudioFocus)应用与源码解析

音频焦点(AudioFocus)

基础介绍

在我们的Android设备中会安装不止一个多媒体应用,如果不制定一个有效合理的规则,应用程序各自为政,那么可能就会出现各种音视频软件的混音,这是非常影响用户体验的。音频焦点旨在保证同一时段内只有一个应用能够维持音频聚焦。

关于AudioFocus的管理准则,Android官方并没有制定需要强制执行的规则,但是为了良好的用户体验,一个音频应用程序最好根据以下准则来管理AudioFocus:

  1. 在开始播放之前立即调用requestAudioFocus(),并验证调用是否返回AUDIOFOCUS_REQUEST_GRANTED。

  1. 当另一个应用程序获得音频焦点时,停止/暂停播放,或降低音量。

  1. 当播放停止,放弃音频焦点。

AudioFocus的使用

AudioFocus因Android版本的不同,其获取方式也不相同:

  1. 从Android 2.2 (API level 8)开始,应用程序通过调用requestAudioFocus()abandonAudioFocus()来管理AudioFocus。应用程序还必须注册一个 AudioManager.OnAudioFocusChangeListener,以接收回调和管理自己的音频级别

  1. 在Android 5.0 (API level 21)或更高版本的应用程序,音频应用程序应该使用AudioAttributes来描述你的应用程序正在播放的音频类型

  1. 在Android 8.0 (API级别26)或更高版本的应用程序应该使用requestAudioFocus()方法,它接受AudioFocusRequest参数。AudioFocusRequest包含关于音频上下文和应用程序功能的信息。系统使用这些信息自动管理音频焦点的获取和丢失

2.1Android 8.0之前版本中的AudioFocus

Java

// 获取AudioManager系统服务

AudioManager mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);

// 定义一个OnAudioFocusChangeListener

OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {

@Override

public void onAudioFocusChange(int focusChange) {

switch(focusChange){

case AudioManager.AUDIOFOCUS_LOSS:

// 长时间丢失焦点,这个时候需要停止播放,并释放资源。根据不同的逻辑,有时候还会释放焦点

mAudioManager.abandonAudioFocus(afChangeListener);

break;

case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:

// 短暂失去焦点,这时可以暂停播放,但是不必要释放资源,因为很快又会获取到焦点

break;

case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:

// 短暂失去焦点,但是可以跟新的焦点拥有者同时播放,并做降噪处理

break;

case AudioManager.AUDIOFOCUS_GAIN:

//获得了音频焦点,可以播放声音

break;

}

}

};

// 发起AudioFocus请求

int result = mAudioManager.requestAudioFocus(afChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);

if(result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {

// 开始播放音乐

}

从以上代码我们可以总结出,在Android 8.0之前的版本中获取AudioFocus的主要有三个步骤:

  1. 通过Context获取AudioManager

  1. 创建并定义一个多媒体应用程序相关的音频焦点请求监听器OnAudioFocusChangeListener实例

  1. 调用AudioManager.requestAudioFocus方法,发起获取音频焦点请求

其中requestAudioFocus方法有三个参数:

第一个参数:OnAudioFocusChangeListener ,此为一个监听控制器,通过这个监听器可以知道自己获取到焦点或者失去焦点

第二个参数:streamType音频流类型,焦点获得之后的数据传输类型

第三个参数:durationHint,获得焦点的时间长短

2.2Android 8.0及更高版本中的AudioFocus

Java

MediaPlayer mMediaPlayer = new MediaPlayer();

final Object mFocusLock =new Object();

// 延迟播放标志

boolean mPlaybackDelayed = false;

// 立即播放标志

boolean mPlaybackNowAuthorized = false;

// 重新获取焦点标志

boolean mResumeOnFocusGain = false;

AudioManager mAudioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);

OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {

@Override

public void onAudioFocusChange(int focusChange) {

switch (focusChange) {

case AudioManager.AUDIOFOCUS_GAIN:

if (mPlaybackDelayed || mResumeOnFocusGain) {

synchronized(mFocusLock) {

mPlaybackDelayed = false;

mResumeOnFocusGain = false;

}

mMediaPlayer.start();

}

break;

case AudioManager.AUDIOFOCUS_LOSS:

synchronized(mFocusLock) {

mResumeOnFocusGain = false;

mPlaybackDelayed = false;

}

mMediaPlayer.pause();

break;

case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:

synchronized(mFocusLock) {

mResumeOnFocusGain = true;

mPlaybackDelayed = false;

}

mMediaPlayer.pause();

break;

case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:

// ... pausing or ducking depends on your app

break;

}

}

};

// 创建并设置AudioAttribute实例

AudioAttributes mPlaybackAttributes = new AudioAttributes.Builder()

.setUsage(AudioAttributes.USAGE_GAME) // 设置AudioFocus的用途

.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)// 设置AudioFocus的内容类型

.build();

// 创建并设置AudioFocusRequest实例

AudioFocusRequest mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)

.setAudioAttributes(mPlaybackAttributes)

// 设置是否能够接受延迟获取焦点。当焦点被另一个应用锁定时,对音频焦点的请求可能会失败,此方法允许延迟、异步地获得焦点

.setAcceptsDelayedFocusGain(true)

// 设置监听器,有两种方法:一种有监听器参数,另一种没有监听器参数。如果不指定监听器,则使用与主Looper关联的处理程序(handler)

.setOnAudioFocusChangeListener(afChangeListener, mHandler)

.build();

// 发起AudioFocus请求

int res = mAudioManager.requestAudioFocus(mFocusRequest);

synchronized(mFocusLock) {

if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {

mPlaybackNowAuthorized = false;

} else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {

mPlaybackNowAuthorized = true;

mediaPlayer.start();

} else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {

mPlaybackDelayed = true;

mPlaybackNowAuthorized = false;

}

}

8.0及以上版本中,应用申请AudioFocus的步骤如下:

  1. 获取AudioManager

  1. 构造一个AudioFocus监听器

  1. 构造一个AudioAttributes实例,AudioAttributes是用于封装描述有关音频流的信息的属性集合的类

  1. 构造一个AudioFocusRequest实例,AudioFocusRequest用于封装有关音频焦点请求的信息的类

  1. AudioFocusRequeste传入requestAudioFocus方法发出请求

AudioFocus的类型:(定义在AudioManager.java)

Java

AUDIOFOCUS_GAIN //长时间获得焦点。此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS -1

AUDIOFOCUS_GAIN_TRANSIENT //短暂性获得焦点,用完应立即释放。此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -2

AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK //短暂性获得焦点并降音,可混音播放。此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -3

AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE //短暂性获得焦点,录音或者语音识别,此期间其他App发送通知是没有提示音的。此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -2

关键属性和方法说明

AudioFocusRequest:代表一个音频焦点请求

FocusRequester:用来处理和audio focus相关的所有信息的类

FocusRequestInfo:对AudioFocusRequest的一个封装

IAudioFocusDispatcher mFocusDispatcher:用来回调注册的AudioFocusChangeListener

MediaFocusControl真正实现音频焦点控制的类

mAudioFocusListenerAudioFocus变化通知回调;当AudioFocus被其他AudioFocus使用者抢走或归还时将通过这个回调对象进行通知;

FocusGain:每个请求都需要这个字段。它的值与android8.0之前对requestAudioFocus()的调用中使用的durationHint相同:AUDIOFOCUS_GAIN, AUDIOFOCUS_GAIN_TRANSIENT, AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE

mFocusLock:音频焦点线程安全锁,保证当有多个线程延迟获取音频焦点时的线程安全

mFocusStack:用来保存请求的栈,用来维护各client的申请和释放

Java

private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();

ClientId:根据传入的Listener计算得到,如果是同一个AudioFocusListener,则这个ClientId就是相同的

Java

final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());

focusChange:一种焦点获得类型

Java

@param focusChange one of focus gain types ({@link #AUDIOFOCUS_GAIN},

{@link #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or

{@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}) or one of the focus loss types ({@link AudioManager#AUDIOFOCUS_LOSS},{@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT},or {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}).

自动Ducking

  1. 在Android 8.0 (API级别26)中,当另一个应用程序使用AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK请求焦点时,系统可以降低音量并恢复音频播放,而无需调用应用程序的onAudioFocusChange()回调

  1. 在音乐和视频播放应用程序中,自动降低音量是可以接受的行为,但在播放语音内容时,应用程序应该暂停播放

  1. 如果想让应用程序在被要求ducking时暂停而不是减少它的音量,创建一个OnAudioFocusChangeListener,使用一个onAudioFocusChange()回调方法来实现所需的暂停/恢复播放。在构造AudioFocusRequest时,调用setOnAudioFocusChangeListener()来注册监听器,并调用setWillPauseWhenDucked(true)来告诉系统使用定义的监听器回调,而不是执行自动ducking操作

延迟焦点:

  1. 有时系统不能批准音频焦点请求,因为焦点被另一个应用程序“锁定”,比如在打电话时,应用程序不应该继续进行音频播放,因为它没有获得焦点

  1. 该方法名为setAcceptsDelayedFocusGain(true),它允许应用程序异步处理焦点请求。设置此标志后,锁定焦点时发出的请求将返回AUDIOFOCUS_REQUEST_DELAYED。当锁定音频焦点的条件不再存在时,例如当电话结束时,系统会授予挂起的焦点请求,并调用onAudioFocusChange()来通知应用程序

requestAudioFocus方法:在8.0及以上版本中AudioManager类中requestAudioFocus有多个重载方法(重载了五次),但最终调用的都是有两个参数,参数类型分别为AudioFocusRequest和AudioPolicy的重载方法。其中,AudioPolicy类承载着音频切换,音轨路由的重要工作。

Java

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

return status;

}

public int requestAudioFocus(@NonNull AudioFocusRequest focusRequest) {

return requestAudioFocus(focusRequest, null /* no AudioPolicy*/);

}

public int requestAudioFocus(OnAudioFocusChangeListener l,

@NonNull AudioAttributes requestAttributes,

int durationHint,

int flags) throws IllegalArgumentException {

return requestAudioFocus(l, requestAttributes, durationHint,

flags & AUDIOFOCUS_FLAGS_APPS,

null /* no AudioPolicy*/);

}

public int requestAudioFocus(OnAudioFocusChangeListener l,

@NonNull AudioAttributes requestAttributes,

int durationHint,

int flags,

AudioPolicy ap) throws IllegalArgumentException {

return requestAudioFocus(afr, ap);

}

public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {

return focusReceiver.requestResult();

}

AudioService中的requestAudioFocus方法只有一个;在调用AudioService的requestAudioFocus方法时传入了一个 mAudioFocusDispatcher参数

requestAudioFocus方法返回一个int型的请求结果,主要有三种类型:

  1. AUDIOFOCUS_REQUEST_FAILED:表示请求音频焦点失败 0

  1. AUDIOFOCUS_REQUEST_GRANTED:表示请求音频焦点成功 1

  1. AUDIOFOCUS_REQUEST_DELAYED:(Android 8.0及以上)表示延迟的请求音频焦点 2

申请AudioFocus源码分析

5.1 时序图

  1. 在即将开始播放之前调用 requestAudioFocus(),并验证调用是否返回 AUDIOFOCUS_REQUEST_GRANTED;

  1. 调用AudioManager请求焦点,并在重构方法里面判断参数合法值,然后注册监听,通过Binder通信,和系统服务AudioService通信。OnAudioFocusChangeListener这个回调监听并没有发送给AudioService,取而代之的是mAudioFocusDispatcher这个参数作为和跨进程回调的桥梁。实际操作在MediaFocusService中

5.2 AudioManager.java

5.2.1 requestAudioFocus

在同一时间,只能有一个音频回放实例拥有焦点

Java

/*使用Builder模式,将请求焦点的这个动作封装成AudioFocusRequest,

注意setOnAudioFocusChangeListenerInt()是用来设置AudioFocusRequest的mFocusListener和mListenerHandler,

mFocusListener就是我们传来进来的OnAudioFocusChangeListener,mListenerHandler指的是一个Handler,此时为null*/

final AudioFocusRequest afr = new AudioFocusRequest.Builder(durationHint)

.setOnAudioFocusChangeListenerInt(l, null /* no Handler for this legacy API */)

.setAudioAttributes(requestAttributes)

.setAcceptsDelayedFocusGain((flags & AUDIOFOCUS_FLAG_DELAY_OK)

== AUDIOFOCUS_FLAG_DELAY_OK)

.setWillPauseWhenDucked((flags & AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS)

== AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS)

.setLocksFocus((flags & AUDIOFOCUS_FLAG_LOCK) == AUDIOFOCUS_FLAG_LOCK)

.build();

Java

public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {

if (afr == null) {

throw new NullPointerException("Illegal null AudioFocusRequest");

}

// this can only be checked now, not during the creation of the AudioFocusRequest instance

if (afr.locksFocus() && ap == null) {

throw new IllegalArgumentException(

"Illegal null audio policy when locking audio focus");

}

//将所有抢占焦点的实例存放在一个map集合里面

//注册AudioFocusListener

registerAudioFocusRequest(afr);

//获取AudioService的代理对象

final IAudioService service = getService();

final int status;

int sdk;

try {

sdk = getContext().getApplicationInfo().targetSdkVersion;

} catch (NullPointerException e) {

// some tests don't have a Context

sdk = Build.VERSION.SDK_INT;

}

//这个ClientId是根据我们传进来的Listener来算出来,如果是同一个AudioFocusListener那么这个clientId就是相同的

final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());

// 一个阻塞的焦点请求结果接收器

final BlockingFocusResultReceiver focusReceiver;

synchronized (mFocusRequestsLock) {

try {

// TODO status contains result and generation counter for ext policy

// 从这开始就进入AudioService了,执行了Binder调用

status = service.requestAudioFocus(afr.getAudioAttributes(),

afr.getFocusGain(), mICallBack,

//它是一个本地的binder对象,后面AudioService通知客户端焦点丢失和回落这个起到了很大的作用

mAudioFocusDispatcher,

clientId,

getContext().getOpPackageName() /* package name */, afr.getFlags(),

ap != null ? ap.cb() : null,

sdk);

} catch (RemoteException e) {

throw e.rethrowFromSystemServer();

}

if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {

// default path with no external focus policy

return status;

}

// 以下代码为当AudioFocus请求需要等待时的策略

// 创建一个用于存储应用监听器和请求结果接收器映射关系的HashMap

if (mFocusRequestsAwaitingResult == null) {

mFocusRequestsAwaitingResult =

new HashMap<String, BlockingFocusResultReceiver>(1);

}

// 创建一个与监听器关联的阻塞请求结果接收器

focusReceiver = new BlockingFocusResultReceiver(clientId);

mFocusRequestsAwaitingResult.put(clientId, focusReceiver);

}

// 请求结果等待超时则返回AUDIOFOCUS_REQUEST_FAILED

focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS);

if (DEBUG && !focusReceiver.receivedResult()) {

Log.e(TAG, "requestAudio response from ext policy timed out, denying request");

}

synchronized (mFocusRequestsLock) {

mFocusRequestsAwaitingResult.remove(clientId);

}

return focusReceiver.requestResult();

}

总结:将请求焦点的信息封装成AudioFocusRequest然后调用registerAudioFocusListener()完成AudioFocusListener的注册,调用AudioService的requestAudioFocus()函数将申请焦点的请求交由AudioService来执行

5.3 AudioService.java

5.3.1 requestAudioFocus

Java

public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,

IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,

IAudioPolicyCallback pcb, int sdk) {

//先弄清楚传递进来几个参数:durationHint:AudioManager.AUDIOFOCUS_GAIN

//fd:AudioService回调回放实例的中介

//clientId:用于唯一标识一个AudioFocusChangeListener;callingPackageName:回放实例所在的包名

final int uid = Binder.getCallingUid();

MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "focus")

.setUid(uid)

//.putInt("durationHint", durationHint)

.set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)

.set(MediaMetrics.Property.CLIENT_NAME, clientId)

.set(MediaMetrics.Property.EVENT, "requestAudioFocus")

.set(MediaMetrics.Property.FLAGS, flags);

// permission checks

if (aa != null && !isValidAudioAttributesUsage(aa)) {

final String reason = "Request using unsupported usage";

Log.w(TAG, reason);

mmi.set(MediaMetrics.Property.EARLY_RETURN, reason)

.record();

return AudioManager.AUDIOFOCUS_REQUEST_FAILED;

}

// 权限检查

if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {

//判断是否是电话应用就是根据这个clientId来对比的,如果是通过调用requestAudioFocusForCall抢占的焦点,这个ClientId会被设为“AudioFocus_For_Phone_Ring_And_Calls”

if (AudioSystem.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) {

if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(

android.Manifest.permission.MODIFY_PHONE_STATE)) {

final String reason = "Invalid permission to (un)lock audio focus";

Log.e(TAG, reason, new Exception());

mmi.set(MediaMetrics.Property.EARLY_RETURN, reason)

.record();

return AudioManager.AUDIOFOCUS_REQUEST_FAILED;

}

} else {

// only a registered audio policy can be used to lock focus

synchronized (mAudioPolicies) {

if (!mAudioPolicies.containsKey(pcb.asBinder())) {

final String reason =

"Invalid unregistered AudioPolicy to (un)lock audio focus";

Log.e(TAG, reason);

mmi.set(MediaMetrics.Property.EARLY_RETURN, reason)

.record();

return AudioManager.AUDIOFOCUS_REQUEST_FAILED;

}

}

}

}

if (callingPackageName == null || clientId == null || aa == null) {

final String reason = "Invalid null parameter to request audio focus";

Log.e(TAG, reason);

mmi.set(MediaMetrics.Property.EARLY_RETURN, reason)

.record();

return AudioManager.AUDIOFOCUS_REQUEST_FAILED;

}

mmi.record();

//焦点管理的具体逻辑都在MediaFocusControl这个类里面

return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,

clientId, callingPackageName, flags, sdk,

forceFocusDuckingForAccessibility(aa, durationHint, uid), -1 /*testUid, ignored*/);

}

总结:在上面有2个地方会觉得很奇怪。第一个是AudioManager.AUDIOFOCUS_FLAG_LOCK ,第二个是mAudioPolicies。AudioManager.AUDIOFOCUS_FLAG_LOCK代表着AudioFocus被锁定的状态,处于这种状态下,假设应用需要获取AudioFocus,首先得去registerAudioPolicy,这就涉及AudioPolicy

5.4 MediaFocusControl.java

5.4.1 requestAudioFocus

Java

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) {

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.log((new AudioEventLogger.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

// 先ping一下,检查请求焦点的过程中,客户端挂没挂

if (!cb.pingBinder()) {

Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");

return AudioManager.AUDIOFOCUS_REQUEST_FAILED;

}

if ((flags != AudioManager.AUDIOFOCUS_FLAG_TEST)

// note we're using the real uid for appOp evaluation

&& (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),

callingPackageName) != AppOpsManager.MODE_ALLOWED)) {

return AudioManager.AUDIOFOCUS_REQUEST_FAILED;

}

synchronized(mAudioFocusLock) {

//焦点栈是有大小限制的,最大是100个,这个就要求什么呢?前面我们不是说到了ClientId这个东东吗

//ClientId这个东西最好要复用哦,要不然焦点栈满了,你可就抢占不到焦点了。复用了之后你抢占无数次,焦点栈都不满!

if (mFocusStack.size() > MAX_STACK_SIZE) {

Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");

return AudioManager.AUDIOFOCUS_REQUEST_FAILED;

}

//对比一下是否是电话焦点,如果是,标志位改成true代表进入电话状态;这个boolean值很重要,我们下面会看到

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;

//只有当canReassignAudioFocus()返回true的时候,focusGrantDelayed才为true,也就是需要延迟申请

//如果通话占用了AudioFocus,任何人都不能够再申请AudioFocus

//AudioManager.AUDIOFOCUS_FLAG_DELAY_OK = 1

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

//这里对客户端做了个死亡监听,如果客户端挂了,要把这个焦点从焦点栈移除掉

//AudioFocusDeathHandler用来监控其生命状态,当申请者意外退出后可以代其完成abandonAudioFocus操作

AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);

try {

//对申请者进行linkToDeath,使得在申请者意外退出后可以代其完成abandonAudioFocus操作

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就是AudioFocus机制所基于的栈,栈中的元素类型为FocusRequester,它保存了一个回放实例的所有信息;

//这里先处理一下特殊情况,如果申请者已经拥有AudioFocus

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

//得到栈顶元素的FocusRequester对象

final FocusRequester fr = mFocusStack.peek();

//如果申请的时长和flags都相同,则表示重复申请,直接返回成功

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);

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) {

//如果如果申请的时长和flags有一个不相同,则认为需要重新申请,此时需要将栈顶的元素出栈

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

//移除可能在栈中其他位置存在着相同clientId的元素,这说明了系统维护的焦点栈里面是不会存在相同clientId的焦点对象的

//所以说如果你的clientId一样,无论抢多少次,焦点栈都是不会满的!还有个要注意的是后面两个参数都是传的false

/*由于申请者可能曾经调用过requestAudioFocus,但是目前被别人夺走了,所以它现在应该在栈的某个位置上,先把它从栈中删除,

注意第三个参数的意思是不通知AudioFocus的变化*/

removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);

//创建新的FocusRequester对象,保存新的焦点申请者的信息

//现在为申请者创建新的实例放置到栈顶,使其拥有AudioFocus

final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,

clientId, afdh, callingPackageName, uid, this, sdk);

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.

//将其插入栈中,什么位置呢?遍历mFocusStack,从栈顶开始isLockedFocusOwner(前面介绍过该放法)为true的元素的下方

final int requestResult = pushBelowLockedFocusOwners(nfr);

if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {

notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);

}

return requestResult;

} else {

// propagate the focus change through the stack

//追溯该方法,会发现该方法调用fd.dispatchAudioFocusChange,也就是fd就是IAudioFocusDispatcher

//通知栈中其他元素丢失焦点

propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);

// 将新建的FocusRequester压入栈顶,并调用FocusRequester的handleFocusGainFromRequest方法将Focus的申请结果返回给申请者

mFocusStack.push(nfr);

nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);

}

notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),

AudioManager.AUDIOFOCUS_REQUEST_GRANTED);

}

//告诉申请者它请求焦点成功

return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;

}

总结一下MediaFocusControl.requestAudioFocus方法的主要工作内容:

  1. 通过canReleasingAudioFocus()来判断是否在通话中,如果在通话过程中则直接拒绝申请

  1. 对申请者进行linkToDeath。使得申请者在意外退出之后可以代其完成abandonAudioFocus操作

  1. 对于已经持有AudioFocus的情况,如果没有改变持有方式,则不做任何处理,直接返回申请成功。否则将其从栈顶删除,暂时将栈顶让给下一个要获取AudioFocus的应用程序

  1. 通过回调告知此时处于栈顶的播放实例,它的AudioFocus将被夺走

  1. 将申请者的信息加入栈顶,成为新的拥有AudioFocus的回访实例

5.4.2 propagateFocusLossFromGain_syncAf

通知栈中其他元素丢失焦点;focusGain:稍后将添加到栈顶部的新焦点

Java

private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,

boolean forceDuck) {

final List<String> clientsToRemove = new LinkedList<String>();

// going through the audio focus stack to signal new focus, traversing order doesn't

// matter as all entries respond to the same external focus gain

if (!mFocusStack.empty()) {

for (FocusRequester focusLoser : mFocusStack) {

// 判断栈中的FocusRequester是否会明确的丢失焦点,并且会通知每个栈中的元素焦点的变化

final boolean isDefinitiveLoss =

//调用FocusRequester处理;handleFocusLossFromGain方法在FocusRequest.java中

focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);

if (isDefinitiveLoss) {

// 如果明确地会丢失焦点,则需将该元素从栈中移除

//此处为何需要先添加在删除?因为栈一边遍历一边删除不符合常理

clientsToRemove.add(focusLoser.getClientId());

}

}

}

if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {

for (FocusRequester multifocusLoser : mMultiAudioFocusList) {

final boolean isDefinitiveLoss =

multifocusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);

if (isDefinitiveLoss) {

clientsToRemove.add(multifocusLoser.getClientId());

}

}

}

for (String clientToRemove : clientsToRemove) {

removeFocusStackEntry(clientToRemove, false /*signal*/,

true /*notifyFocusFollowers*/);

}

}

propagateFocusLossFromGain_syncAf()方法做了两件事情:

  1. 遍历整个栈,在栈中传播AudioFocus LOSS的消息

  1. 调用removeFocusStackEntry方法,删除栈中刚刚收到会明确失去焦点的元素

5.4.3 removeFocusStackEntry

Java

//移除焦点栈中拥有相同clientId的对象的实现

private void removeFocusStackEntry(String clientToRemove, boolean signal,

boolean notifyFocusFollowers) {

AudioFocusInfo abandonSource = null;

// is the current top of the focus stack abandoning focus? (because of request, not death)

//判断是否栈顶的和当前申请焦点的clientID一样

if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))

{

//Log.i(TAG, " removeFocusStackEntry() removing top of stack");

//一样的话直接出栈,然后释放

FocusRequester fr = mFocusStack.pop();

//释放FocusRequester:取消对应用程序生命状态的监控,即将持有的DeathHandler设置为空;AudioFocusDispatcher也被设置为空。这样做能够清除引用,使得这两个对象能够被JVM回收。

fr.maybeRelease();

if (notifyFocusFollowers) {

abandonSource = fr.toAudioFocusInfo();

}

if (signal) {

// 通知栈顶元素即将获得焦点

//这个signal在requestAudioFocus时传的时false,栈顶元素肯定是当前抢占焦点的实例,没必要通知

//只有在abandonAudioFocus的时候才会传true,通知栈顶的回放实例,AudioFocus回来了

// notify the new top of the stack it gained focus

notifyTopOfAudioFocusStack();

}

} else {

// focus is abandoned by a client that's not at the top of the stack,

// no need to update focus.

// (using an iterator on the stack so we can safely remove an entry after having

// evaluated it, traversal order doesn't matter here)

// 如果焦点被一个非栈顶元素放弃,因为不是栈顶,所以焦点还是栈顶的,则无需进行焦点变化通知.

Iterator<FocusRequester> stackIterator = mFocusStack.iterator();

while(stackIterator.hasNext()) {

FocusRequester fr = stackIterator.next();

if(fr.hasSameClient(clientToRemove)) {

Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for "

+ clientToRemove);

stackIterator.remove();

if (notifyFocusFollowers) {

abandonSource = fr.toAudioFocusInfo();

}

// stack entry not used anymore, clear references

fr.maybeRelease();

}

}

}

// focus followers still want to know focus was abandoned, handled as a loss

if (abandonSource != null) {

abandonSource.clearLossReceived();

notifyExtPolicyFocusLoss_syncAf(abandonSource, false);

}

if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {

Iterator<FocusRequester> listIterator = mMultiAudioFocusList.iterator();

while (listIterator.hasNext()) {

FocusRequester fr = listIterator.next();

if (fr.hasSameClient(clientToRemove)) {

listIterator.remove();

fr.release();

}

}

if (signal) {

// notify the new top of the stack it gained focus

notifyTopOfAudioFocusStack();

}

}

}

5.4.4 notifyTopOfAudioFocusStack

通知栈顶元素获得焦点

Java

private void notifyTopOfAudioFocusStack() {

// notify the top of the stack it gained focus

if (!mFocusStack.empty()) {

if (canReassignAudioFocus()) {

//返回栈顶元素调用handleFocusGain通知焦点回落了

mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);

}

}

if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {

for (FocusRequester multifr : mMultiAudioFocusList) {

if (isLockedFocusOwner(multifr)) {

multifr.handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);

}

}

}

}

5.4.5 canReassignAudioFocus

处理焦点是电话类型,关于延迟焦点focusGrantDelayed是true还是false的问题

Java

private boolean canReassignAudioFocus() {

// focus requests are rejected during a phone call or when the phone is ringing

// this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus

if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) {

return false;

}

return true;

}

isLockedFocusOwner

Java

//该位置的isLockedFocusOwner在MediaFocusControl中;fr.isLockedFocusOwner()需要进入FocusRequester类中查看

//如果焦点类型是电话的话,则与FocusRequest中的isLockedFocusOwner方法的返回值无关

private boolean isLockedFocusOwner(FocusRequester fr) {

return (fr.hasSameClient(AudioSystem.IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner());

}

总结:如果是电话请求的话,则fr.hasSameClient(AudioSystem.IN_VOICE_COMM_FOCUS_ID)所在的

fr.isLockedFocusOwner()返回true, canReassignAudioFocus()返回false,则表示现在是电话类型,拒绝栈顶元素获得焦点;

如果不是电话请求的话,则进入FocusRequest中的isLockedFocusOwner(),栈中元素需要删除的时候flags的值为true,不需要删除的时候值为false,所以不需要删除的元素canReassignAudioFocus()返回的是true,表示栈顶元素可以获得焦点;

如果不是电话请求,同时是需要删除的栈中元素,FocusRequest的isLockedFocusOwner()返回的是true,则canReassignAudioFocus()返回false,表示需要删除的栈中元素也获得不到焦点

5.5 FocusRequest.java

5.5.1 handleFocusLossFromGain

Java

boolean handleFocusLossFromGain(int focusGain, final FocusRequester frWinner, boolean forceDuck)

{

final int focusLoss = focusLossForGainRequest(focusGain);

handleFocusLoss(focusLoss, frWinner, forceDuck);

return (focusLoss == AudioManager.AUDIOFOCUS_LOSS);

}

5.5.2 focusLossForGainRequest

Java

private int focusLossForGainRequest(int gainRequest) {

switch(gainRequest) {

case AudioManager.AUDIOFOCUS_GAIN:

switch(mFocusLossReceived) {

case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:

case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:

case AudioManager.AUDIOFOCUS_LOSS:

case AudioManager.AUDIOFOCUS_NONE:

return AudioManager.AUDIOFOCUS_LOSS;

}

case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:

case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:

switch(mFocusLossReceived) {

case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:

case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:

case AudioManager.AUDIOFOCUS_NONE:

return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;

case AudioManager.AUDIOFOCUS_LOSS:

return AudioManager.AUDIOFOCUS_LOSS;

}

case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:

switch(mFocusLossReceived) {

case AudioManager.AUDIOFOCUS_NONE:

case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:

return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;

case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:

return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;

case AudioManager.AUDIOFOCUS_LOSS:

return AudioManager.AUDIOFOCUS_LOSS;

}

default:

Log.e(TAG, "focusLossForGainRequest() for invalid focus request "+ gainRequest);

return AudioManager.AUDIOFOCUS_NONE;

}

}

总结:focusLossForGainRequest中实现;如果当前申请的焦点时长为AudioManager.AUDIOFOCUS_GAIN,则msg.arg1=AudioManager.AUDIOFOCUS_LOSS;

如果当前申请的焦点时长为AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,则msg.arg1=AudioManager.AUDIOFOCUS_LOSS_TRANSIENT

5.5.3 handleFocusLoss

Java

void handleFocusLoss(int focusLoss, @Nullable final FocusRequester frWinner, boolean forceDuck)

{

try {

if (focusLoss != mFocusLossReceived) {

mFocusLossReceived = focusLoss;

mFocusLossWasNotified = false;

// before dispatching a focus loss, check if the following conditions are met:

// 1/ the framework is not supposed to notify the focus loser on a DUCK loss

// (i.e. it has a focus controller that implements a ducking policy)

// 2/ it is a DUCK loss

// 3/ the focus loser isn't flagged as pausing in a DUCK loss

// if they are, do not notify the focus loser

if (!mFocusController.mustNotifyFocusOwnerOnDuck()

&& mFocusLossReceived == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK

&& (mGrantFlags

& AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) == 0) {

if (DEBUG) {

Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)

+ " to " + mClientId + ", to be handled externally");

}

mFocusController.notifyExtPolicyFocusLoss_syncAf(

toAudioFocusInfo(), false /* wasDispatched */);

return;

}

// check enforcement by the framework

boolean handled = false;

if (frWinner != null) {

handled = frameworkHandleFocusLoss(focusLoss, frWinner, forceDuck);

}

if (handled) {

if (DEBUG) {

Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)

+ " to " + mClientId + ", response handled by framework");

}

mFocusController.notifyExtPolicyFocusLoss_syncAf(

toAudioFocusInfo(), false /* wasDispatched */);

return; // with mFocusLossWasNotified = false

}

final IAudioFocusDispatcher fd = mFocusDispatcher;

if (fd != null) {

if (DEBUG) {

Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "

+ mClientId);

}

mFocusController.notifyExtPolicyFocusLoss_syncAf(

toAudioFocusInfo(), true /* wasDispatched */);

mFocusLossWasNotified = true;

//这个fd就是我们之前抢占焦点时说的mAudioFocusDispathcer这个对象的服务端代理对象;通过这个代理对象去调用客户端及App端通知它焦点发生变化

fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);

}

}

} catch (android.os.RemoteException e) {

Log.e(TAG, "Failure to signal loss of audio focus due to:", e);

}

}

5.5.4 isLockedFocusOwner

Java

//这里的mGrantFlags是在FocusRequester的构造方法中初始化的,其实就是前面传进来的flags

//FocusRequest中如果flags即mGrantFlags!=0,则return true;

boolean isLockedFocusOwner() {

return ((mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0);

}

//AudioManager.AUDIOFOCUS_FLAG_LOCK等于4

释放AudioFocus源码分析

6.1 AudioManager.java

Java

public int abandonAudioFocusRequest(@NonNull AudioFocusRequest focusRequest) {

if (focusRequest == null) {

throw new IllegalArgumentException("Illegal null AudioFocusRequest");

}

return abandonAudioFocus(focusRequest.getOnAudioFocusChangeListener(),

focusRequest.getAudioAttributes());

}

Java

//隐藏的

public int abandonAudioFocus(OnAudioFocusChangeListener l, AudioAttributes aa) {

int status = AUDIOFOCUS_REQUEST_FAILED;

//取消注册listener。这里将把l从ConcurrentHashMap中删除

unregisterAudioFocusRequest(l);

final IAudioService service = getService();

try {

status = service.abandonAudioFocus(mAudioFocusDispatcher,

getIdForAudioFocusListener(l), aa, getContext().getOpPackageName());

} catch (RemoteException e) {

throw e.rethrowFromSystemServer();

}

return status;

}

6.2 AudioService.java

Java

public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa,

String callingPackageName) {

MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "focus")

.set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)

.set(MediaMetrics.Property.CLIENT_NAME, clientId)

.set(MediaMetrics.Property.EVENT, "abandonAudioFocus");

if (aa != null && !isValidAudioAttributesUsage(aa)) {

Log.w(TAG, "Request using unsupported usage.");

mmi.set(MediaMetrics.Property.EARLY_RETURN, "unsupported usage").record();

return AudioManager.AUDIOFOCUS_REQUEST_FAILED;

}

mmi.record();

return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName);

}

6.3 MediaFocusControl.java

Java

protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa,

String callingPackageName) {

new MediaMetrics.Item(mMetricsId)

.setUid(Binder.getCallingUid())

.set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)

.set(MediaMetrics.Property.CLIENT_NAME, clientId)

.set(MediaMetrics.Property.EVENT, "abandonAudioFocus")

.record();

// AudioAttributes are currently ignored, to be used for zones / a11y

mEventLogger.log((new AudioEventLogger.StringEvent(

"abandonAudioFocus() from uid/pid " + Binder.getCallingUid()

+ "/" + Binder.getCallingPid()

+ " clientId=" + clientId))

.printLog(TAG));

try {

// this will take care of notifying the new focus owner if needed

//如果需要,这将负责通知新的焦点所有者

synchronized(mAudioFocusLock) {

// external focus policy?

if (mFocusPolicy != null) {

final AudioFocusInfo afi = new AudioFocusInfo(aa, Binder.getCallingUid(),

clientId, callingPackageName, 0 /*gainRequest*/, 0 /*lossReceived*/,

0 /*flags*/, 0 /* sdk n/a here*/);

if (notifyExtFocusPolicyFocusAbandon_syncAf(afi)) {

return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;

}

}

boolean exitingRingOrCall = mRingOrCallActive

& (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);

if (exitingRingOrCall) { mRingOrCallActive = false; }

//这里可以看到signal是传的true

//注意了,如果栈中是A-B-C 这个时候B释放焦点的时候 最后焦点栈中是A-C 只会移除释放那个

removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);

if (ENFORCE_MUTING_FOR_RING_OR_CALL & exitingRingOrCall) {

//退出电话焦点和前面的是一样的哈,做解除静音的操作

runAudioCheckerForRingOrCallAsync(false/*enteringRingOrCall*/);

}

}

} catch (java.util.ConcurrentModificationException cme) {

// Catching this exception here is temporary. It is here just to prevent

// a crash seen when the "Silent" notification is played. This is believed to be fixed

// but this try catch block is left just to be safe.

Log.e(TAG, "FATAL EXCEPTION AudioFocus abandonAudioFocus() caused " + cme);

cme.printStackTrace();

}

return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;

}

处理服务端回调的源码分析

7.1 registerAudioFocusRequest

注册AudioFocusListener;是被注册进AudioManager的一个ConcurrentHashMap中而不是AudioService中;key是getIdForAudioFocusListener()分配的一个字符串型的Id

Java

public void registerAudioFocusRequest(@NonNull AudioFocusRequest afr) {

final Handler h = afr.getOnAudioFocusChangeListenerHandler();

//获取到的h为null,将AudioFocusRequest和Handler封装成FocusRequestInfo

final FocusRequestInfo fri = new FocusRequestInfo(afr, (h == null) ? null :

new ServiceEventHandlerDelegate(h).getHandler());

final String key = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());

//key为AudioFocusListener的字符串型Id,value为FocusRequestInfo

mAudioFocusIdListenerMap.put(key, fri);

}

AudioManager这一侧一定有一个代理负责接受AudioService的回调并从这个ConcurrentHashMap中通过Id将回调转发给相应的Listener。谁在做这个代理呢?就是后面将作为参数传递给AudioService的mAudioFocusDispatcher

其中mAudioFocusIdListenerMap的初始化:

Java

@UnsupportedAppUsage

private final ConcurrentHashMap<String, FocusRequestInfo> mAudioFocusIdListenerMap = new ConcurrentHashMap<String, FocusRequestInfo>();

7.2 mAudioFocusDispatcher

当音频焦点的拥有者发生变化时,AudioService会回调mAudioFocusDispatcher的dispatchAudioFocusChange()方法;mAudioFocusDispatcher作为参数传递给了AudioService;它也是AudioManager的代理,负责接受AudioService的回调并从这个Map中通过id将回调转发给相应的Listener

Java

//stub是为了方便client,service交互而生成出来的代码

private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() {

@Override

public void dispatchAudioFocusChange(int focusChange, String id) {

final FocusRequestInfo fri = findFocusRequestInfo(id);

if (fri != null) {

//listener是之前传递进来的mAudioFocusChangeListener

final OnAudioFocusChangeListener listener = fri.mRequest.getOnAudioFocusChangeListener();

if (listener != null) {

//如果我们没有传这个Handler对象就会用mServiceEventHandlerDelegate的Handler,这个Handler呢是个和主线程Looper绑定的Handler,也就是说它的回调是在主线程的

//这个时候如果主线程出现了卡顿,你可能就不能及时收到焦点变化的信息;怎么解决这个问题呢?

//可以创建个自己的Handler和子线程绑定Looper绑定的;然后就算主线程卡顿,我们照样可以及时收到焦点变化的通知

//fri.mHandler == null,所以使用ServiceEventHandlerDelegate的Handler

final Handler h = (fri.mHandler == null) ?

mServiceEventHandlerDelegate.getHandler() : fri.mHandler;

final Message m = h.obtainMessage(MSSG_FOCUS_CHANGE/what/, focusChange/arg1/, 0/arg2 ignored/,id/obj/);

//MSSG_FOCUS_CHANGE消息发送给ServiceEventHandlerDelegateHandler去处理,并携带者"音频焦点新的拥有者将拥有焦点多长时间"的标志

h.sendMessage(m);

}

}

}

总结:mAudioFocusDispatcher直接把focusChange和listener的id发送给了一个Handle去处理,这个做法是很有必要的。要知道,目前这个回调尚在Binder的调用线程中,如果在这里因为用户传入的Listener的代码有问题而报出异常或阻塞甚至恶意拖延,则会导致Binder的另一端因异常而崩溃或阻塞。到这里为止,AudioService已经尽到了通知义务,应该通过Handle将后续的操作发往另一个线程,使AudioService尽可能远离回调实现的影响

关于onAudioFocusChange的回调流程:

关于onAudioFocusChange回调的说明:

调用requestAudioFocus的时候,如果获取成功,是立即返回的,不会触发onAudioFocusChange里面的AudioManager.AUDIOFOCUS_GAIN这个场景;onAudioFocusChange是这之后的状态发生变化的时候才会触发,比如说主动调用abandonAudioFocus、或者是其它应用也调用了requestAudioFocus,相应的状态变化的时候才触发

7.3 ServiceEventHandlerDelegate

ServiceEventHandlerDelegate的处理逻辑:

 代码讲解:

Java

private final ServiceEventHandlerDelegate mServiceEventHandlerDelegate =

new ServiceEventHandlerDelegate(null);

private class ServiceEventHandlerDelegate {

private final Handler mHandler;

ServiceEventHandlerDelegate(Handler handler) {

Looper looper;

//可以看到默认是主线程的Looper

if (handler == null) {

if ((looper = Looper.myLooper()) == null) {

//获取主线程的Looper对象

looper = Looper.getMainLooper();

}

} else {

looper = handler.getLooper();

}

if (looper != null) {

// implement the event handler delegate to receive events from audio service

mHandler = new Handler(looper) {

@Override

public void handleMessage(Message msg) {

switch (msg.what) {

case MSSG_FOCUS_CHANGE: {

final FocusRequestInfo fri = findFocusRequestInfo((String)msg.obj);

if (fri != null) {

final OnAudioFocusChangeListener listener =

fri.mRequest.getOnAudioFocusChangeListener();

if (listener != null) {

Log.d(TAG, "dispatching onAudioFocusChange("

+ msg.arg1 + ") to " + msg.obj);

//这里直接通过我们抢占焦点时传进来的listener回调焦点变化的通知

//调用OnAudioFocusChangeListener.onAudioFocusChange(),通知使用者AudioFocus的归属发生了变化

listener.onAudioFocusChange(msg.arg1);

}

}

} break;

case MSSG_RECORDING_CONFIG_CHANGE: {

final RecordConfigChangeCallbackData cbData =

(RecordConfigChangeCallbackData) msg.obj;

if (cbData.mCb != null) {

if (DEBUG) {

Log.d(TAG, "dispatching onRecordingConfigChanged()");

}

cbData.mCb.onRecordingConfigChanged(cbData.mConfigs);

}

} break;

case MSSG_PLAYBACK_CONFIG_CHANGE: {

final PlaybackConfigChangeCallbackData cbData =

(PlaybackConfigChangeCallbackData) msg.obj;

if (cbData.mCb != null) {

if (DEBUG) {

Log.d(TAG, "dispatching onPlaybackConfigChanged()");

}

cbData.mCb.onPlaybackConfigChanged(cbData.mConfigs);

}

} break;

default:

Log.e(TAG, "Unknown event " + msg.what);

}

}

};

} else {

mHandler = null;

}

}

Handler getHandler() {

return mHandler;

}

}

总结:消息的处理主要是通过IAudioFocusDispatcher这个类,传给AudioManager的一个内部类mServiceEventHandlerDelegate这个进行处理;将回调操作以消息的方式发送给mServiceEventHandlerDelegate的Handler,在Handler的消息处理函数中通知回放实例

其中的参数mRequest的来源:

Java

private static class FocusRequestInfo {

@NonNull final AudioFocusRequest mRequest;

@Nullable final Handler mHandler;

FocusRequestInfo(@NonNull AudioFocusRequest afr, @Nullable Handler handler) {

mRequest = afr;

mHandler = handler;

}

}

优质博文

三个步骤实现音频聚焦:http://www.javashuo.com/article/p-uioiyrqb-eo.html

音频焦点底层源码:https://www.jianshu.com/p/e5785dcba952

分析Android Framework源码--彻底了解Android AudioFocus机制,肯定有你不知道的知识点(基于Android10.0)_7-brain的博客-CSDN博客

Android Audio(六)—— AudioFocus_tudouhuashengmi的博客-CSDN博客

Audio Focus分析总结_学如逆水行舟,不进则退3038的博客-CSDN博客_audio focus

Android AudioFocus音频焦点机制学习和理解_天花板之恋的博客-CSDN博客_音频焦点是什么意思

AudioService_Ian22l的博客-CSDN博客_audioservice

猜你喜欢

转载自blog.csdn.net/qq_39039885/article/details/127230945