静音控制的情况与音量调节又很大的不同。因为每个应用都有可能进行静音操作,所以为了防止状态发生紊乱,就需要为静音操作进行计数,也就是说多次静音后需要多次取消静音才可以。
不过,如果进行了静音计数后还会引入另外一个问题。如果一个应用在静音操作(计数加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);
}
}
```
这个实现不难理解。读者可以将自行分析一下为什么这么做可以消除意外退出的客户端遗留下来的影响。
- 前言
- 推荐序
- 第1章 开发环境部署
- 1.1获取Android源代码
- 1.2Android的编译
- 1.3在IDE中导入Android源代码
- 1.3.1将Android源代码导入Eclipse
- 1.3.2将Android源代码导入SourceInsight
- 1.4调试Android源代码
- 1.4.1使用Eclipse调试Android Java源代码
- 1.4.2使用gdb调试Android C/C 源代码
- 1.5本章小结
- 第2章 深入理解Java Binder和MessageQueue
- 2.1概述
- 2.2Java层中的Binder分析
- 2.2.1Binder架构总览
- 2.2.2初始化Java层Binder框架
- 2.2.3窥一斑,可见全豹乎
- 2.2.4理解AIDL
- 2.2.5Java层Binder架构总结
- 2.3心系两界的MessageQueue
- 2.3.1MessageQueue的创建
- 2.3.2提取消息
- 2.3.3nativePollOnce函数分析
- 2.3.4MessageQueue总结
- 2.4本章小结
- 第3章 深入理解AudioService
- 3.1概述
- 3.2音量管理
- 3.2.1音量键的处理流程
- 3.2.2通用的音量设置函数setStreamVolume()
- 3.2.3静音控制
- 3.2.4音量控制小结
- 3.3音频外设的管理
- 3.3.1 WiredAccessoryObserver 设备状态的监控
- 3.3.2AudioService的外设状态管理
- 3.3.3音频外设管理小结
- 3.4AudioFocus机制的实现
- 3.4.1AudioFocus简单的例子
- 3.4.2AudioFocus实现原理简介
- 3.4.3申请AudioFocus
- 3.4.4释放AudioFocus
- 3.4.5AudioFocus小结
- 3.5AudioService的其他功能
- 3.6本章小结
- 第4章 深入理解WindowManager-Service
- 4.1初识WindowManagerService
- 4.1.1一个从命令行启动的动画窗口
- 4.1.2WMS的构成
- 4.1.3初识WMS的小结
- 4.2WMS的窗口管理结构
- 4.2.1理解WindowToken
- 4.2.2理解WindowState
- 4.2.3理解DisplayContent
- 4.3理解窗口的显示次序
- 4.3.1主序、子序和窗口类型
- 4.3.2通过主序与子序确定窗口的次序
- 4.3.3更新显示次序到Surface
- 4.3.4关于显示次序的小结
- 4.4窗口的布局
- 4.4.1从relayoutWindow()开始
- 4.4.2布局操作的外围代码分析
- 4.4.3初探performLayoutAndPlaceSurfacesLockedInner()
- 4.4.4布局的前期处理
- 4.4.5布局DisplayContent
- 4.4.6布局的阶段
- 4.5WMS的动画系统
- 4.5.1Android动画原理简介
- 4.5.2WMS的动画系统框架
- 4.5.3WindowAnimator分析
- 4.5.4深入理解窗口动画
- 4.5.5交替运行的布局系统与动画系统
- 4.5.6动画系统总结
- 4.6本章小结
- 第5章 深入理解Android输入系统
- 5.1初识Android输入系统
- 5.1.1getevent与sendevent工具
- 5.1.2Android输入系统简介
- 5.1.3IMS的构成
- 5.2原始事件的读取与加工
- 5.2.1基础知识:INotify与Epoll
- 5.2.2 InputReader的总体流程
- 5.2.3 深入理解EventHub
- 5.2.4 深入理解InputReader
- 5.2.5原始事件的读取与加工总结
- 5.3输入事件的派发
- 5.3.1通用事件派发流程
- 5.3.2按键事件的派发
- 5.3.3DispatcherPolicy与InputFilter
- 5.3.4输入事件的派发总结
- 5.4输入事件的发送、接收与反馈
- 5.4.1深入理解InputChannel
- 5.4.2连接InputDispatcher和窗口
- 5.4.3事件的发送
- 5.4.4事件的接收
- 5.4.5事件的反馈与发送循环
- 5.4.6输入事件的发送、接收与反馈总结
- 5.5关于输入系统的其他重要话题
- 5.5.1输入事件ANR的产生
- 5.5.2 焦点窗口的确定
- 5.5.3以软件方式模拟用户操作
- 5.6本章小结
- 第6章 深入理解控件系统
- 6.1 初识Android的控件系统
- 6.1.1 另一种创建窗口的方法
- 6.1.2 控件系统的组成
- 6.2 深入理解WindowManager
- 6.2.1 WindowManager的创建与体系结构
- 6.2.2 通过WindowManagerGlobal添加窗口
- 6.2.3 更新窗口的布局
- 6.2.4 删除窗口
- 6.2.5 WindowManager的总结
- 6.3 深入理解ViewRootImpl
- 6.3.1 ViewRootImpl的创建及其重要的成员
- 6.3.2 控件系统的心跳:performTraversals()
- 6.3.3 ViewRootImpl总结
- 6.4 深入理解控件树的绘制
- 6.4.1 理解Canvas
- 6.4.2 View.invalidate()与脏区域
- 6.4.3 开始绘制
- 6.4.4 软件绘制的原理
- 6.4.5 硬件加速绘制的原理
- 6.4.6 使用绘图缓存
- 6.4.7 控件动画
- 6.4.8 绘制控件树的总结
- 6.5 深入理解输入事件的派发
- 6.5.1 触摸模式
- 6.5.2 控件焦点
- 6.5.3 输入事件派发的综述
- 6.5.4 按键事件的派发
- 6.5.5 触摸事件的派发
- 6.5.6 输入事件派发的总结
- 6.6 Activity与控件系统
- 6.6.1 理解PhoneWindow
- 6.6.2 Activity窗口的创建与显示
- 6.7 本章小结
- 第7章 深入理解SystemUI
- 7.1 初识SystemUI
- 7.1.1 SystemUIService的启动
- 7.1.2 状态栏与导航栏的创建
- 7.1.3 理解IStatusBarService
- 7.1.4 SystemUI的体系结构
- 7.2 深入理解状态栏
- 7.2.1 状态栏窗口的创建与控件树结构
- 7.2.2 通知信息的管理与显示
- 7.2.3 系统状态图标区的管理与显示
- 7.2.4 状态栏总结
- 7.3 深入理解导航栏
- 7.3.1 导航栏的创建
- 7.3.2 虚拟按键的工作原理
- 7.3.3 SearchPanel
- 7.3.4 关于导航栏的其他话题
- 7.3.5 导航栏总结
- 7.4 禁用状态栏与导航栏的功能
- 7.4.1 如何禁用状态栏与导航栏的功能
- 7.4.2 StatusBarManagerService对禁用标记的维护
- 7.4.3 状态栏与导航栏对禁用标记的响应
- 7.5 理解SystemUIVisibility
- 7.5.1 SystemUIVisibility在系统中的漫游过程
- 7.5.2 SystemUIVisibility发挥作用
- 7.5.3 SystemUIVisibility总结
- 7.6 本章小结
- 第8章 深入理解Android壁纸
- 8.1 初识Android壁纸
- 8.2深入理解动态壁纸
- 8.2.1启动动态壁纸的方法
- 8.2.2壁纸服务的启动原理
- 8.2.3 理解UpdateSurface()方法
- 8.2.4 壁纸的销毁
- 8.2.5 理解Engine的回调
- 8.3 深入理解静态壁纸-ImageWallpaper
- 8.3.1 获取用作静态壁纸的位图
- 8.3.2 静态壁纸位图的设置
- 8.3.3 连接静态壁纸的设置与获取-WallpaperObserver
- 8.4 WMS对壁纸窗口的特殊处理
- 8.4.1 壁纸窗口Z序的确定
- 8.4.2 壁纸窗口的可见性
- 8.4.3 壁纸窗口的动画
- 8.4.4 壁纸窗口总结
- 8.5 本章小结