前言
关于CarAudioZone的部分已经说的七七八八了,但我们一直都还有个疑问,既然CarAudioZone分了不同的zone来实现各自的声音路由、音量调节、音频焦点控制等,那么对于应用又是如何才区分使用的是哪个zone的呢,那么就是今天要说的uid
正文
说到uid,先说下Android的几个概念
pid 是process进程id
uid 是user 用户id
tid(是thead线程id
每个应用都有一个uid,和n个pid,以及n个tid,那么三个id的获取方式呢
android.os.Process.myPid();
android.os.Process.myTid();
android.os.Process.myUid();
android会指定一些系统应用或服务的uid,比如system server的uid是1000,一般app的应用的uid都是1000多的一个值。
关于uid这里就不多说了,我们看下uid在CarAudio中的应用,先来看下CarAudioManger的Api
public boolean setZoneIdForUid(int zoneId, int uid) {
try {
return mService.setZoneIdForUid(zoneId, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
直接调到CarAudioService中了,
public boolean setZoneIdForUid(int zoneId, int uid) {
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
synchronized (mImplLock) {
Log.i(CarLog.TAG_AUDIO, "setZoneIdForUid Calling uid "
+ uid + " mapped to : "
+ zoneId);
// Figure out if anything is currently holding focus,
// This will change the focus to transient loss while we are switching zones
Integer currentZoneId = mUidToZoneMap.get(uid);
ArrayList<AudioFocusInfo> currentFocusHoldersForUid = new ArrayList<>();
ArrayList<AudioFocusInfo> currentFocusLosersForUid = new ArrayList<>();
if (currentZoneId != null) {
currentFocusHoldersForUid = mFocusHandler.getAudioFocusHoldersForUid(uid,
currentZoneId.intValue());
currentFocusLosersForUid = mFocusHandler.getAudioFocusLosersForUid(uid,
currentZoneId.intValue());
if (!currentFocusHoldersForUid.isEmpty() || !currentFocusLosersForUid.isEmpty()) {
// Order matters here: Remove the focus losers first
// then do the current holder to prevent loser from popping up while
// the focus is being remove for current holders
// Remove focus for current focus losers
mFocusHandler.transientlyLoseInFocusInZone(currentFocusLosersForUid,
currentZoneId.intValue());
// Remove focus for current holders
mFocusHandler.transientlyLoseInFocusInZone(currentFocusHoldersForUid,
currentZoneId.intValue());
}
}
// if the current uid is in the list
// remove it from the list
if (checkAndRemoveUidLocked(uid)) {
if (setZoneIdForUidNoCheckLocked(zoneId, uid)) {
// Order matters here: Regain focus for
// Previously lost focus holders then regain
// focus for holders that had it last
// Regain focus for the focus losers from previous zone
if (!currentFocusLosersForUid.isEmpty()) {
regainAudioFocusLocked(currentFocusLosersForUid, zoneId);
}
// Regain focus for the focus holders from previous zone
if (!currentFocusHoldersForUid.isEmpty()) {
regainAudioFocusLocked(currentFocusHoldersForUid, zoneId);
}
return true;
}
}
return false;
}
}
代码有点长,但是逻辑不是很复杂,我们一点一点来分析。mUidToZoneMap是表示uid与zoneid的map表,即每个uid对应在哪个音区中,currentFocusHoldersForUid 和currentFocusLosersForUid这俩list是来获取当前uid在hoder音频焦点map和loser音频焦点map中的焦点信息集合(这俩集合说到音频焦点交互的时候再细说),具体来看一下getAudioFocusHoldersForUid在CarZonesAudioFocus.java中
ArrayList<AudioFocusInfo> getAudioFocusHoldersForUid(int uid, int zoneId) {
CarAudioFocus focus = mFocusZones.get(zoneId);
return focus.getAudioFocusHoldersForUid(uid);
}
根据zoneId先找到对应的音区,然后对应到该音区的CarAudioFocus 获取getAudioFocusHoldersForUid
ArrayList<AudioFocusInfo> getAudioFocusHoldersForUid(int uid) {
return getAudioFocusListForUid(uid, mFocusHolders);
}
这里的mFocusHolders是我们在做音频焦点优先级处理的时候,维护的一个map,这个和MediaFocusControl中那个mFocusStack不同,mFocusStack存的是FocusRequester,而且使用stack来存储的,而这里使用map存储的一个clientId与FocusEntry对应关系,FocusEntry中最重要的一个就是AudioFocusInfo。关于mFocusHolders的存储等我们说到音频焦点优先级处理的时候再具体说。
private ArrayList<AudioFocusInfo> getAudioFocusListForUid(int uid,
HashMap<String, FocusEntry> mapToQuery) {
ArrayList<AudioFocusInfo> matchingInfoList = new ArrayList<>();
for (String clientId : mapToQuery.keySet()) {
AudioFocusInfo afi = mapToQuery.get(clientId).mAfi;
if (afi.getClientUid() == uid) {
matchingInfoList.add(afi);
}
}
return matchingInfoList;
}
这里遍历mapToQuery也就是mFocusHolders中找出对应uid的AudioFocusInfo返回,同理我们又拿到了currentFocusLosersForUid 。拿到之后做了一个remove即transientlyLoseInFocusInZone
void transientlyLoseInFocusInZone(@NonNull ArrayList<AudioFocusInfo> afiList,
int zoneId) {
CarAudioFocus focus = mFocusZones.get(zoneId);
for (AudioFocusInfo info : afiList) {
focus.removeAudioFocusInfoAndTransientlyLoseFocus(info);
}
}
又到了CarAudioFocus 中
void removeAudioFocusInfoAndTransientlyLoseFocus(AudioFocusInfo afi) {
//这个源码不再这里分析了,这个方法是在mFocusHolders中移除AudioFocusInfo
//如果mFocusHolders中有这个afi,则deadEntry不为null
FocusEntry deadEntry = removeFocusEntry(afi);
if (deadEntry != null) {
//移除后(移除我们uid对应的这个focus),要先通知其临时失去焦点了
sendFocusLoss(deadEntry, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
//这个移除后,loser的map中剩余的焦点,根据策略决定是否被restore即恢复granted。
removeFocusEntryAndRestoreUnblockedWaiters(deadEntry);
}
}
然后回到CarAudioService的setZoneIdForUid中checkAndRemoveUidLocked
private boolean checkAndRemoveUidLocked(int uid) {
Integer zoneId = mUidToZoneMap.get(uid);
if (zoneId != null) {
Log.i(CarLog.TAG_AUDIO, "checkAndRemoveUid removing Calling uid "
+ uid + " from zone " + zoneId);
if (mAudioPolicy.removeUidDeviceAffinity(uid)) {
// TODO use the uid device affinity in audio policy when available
mUidToZoneMap.remove(uid);
return true;
}
//failed to remove device affinity from zone devices
Log.w(CarLog.TAG_AUDIO,
"checkAndRemoveUid Failed remove device affinity for uid "
+ uid + " in zone " + zoneId);
return false;
}
return true;
}
这里涉及一个mAudioPolicy.removeUidDeviceAffinity(uid),这里先不细说了,这个是在声音播放的时候选择声音在哪个分区,通过Audiopolicy注册到native的AudioPlocyManager的AudioPolicyMix中做声音路由的,这里主要是 mUidToZoneMap.remove(uid),将uid从map中移除,之后再setZoneIdForUidNoCheckLocked
private boolean setZoneIdForUidNoCheckLocked(int zoneId, int uid) {
Log.d(CarLog.TAG_AUDIO, "setZoneIdForUidNoCheck Calling uid "
+ uid + " mapped to " + zoneId);
//Request to add uid device affinity
if (mAudioPolicy.setUidDeviceAffinity(uid, mCarAudioZones[zoneId].getAudioDeviceInfos())) {
// TODO do not store uid mapping here instead use the uid
// device affinity in audio policy when available
mUidToZoneMap.put(uid, zoneId);
return true;
}
Log.w(CarLog.TAG_AUDIO, "setZoneIdForUidNoCheck Failed set device affinity for uid "
+ uid + " in zone " + zoneId);
return false;
}
这里mAudioPolicy.setUidDeviceAffinity(uid, mCarAudioZones[zoneId].getAudioDeviceInfos())也先不说,后续在讲, 然后将uid和zoneId放入map。最后再regainAudioFocusLocked(currentFocusLosersForUid, zoneId)
void regainAudioFocusLocked(ArrayList<AudioFocusInfo> afiList, int zoneId) {
for (AudioFocusInfo info : afiList) {
if (mFocusHandler.reevaluateAndRegainAudioFocus(info)
!= AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.i(CarLog.TAG_AUDIO,
" Focus could not be granted for entry "
+ info.getClientId()
+ " uid " + info.getClientUid()
+ " in zone " + zoneId);
}
}
}
再来复归焦点
int reevaluateAndRegainAudioFocus(AudioFocusInfo afi) {
CarAudioFocus focus = getFocusForAudioFocusInfo(afi);
return focus.reevaluateAndRegainAudioFocus(afi);
}
这里看似没啥,其实这个音频焦点的交付已经转到我们设置的uid对应的音区了
int reevaluateAndRegainAudioFocus(AudioFocusInfo afi) {
int results = evaluateFocusRequest(afi);
if (results == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
return dispatchFocusGained(afi);
}
return results;
}
最后新的音区中重新来做音频焦点的交互。
总结
简单总结一下,主要是关于setZoneIdForUid的这个方法,他有两个功能,一个是将AudioFocus根据不同的zone,实现各自独立的音频焦点的交互。另一个功能是将uid与之对应的device注册下去实现音频路由在不同分区的实现(比如我们都播放音乐我们可以根据uid来指定我们的音乐播放到哪个音区中)
关于AudioFocus这里后续没有细说,因为涉及篇幅较大,后续单独分析。这里主要我们在set uid的时候,根据set前uid所在分区找到focus的集合,然后在之前的分区中移除这些uid对应的audiofocus,如果focus在之前分区中是holder的即actived的,那么通知其失去。移除后当我们在设置uid对应的新的音区时,在把移除的这些focus加入到新的音区,如果在新的音区是loser的状态,则不回调给应用了,因为在之前音区移除的时候已经通知lose了,如果在新的音区是granted的,则通知其又获取了focus,不得不说谷歌在这里处理的真的很好,对于已经是在使用中的音频焦点,重新设置分区,即不会扰乱设置前分区中audiofocus的逻辑,也不会打乱从新set后的音区中AudioFocus的逻辑。而且每一种可能处理的都十分完美。
谷歌在Android10.0中CarAudioZone的设计,虽然代码逻辑还有的优化,但整体功能真的太棒了。
最后欢迎大家交流沟通