一 、为什么要发明音频焦,它是什么?
答:两个或两个以上的 Android App可同时向同一输出流(比如手机的蓝牙、手机的喇叭)播放音频,系统会将所有音频流(就是音频数据了)混合在一起。这是一项有意思的技术,但却会出现混音。为了避免所有音乐应用同时播放,Android 引入了“音频焦点”的概念。 音频焦点机制是Android系统提供的一种道德约定,它倡导的东西有三点:
1、 只有一个App持有音频焦点;
2 、播放声音前申请音频焦点,不需要播放的时候释放音频焦点;
3 、失去音频焦点应该暂停播放或者降低音量。
音频焦点是Android系统进程管理的一个值,这个值就记录了当前音频焦点属于哪个应用,类型等。
二、音频焦点在Android系统中是怎么表示,怎么管理的?
答:音频焦点的管理以栈的形式维护在系统进程SystemServer->MediaFocusControl中
private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
栈顶FocusRequester对象对应的App就是当前持有音频焦点的App。
App成功申请到音频焦点时,会在mFocusStack栈顶添加一个FocusRequester对象 ,然后通知栈顶对应的App音频焦点申请成功,通知栈中其他FocusRequester对象对应的App音频焦点丢失。
FocusRequester这个类就是音频焦点表示类
/**
* @hide
* Class to handle all the information about a user of audio focus. The lifecycle of each
* instance is managed by android.media.MediaFocusControl, from its addition to the audio focus
* stack, or the map of focus owners for an external focus policy, to its release.
* 隐藏类 所有音频焦点相关信息封装类。
* 每个音频焦点实例都被MediaFocusControl类锁管理,它管理着音频焦点的新增与释放,音频焦点栈,外部策略的焦点拥有者等等;
*/
public class FocusRequester {
// on purpose not using this classe's name, as it will only be used from MediaFocusControl
private static final String TAG = "MediaFocusControl";
private static final boolean DEBUG = false;
/**
* 它包含一个iBinder,可以感知焦点申请方(App)是否存活
* 如果App 进程被杀掉,就会通过iBinder通知到对应的FocusRequester,一般就是
* 通知MediaFocusControl-> mFocusStack中的FocusRequester对象,从而从mFocusStack移除对应的
* FocusRequester 释放音频焦点, 且以后其他进程释放焦点,也不会分发或者通知给它对应的App
*/
private AudioFocusDeathHandler mDeathHandler; // may be null
/**
* 客户端回调,当这个FocusRequester对应的App 音频焦点发生变化
* 比如重新获取、丢失时候会给客户端回调
*/
private IAudioFocusDispatcher mFocusDispatcher; // may be null
/**
* 这个iBinder就是这个FocusRequester 对应的binder服务端-App
* 它作用有 :
* 1 当FocusRequester从焦点管理栈退出,比如Abdon后,就进行释放,那么以后App生死都不会通知到 MediaFocusControl
* 2 比较两个FocusRequester一般也是通过他们持有的iBinder对比,比如当一个应用死了,从焦点栈中移除FocusRequester
* 就是通过对比他们持有的iBinder
*/
private final IBinder mSourceRef; // may be null
/**
* App传过来的音频焦点回调的hashcode值,可以认为就是代表一个具体的回调- OnAudioFocusChangeListener
* 因为一个App可能设置多个的音频焦点回调
*/
private final @NonNull String mClientId;
/**
* App包名-ApplicationId
*/
private final @NonNull String mPackageName;
/**
* 申请音频焦点的App的uid
* 音频焦点的申请一般是App跨进程向系统服务SystemServer进程申请的
* Binder类提供了可以获取调用方uid的方法,然后写入到这个FocusRequester中。
*/
private final int mCallingUid;
/**
* 这个就是管理这个FocusRequester所在的MediaFocusControl,所以这边是不能为null
*/
private final MediaFocusControl mFocusController; // never null
/**
* 申请焦点的App的targetSdkVersion(App最佳运行Android版本,在这个版本上做了充分测试和适配)
*/
private final int mSdkTarget;
/**
* the audio focus gain request that caused the addition of this object in the focus stack.
* 标记这个FocusRequester是申请哪种类型的音频焦点,比如正常焦点类型-AudioManager#AUDIOFOCUS_GAIN
* 短暂获取的焦点申请-AudioManager#AUDIOFOCUS_GAIN_TRANSIENT 等等
*/
private final int mFocusGainRequest;
/**
* the flags associated with the gain request that qualify the type of grant (e.g. accepting
* delay vs grant must be immediate)
* 音频焦点是否可以延迟获取到,通过AudioFocusRequest的 build的方法setAcceptsDelayedFocusGain(true)设置
* 如果设置为true ,那么如果申请的时候没有立即给到申请者,那么当其他应用释放后,申请者依旧可以收到音频焦点
*/
private final int mGrantFlags;
/**
* the audio focus loss received my mFocusDispatcher, is AudioManager.AUDIOFOCUS_NONE if
* it never lost focus.
* 音频焦点丢失事件是否已经收到,如果从来没有收到就是AudioManager.AUDIOFOCUS_NONE,如果已经收到
* 那就通过这个保证不重复收到,只有申请者下次重新申请音频焦点后恢复状态才可能会继续收到焦点丢失事件
*/
private int mFocusLossReceived;
/**
* whether this focus owner listener was notified when it lost focus
* 这个值根mFocusLossReceived 关联的,记录是否收到过音频焦点丢失事件,如果已经收到过
* 那音频焦点栈,最上面应用释放音频焦点后,如果这个FocusRequester在最上面就,那就重新获取了音频焦点
* 需要通知到对应的client,如果没有收到过音频焦点丢失通知,那么当最上面的应用释放音频焦点后,就算
* 这个FocusRequester在最上面,也不会通知对应的client重新获取音频焦点
*/
private boolean mFocusLossWasNotified;
/**
* the audio attributes associated with the focus request
* 音频属性 -不做展开
*/
private final @NonNull AudioAttributes mAttributes;
}
三、App失去音频焦点,还可以播放声音吗?
答:可以,但不推荐。上文讲到音频焦点机制是Android系统提供的一种道德约定,所以也可以不必遵守,当失去音频焦点的时候依旧我行我素继续播放,但这种体验很不好。
比如你是一个视频应用,一般在应用退到后台的时候或者来电话的时候会失去音频焦点,这之后应用继续播放用户明显能感觉到是这个视频应用有问题。
再比如一个放音乐的应用,在后台放音乐,你打开爱奇艺看电影,这时候这个音乐程序虽失去了音频焦点,但依旧继续放音乐,那我肯定也非常不爽这个音乐应用。
综合来说虽然音频焦点机制是Android系统提供的一种道德约定,失去音频焦点依旧可以播放声音,但是这样产生的不好体验很容易被发现,所以大家还是遵守的好。
四、应用通过AudioManager.requestAudioFocus申请音频焦点后,系统是同步给出申请结果的吗?
答:是的,除非当时电话类应用占用着音频焦点+App设置了 可以延迟获取焦点:
mAudioFocusRequest =
new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(mAudioAttributes)
.setAcceptsDelayedFocusGain(true) // 可以延迟获取焦点
.setOnAudioFocusChangeListener(mOnAudioFocusChangeListener)
.build();
mAudioManager.requestAudioFocus(mAudioFocusRequest);
这样等其他应用释放焦点后,如果当前申请在焦点管理栈最上方,那就会接收到获得到音频焦点的回调。
五、看到类似Log: 09-24 17:05:01.116 W/MediaFocusControl( 2742): requestAudioFocus() from uid/pid 10060/15667 clientId=android~~~ 就代表申请音频焦点成功了吗
答:不是的,这个只代表申请流程走到了系统SystemServer进程,如果有电话类应用占用,Binder通信异常等问题都会导致焦点申请失败,但大部分情况下可以认为是申请成功。
六、音频焦点申请的详细流程是怎么样的?
七、如果播放结束忘记释放音频焦点,会有什么影响?系统会回收吗?
答:会有影响,比如你就需要短暂获取下焦点做个提示音,就会导致比如你播放完提示音
本来应该继续放音乐的App获取不了焦点不继续播放。系统会回收,但是需要进程死掉。
八、看到类似Log:09-24 17:05:01.116 W/MediaFocusControl :abandonAudioFocus() from uid/pid代表释放焦点释放了吗?
答:同问题五,这个只代表App申请音频焦点释放流程走到系统SystemServer进程,如果出现binder通信异常等问题,也会导致音频焦点释放失败,不过绝大部分时候我们可以认为成功释放了。
九、 音频焦点释放的流程是怎么样的?
十、音频焦点申请类型AUDIOFOCUS_GAIN_TRANSIENT和AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK的区别?
答:AUDIOFOCUS_GAIN_TRANSIENT 对应 AUDIOFOCUS_LOSS_TRANSIENT
AUDIOFOCUS_GAIN_TRANSIENT 表示 短暂获得,一会就释放焦点,比如你只是想发个notification时用下一秒不到的铃声。
AUDIOFOCUS_LOSS_TRANSIENT 表示 短暂的失去音频焦点,不要自己主动去放弃焦点,可以暂停音乐,但不要释放资源,因为过系统会把焦点分发给继续使用。
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 对应 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
duck 、ducking英文是鸭子,钻入水中,低头的意思,在这里就是我们就可以理解低头的意思。
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 表示短暂获音频焦点,之前的音频焦点使用者虽然会丢失音频焦点,但无需暂停播放,只需要降低音量就好;
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 表示临时失去了音频焦点,但可以以较低的音量的播放音频; 嗨嗨 低头播放 不与争锋
网友评论