💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
静音控制的情况与音量调节又很大的不同。因为每个应用都有可能进行静音操作,所以为了防止状态发生紊乱,就需要为静音操作进行计数,也就是说多次静音后需要多次取消静音才可以。 不过,如果进行了静音计数后还会引入另外一个问题。如果一个应用在静音操作(计数加1)后因为某种原因不小心挂了,那么将不会有人再为它进行取消静音的操作,静音计数无法再回到0,也就是说这个倒霉的流将被永远静音下去。 那么怎么处理应用异常退出后的静音计数呢?AudioService的解决办法是记录下来每个应用的自己的静音计数,当应用崩溃时,在总的静音计数中减去崩溃应用自己的静音计数,也就是说,由我们为这个应用完成它没能完成的取消静音这个操作。为此,VolumeStreamState定义了一个继承自DeathRecepient的内部类名为VolumeDeathHandler,并为每个进行静音操作的进程创建一个实例。它保存了对应进程的静音计数,并在进程死亡时进行计数清零的操作。从这个名字来看可能是Google希望这个类将来能够承担更多与音量相关的事情吧,不过眼下它只负责静音。我们将在后续的内容对这个类进行深入的讲解。 经过前面的介绍,我们不难得出AudioService、VolumeStreamState与VolumeDeathHandler的关系如下: :-: ![](http://img.blog.csdn.net/20150811133130548?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图 3-4 与静音相关的类 #### 1. setStreamMute()分析 同音量设置一样,静音控制也是相对于某一个流类型而言的。而且正如本节开头所提到的,静音控制涉及到引用计数和客户端进程的死亡监控。所以相对与音量控制来说,静音控制有一定的复杂度。不过还好,静音控制对外入口只有一个函数,就是AudioManager.setStreamMute()。第二个参数state为true表示静音,否则为解除静音。 **AudioManager.java-->AudioManager.setStreamMute()** ``` public void setStreamMute(int streamType, booleanstate) { 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的变量。查看一下它的定义: private final IBinder mICallBack = new Binder(); 真是简单得不得了。全文搜索一下,我们发现它只被用来作为AudioService的几个函数调用的参数。从AudioManager这边看来它没有任何实际意义。其实,这在Android中进程间交互通讯中是一种常见且非常重要的技术。mICallBack这个简单的变量可以充当Bp端在Bn端的一个唯一标识。Bn端,也就是AudioService拿到这个标识后,就可以通过DeathRecipient机制获取到Bp端异常退出的回调。这是AudioService维持静音状态正常变迁的一个基石。 **注意** 服务端把客户端传入的这个Binder对象作为客户端的一个唯一标识,能做的事情不仅仅DeathRecipient这一个。还以这个标识为键创建一个Hashtable,用来保存每个客户端相关信息。这在Android各个系统服务的实现中是一种很常见的用法。 另外,本例中传入的mICallBack是直接从Binder类实例化出来的,是一个很原始的IBinder对象。进一步讲,如果传递了一个通过AIDL定义的IBinder对象,这个对象就有了交互能力,服务端可以它向客户端进行回调。在后面探讨AudioFocus机制时会遇到这种情况。 #### 2. VolumeDeathHandler分析 我们继续跟踪AudioService.setStreamMute()的实现,记得注意第三个参数cb,它是代表特定客户端的标识。 **AudioService.java-->AudioService.setStreamMute()** ``` public void setStreamMute(int streamType, booleanstate, 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, booleanstate) { // 这句话是一个重点,VolumeDeathHandler与cb一一对应 // 用来管理客户端的静音操作,并且监控客户端的生命状态 VolumeDeathHandler handler = getDeathHandler(cb, state); if(handler == null) { Log.e(TAG, "Could not get client deathhandler for stream: "+mStreamType); return; } // 通过VolumeDeathHandler执行静音操作 handler.mute(state); } ``` 上述代码引入了静音控制的主角,VolumeDeathHandler,也许叫做MuteHandler更合适一些。它其实只有两个成员变量,分别是mICallBack和mMuteCount。其中mICallBack保存了客户端的传进来的标识,mMuteCount则保存了当前客户端执行静音操作的引用计数。另外,它继承自IBinder.DeathRecipient,所以它拥有监听客户端生命状态的能力。而成员函数则只有两个,分别是mute()和binderDied()。说到这里,再看看上面VolumeStreamState.mute()的实现,读者能否先想想VolumeDeathHandler的具体实现是什么样子的么? 继续上面的脚步,看一下它的mute()函数。它的参数state的取值指定了进行静音还是取消静音。所以这个函数也就分成了两部分,分别处理静音与取消静音两个操作。其实,这完全可以放在两个函数中完成。先看看静音操作是怎么做的吧。 **AudioService.java-->VolumeDeathHandler.mute()part1** ``` 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); } } ``` 这个实现不难理解。读者可以将自行分析一下为什么这么做可以消除意外退出的客户端遗留下来的影响。