静音控制的情况与音量调节有很大的不同。因为每个应用都有可能进行静音操作,所以为了防止状态发生紊乱,就需要为静音操作进行计数,也就是说多次静音后需要多次取消静音。
不过,进行了静音计数后还会引入另外一个问题。如果一个应用在静音操作(计数加1)后因为某种原因不小心崩溃了,那么将不会有人再为它进行取消静音的操作,静音计数无法再回到0,也就是说这个“倒霉”的流将被永远静音下去。
那么怎么处理应用异常退出后的静音计数呢?AudioService的解决办法是记录下每个应用自己的静音计数,当应用崩溃时,在总的静音计数中减去崩溃应用自己的静音计数,也就是说,为这个应用完成它没能完成的取消静音这个操作。为此,VolumeStreamState定义了一个继承自DeathRecepient的内部类,名为VolumeDeathHandler,并且为每个进行静音操作的进程创建一个实例。VolumeDeathHandler的实例保存了对应进程的静音计数,并在进程死亡时进行计数清零的操作。从这个名字来看可能是Google希望这个类将来能够承担更多与音量相关的事情吧,不过眼下它只负责静音。我们将在后续的内容中对这个类进行深入讲解。
经过前面的介绍,我们不难得出AudioService、VolumeStreamState与VolumeDeathHandler的关系,如图3-4所示。
1. setStreamMute()分析
同音量设置一样,静音控制也是相对于某一个流类型而言的。正如本节开头所提到的,静音控制涉及引用计数和客户端进程的死亡监控。所以相对于音量控制来说,静音控制有一定的复杂度。还好,静音控制对外入口只有一个函数,就是
AudioManager.setStreamMute()。其第二个参数state为true,表示静音,否则表示解除静音。
[AudioManager.java-->AudioManager.setStreamMute()]
public void setStreamMute(int streamType, boolean state) {
IAudioService service = getService();
try {
// 调用AudioService的setStreamMute,注意第三个参数mICallBack
service.setStreamMute(streamType, state, mICallBack);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setStreamMute", e);
}
}
AudioManager一如既往地充当着AudioService代理的一个角色,不过这次有一个很小却很重要的动作:AudioManager为AudioService传入了一个名为mICallBack的变量。查看一下mICallBack的定义:
private final IBinder mICallBack = new Binder();
真是简单得不得了。全文搜索一下,我们发现mICallBack只用来作为AudioService的几个函数调用的参数。从AudioManager角度看它没有任何实际意义。其实,这在Android的进程间交互通信中是一种常见且非常重要的技术。mICallBack这个简单Binder对象可以充当Bp端在Bn端的一个唯一标识。而且AudioService拿到这个标识后,就可以通过DeathRecipient机制获取Bp端异常退出的回调。这是AudioService维持静音状态正常变迁的一个基石。
服务端把客户端传入的这个Binder对象作为客户端的一个唯一标识的时候,往往会以这个标识为键创建一个Hashtable,用来保存每个客户端的相关信息。这在Android各个系统服务的实现中是一种很常见的用法。
另外,本例传入的mICallBack是直接从Binder类实例化出来的,是一个很原始的IBinder对象。进一步讲,如果传递了一个通过AIDL定义的IBinder对象,那么这个对象就有了交互能力,服务端可以通过它向客户端进行回调。在后面探讨AudioFocus机制时会遇到这种情况。
2. VolumeDeathHandler分析
我们继续跟踪AudioService.setStreamMute()的实现,记得注意第三个参数cb,它代表特定客户端的标识。
[AudioService.java-->AudioService.setStreamMute()]
public void setStreamMute(int streamType, boolean state, IBinder cb) {
// 只有可以静音的流类型才能执行静音操作。这说明,并不是所有的流都可以被静音
if (isStreamAffectedByMute(streamType)) {
// 直接调用了流类型对应的mStreamStates的mute()函数
// 这里没有进行那个令人讨厌的流类型的映射。这是出于操作语义上的原因。读者可以自行思考一下
mStreamStates[streamType].mute(cb, state);
}
}
接下来是VolumeStreamState的mute()函数。VolumeStreamState的确是音量相关操作的核心类型。
[AudioService.java-->VolumeStreamState.mute()]
public synchronized void mute(IBinder cb, boolean state) {
// 这句话是一个重点,VolumeDeathHandler与cb一一对应
// 用来管理客户端的静音操作,并且监控客户端的生命状态
VolumeDeathHandler handler = getDeathHandler(cb, state);
if (handler == null) {
Log.e(TAG, "Could not get client death handler for stream:"+mStreamType);
return;
}
// 通过VolumeDeathHandler执行静音操作
handler.mute(state);
}
上述代码引入了静音控制的主角,VolumeDeathHandler,也许叫做MuteHandler更合适一些。它其实只有两个成员变量,分别是mICallBack和mMuteCount。其中mICallBack保存了客户端传进来的标识,mMuteCount则保存了当前客户端执行静音操作的引用计数。另外,它继承自IBinder.DeathRecipient,所以它拥有监听客户端生命状态的能力。而VolumeDeathHandler()的成员函数只有两个,分别是mute()和binderDied()。说到这里,再看看上面VolumeStreamState.mute()的实现,读者能想象到VolumeDeathHandler的具体实现是什么样子的吗?
继续上面的脚步,看一下它的mute()函数。它的参数state的取值指定了进行静音还是取消静音。所以这个函数也就被分成两部分,分别是处理静音与取消静音两个操作。其实,这完全可以放在两个函数中完成。先看看静音操作是怎么实现的吧。
[AudioService.java-->VolumeDeathHandler.mute()part 1]
public void mute(boolean state) {
if (state) {
// 静音操作
if (mMuteCount == 0) {
// 如果mMuteCount 等于0,则表示客户端是第一次执行静音操作
// 此时linkToDeath开始对客户端的生命状况进行监听
// 这样做的好处是可以避免非静音状态下额外占用Binder资源
try {
// 为什么要判断linkToDeath是否为空?AudioManager不是传递进一个有效的
// Binder吗?原来AudioManager也可能会调用mute()
// 此时的mICallback为空
if (mICallback != null) {
mICallback.linkToDeath(this, 0);
}
// 保存到mDeathHandlers列表中
mDeathHandlers.add(this);
// muteCount()是对全局的静音操作的引用计数
// 如果它的返回值为0,则表示这个流目前还没有被静音
if (muteCount() == 0) {
// 在这里设置流的音量为0
......
}
} catch (RemoteException e) {
......
}
}
// 引用计数加1
mMuteCount++;
} else {
// 暂时先不给出取消静音的操作
......
}
}
看明白了吗?这个函数的条件嵌套比较多,仔细归纳一下,就会发现这段代码的思路是非常清晰的。静音操作根据条件满足与否,完成三个任务:
无论在什么条件下,只要执行这个函数,静音操作的引用计数都会加1。
如果这是客户端第一次执行静音,则开始监控其生命状态,并且把自己加入VolumeStreamState的mDeathHandlers列表中。这是这段代码中很精练的一个操作,只有在客户端执行过静音操作后才会对其生命状态感兴趣,才有保存其VolumeDeathHandler的必要。
更进一步的是,如果这是这个流类型第一次被静音,则设置流音量为0,这才是真正的静音动作。
不得不说,这段代码是非常精练的,不是说代码量少,而是它的行为非常干净,决不会做多余的操作,也不会保存多余的变量。
下面我们要看一下取消静音的操作。取消静音作为静音的逆操作,相信读者已经可以想象到它都做什么事情了吧?这里就不再对其进行说明了。
[AudioService.java-->VolumeDeathHandler.mute() part 2]
public void mute(boolean state) {
if (state) {
// 忽略掉静音操作
......
} else {
if (mMuteCount == 0) {
Log.e(TAG, "unexpected unmute for stream: "+mStreamType);
} else {
// 引用计数先减1
mMuteCount--;
if (mMuteCount == 0) {
// 如果这是客户端最后一次有效地取消静音
mDeathHandlers.remove(this);
if (mICallback != null) {
mICallback.unlinkToDeath(this, 0);
}
if (muteCount() == 0) {
// 将流的音量值设置回静音前的音量,也就是lastAudibleIndex
......
}
}
}
}
}
下面就剩下最后的binderDied()函数了。当客户端发生异常,没能取消其执行过的静音操作时,需要替它完成它应该做却没做的事情。
[AudioService.java-->VolumeDeathHandler.binderDied()]
public void binderDied() {
if (mMuteCount != 0) {
mMuteCount = 1;
mute(false);
}
}
这个实现不难理解,读者可以自行分析一下为什么这么做可以消除意外退出的客户端遗留下来的影响。