音频焦点(AudioFocus)
基础介绍
在我们的Android设备中会安装不止一个多媒体应用,如果不制定一个有效合理的规则,应用程序各自为政,那么可能就会出现各种音视频软件的混音,这是非常影响用户体验的。音频焦点旨在保证同一时段内只有一个应用能够维持音频聚焦。
关于AudioFocus的管理准则,Android官方并没有制定需要强制执行的规则,但是为了良好的用户体验,一个音频应用程序最好根据以下准则来管理AudioFocus:
-
在开始播放之前立即调用requestAudioFocus(),并验证调用是否返回AUDIOFOCUS_REQUEST_GRANTED。
-
当另一个应用程序获得音频焦点时,停止/暂停播放,或降低音量。
-
当播放停止,放弃音频焦点。
AudioFocus的使用
AudioFocus因Android版本的不同,其获取方式也不相同:
-
从Android 2.2 (API level 8)开始,应用程序通过调用requestAudioFocus()和abandonAudioFocus()来管理AudioFocus。应用程序还必须注册一个 AudioManager.OnAudioFocusChangeListener,以接收回调和管理自己的音频级别
-
在Android 5.0 (API level 21)或更高版本的应用程序,音频应用程序应该使用AudioAttributes来描述你的应用程序正在播放的音频类型
-
在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的主要有三个步骤:
-
通过Context获取AudioManager
-
创建并定义一个多媒体应用程序相关的音频焦点请求监听器OnAudioFocusChangeListener实例
-
调用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的步骤如下:
-
获取AudioManager
-
构造一个AudioFocus监听器
-
构造一个AudioAttributes实例,AudioAttributes是用于封装描述有关音频流的信息的属性集合的类
-
构造一个AudioFocusRequest实例,AudioFocusRequest用于封装有关音频焦点请求的信息的类
-
将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:真正实现音频焦点控制的类
mAudioFocusListener:AudioFocus变化通知回调;当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:
-
在Android 8.0 (API级别26)中,当另一个应用程序使用AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK请求焦点时,系统可以降低音量并恢复音频播放,而无需调用应用程序的onAudioFocusChange()回调
-
在音乐和视频播放应用程序中,自动降低音量是可以接受的行为,但在播放语音内容时,应用程序应该暂停播放
-
如果想让应用程序在被要求ducking时暂停而不是减少它的音量,创建一个OnAudioFocusChangeListener,使用一个onAudioFocusChange()回调方法来实现所需的暂停/恢复播放。在构造AudioFocusRequest时,调用setOnAudioFocusChangeListener()来注册监听器,并调用setWillPauseWhenDucked(true)来告诉系统使用定义的监听器回调,而不是执行自动ducking操作
延迟焦点:
-
有时系统不能批准音频焦点请求,因为焦点被另一个应用程序“锁定”,比如在打电话时,应用程序不应该继续进行音频播放,因为它没有获得焦点
-
该方法名为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型的请求结果,主要有三种类型:
-
AUDIOFOCUS_REQUEST_FAILED:表示请求音频焦点失败 0
-
AUDIOFOCUS_REQUEST_GRANTED:表示请求音频焦点成功 1
-
AUDIOFOCUS_REQUEST_DELAYED:(Android 8.0及以上)表示延迟的请求音频焦点 2
申请AudioFocus源码分析
5.1 时序图
-
在即将开始播放之前调用 requestAudioFocus(),并验证调用是否返回 AUDIOFOCUS_REQUEST_GRANTED;
-
调用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方法的主要工作内容:
-
通过canReleasingAudioFocus()来判断是否在通话中,如果在通话过程中则直接拒绝申请
-
对申请者进行linkToDeath。使得申请者在意外退出之后可以代其完成abandonAudioFocus操作
-
对于已经持有AudioFocus的情况,如果没有改变持有方式,则不做任何处理,直接返回申请成功。否则将其从栈顶删除,暂时将栈顶让给下一个要获取AudioFocus的应用程序
-
通过回调告知此时处于栈顶的播放实例,它的AudioFocus将被夺走
-
将申请者的信息加入栈顶,成为新的拥有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()方法做了两件事情:
-
遍历整个栈,在栈中传播AudioFocus LOSS的消息
-
调用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消息发送给ServiceEventHandlerDelegate的Handler去处理,并携带者"音频焦点新的拥有者将拥有焦点多长时间"的标志 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