靜音控制的情況與音量調節又很大的不同。因為每個應用都有可能進行靜音操作,所以為了防止狀態發生紊亂,就需要為靜音操作進行計數,也就是說多次靜音后需要多次取消靜音才可以。
不過,如果進行了靜音計數后還會引入另外一個問題。如果一個應用在靜音操作(計數加1)后因為某種原因不小心掛了,那么將不會有人再為它進行取消靜音的操作,靜音計數無法再回到0,也就是說這個倒霉的流將被永遠靜音下去。
那么怎么處理應用異常退出后的靜音計數呢?AudioService的解決辦法是記錄下來每個應用的自己的靜音計數,當應用崩潰時,在總的靜音計數中減去崩潰應用自己的靜音計數,也就是說,由我們為這個應用完成它沒能完成的取消靜音這個操作。為此,VolumeStreamState定義了一個繼承自DeathRecepient的內部類名為VolumeDeathHandler,并為每個進行靜音操作的進程創建一個實例。它保存了對應進程的靜音計數,并在進程死亡時進行計數清零的操作。從這個名字來看可能是Google希望這個類將來能夠承擔更多與音量相關的事情吧,不過眼下它只負責靜音。我們將在后續的內容對這個類進行深入的講解。
經過前面的介紹,我們不難得出AudioService、VolumeStreamState與VolumeDeathHandler的關系如下:
:-: 
圖 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 本章小結