天天看點

《深入了解Android:卷III A》一一3.2音量管理

本節書摘來華章計算機出版社《深入了解android:卷iii a》一書中的第3章,第3.2節,作者:張大偉 更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。1

android手機有兩種改變系統音量的方式。最直接的做法就是通過手機的音量鍵進行音量調整,還有一種做法是從設定界面中調整某一種類型音頻的音量。另外,應用程式可以随時将某種類型的音頻靜音。它們都是通過audioservice進行的。

本節将從上述三個方面對audioservice的音量管理進行探讨。

3.2.1音量鍵的處理流程

觸發音量鍵

在音量鍵被按下後,android輸入系統将該事件一路派發給activity,如果無人截獲并處理這個事件,承載目前activity的顯示phonewindow類的onkeydown()或onkeyup()函數将會處理,進而開始通過音量鍵調整音量的處理流程。輸入事件的派發機制及phonewindow類的作用将在後續章節中詳細介紹,現在隻需要知道,phonewindow描述了一片顯示區域,用于顯示與管理我們所看到的activity和對話框等内容。同時,它還是輸入事件的派發對象,而且隻有顯示在最上面的phonewindow才會收到事件。

按照android的輸入事件派發政策,window對象在事件的派發隊列中位于activity的後面,是以應用程式可以重寫自己的activity.onkeydown()函數以截獲音量鍵的消息,将其用作其他的功能。比如說,在一個相機應用中,按下音量鍵所執行的動作是拍照而不是調節音量。

phonewindow的onkeydown()函數實作如下:

[phonewindow.java-->phonewindow.onkeydown()]

......

注意handlekeydown()函數的第二個參數,它的意義是指定音量鍵将要改變哪一種流類型的音量。在android中,音量的控制與流類型是密不可分的,每種流類型都獨立地擁有自己的音量設定,它們在絕大部分情況下互不幹擾,例如音樂音量、通話音量就是互相獨立的。是以說,離開流類型談音量是沒有意義的。在android中,音量這個概念描述的一定是某一種流類型的音量。

這裡傳入了mvolumecontrolstreamtype,那麼這個變量的值是從哪裡來的呢?activity類中有一個函數名為setvolumecontrolstream(int streamtype)。應用可以通過調用這個函數來指定顯示這個activity時音量鍵所控制的流類型。這個函數的内容很簡單,就一行,如下:

[activity.java-->activity.setvolumecontrolstream()]

getwindow().setvolumecontrolstream(streamtype);

getwindow()的傳回值就是用于顯示目前activity的phonewindow。從名字就可以看出,這個調用改變了mvolumecontrolstreamtype,于是也改變了按下音量鍵後傳入audiomanager.handlekeyup()函數的參數,進而達到setvolumecontrolstream的目的。同時,還應該能看出,這個設定被綁定到activity的window上,在不同activity之間切換時,接收按鍵事件的window也會随之切換,是以應用不需要去考慮在其生命周期中音量鍵所控制的流類型的切換問題。

audiomanager的handlekeydown()的實作很簡單,在一個switch中,它調用了audioservice的adjustsuggestedstreamvolume(),是以直接看一下audioservice的這個函數。

adjustsuggestedstreamvolume()分析

我們先來看函數原型:

public void adjustsuggestedstreamvolume(int direction, int suggestedstreamtype,

adjustsuggestedstreamvolume()有三個參數,第一個參數direction訓示了音量的調整方向,1為增大,-1為減小;第二個參數suggestedstreamtype表示要求調整音量;第三個參數flags的意思就不那麼容易了解了。其實audiomanager在handlekeydown()中設定了兩個flag,分别是flag_show_ui和flag_vibrate。從名字上我們就能看出一些端倪。前者告訴audioservice我們需要彈出一個音量控制台。而在handlekeyup()裡設定了flag_play_sound,這是為什麼當在松開音量鍵後“有時候”會有一個提示音。注意,handlekeyup()中設定了flag_play_sound,但隻是有時候這個flag才會生效,在下面的代碼中可以看到這是為什麼。還需要注意的是,第二個參數名為suggestedstreamtype,從其命名來推斷,這個參數傳入的流類型對audioservice來說隻是一個建議,是否采納這個建議,audioservice有自己的考慮。

看一下它的實作:

[audioservice.java-->audioservice.adjustsuggestedstreamvolume()]

}

初看這段代碼時,可能有讀者對下面這句代碼感到疑惑:

volumestreamstate streamstate = mstreamstates[mstreamvolumealias[streamtype]];

其實,這是為了滿足所謂的“将鈴聲音量用作通知音量”這種需求。這就需要實作在兩個有這個需求的流a與b之間建立起一個a→b映射。當我們對a流進行音量操作時,實際上是在操作b流。筆者個人認為這個功能對使用者體驗的提升并不大,卻給audioservice的實作增加了不小的複雜度。直覺上來想,我們可以使用一個hashmap解決這個問題,鍵是源流類型,值是目标流類型。而android使用了一個更簡單卻不是那麼好了解的方法來完成這件事。audioservice用一個名為mstreamvolumealias的整型數組來描述這個映射關系。

要實作“以鈴聲音量用作音樂音量”,隻需要修改相應位置的值為stream_ring即可,就像下面這樣:

mstreamvolumealias[audiosystem.stream_music] = audiosystem.stream_ring;

之後,因為需要對a流進行音量操作時,實際上是在操作b流,是以就不難了解為什麼在很多和流相關的函數裡都會先做這樣的一個轉換:

streamtype = mstreamvolumealias[streamtype];

其具體的工作方式就留給讀者思考。在本章的分析過程中,大可忽略這種轉換,這并不影響我們對音量控制原理的了解。

簡單來說,這個函數做了三件事:

确定要調整音量的流類型。

在某些情況下屏蔽flag_play_sound。

調用adjuststreamvolume()。

關于這個函數有幾點仍需要說明一下。在函數剛開始的時候有一個判斷,條件是一個名為mvolumecontrolstream的整型變量是否等于-1,從這塊代碼來看,mvolumecontrolstream比參數傳入的suggestedstreamtype厲害多了,隻要它不是-1,要調整音量的流類型就是它。那這麼厲害的控制手段的作用是什麼?其實,mvolumecontrolstream是volumepanel通過forcevolumecontrolstream()函數設定的。什麼是volumepanel呢?就是我們按下音量鍵後的那個音量調節通知框。volumepanel在顯示時會調用forcevolumecontrolstream強制後續的音量鍵操作固定為促使它顯示的那個流類型,并在它關閉時取消這個強制設定,即設定mvolumecontrolstream為-1。這個在後面分析volumepanel時會看到。

接下來我們繼續看一下adjuststreamvolume()的實作。

adjuststreamvolume()分析

[audioservice.java-->audioservice.adjuststreamvolume()]

public void adjuststreamvolume(int streamtype, int direction, int flags) {

在這個函數的實作中,有一個非常重要的類型:volumestreamstate。前面提到過,android的音量是依賴于某種流類型的。如果android定義了n個流類型,audioservice就需要維護n個音量值與之對應。另外每個流類型的音量等級範圍不一樣,是以還需要為每個流類型維護它們的音量調節範圍。volumestreamstate類的功能就是為了儲存與一個流類型所有音量相關的資訊。audioservice為每一種流類型都配置設定了一個volumestreamstate對象,并且以流類型的值為索引,儲存在一個名為mstreamstates的數組中。在這個函數中調用了volumestreamstate對象的adjustindex()函數,于是就改變了這個對象中存儲的音量值。不過,僅僅是改變了它的存儲值,并且沒有把這個變化設定到底層。

總結一下這個函數都做了什麼。

準備工作。計算按下音量鍵的音量步進值。細心的讀者一定注意到了,這個步進值是10而不是1。原來,在volumestreamstate中儲存的音量值是其實際值的10倍。為什麼這麼做呢?這是為了在不同流類型之間進行音量轉換時能夠保證一定精度的一種實作,其轉換過程讀者可以參考rescaleindex()函數的實作。我們可以将這種做法了解為在轉換過程中保留了小數點後一位的精度。其實,直接使用float類型來儲存豈不更簡單?

檢查是否需要改變情景模式。checkforringermodechange()和情景模式有關。讀者可以自行研究其實作。

調用adjustindex()更改volumestreamstate對象中儲存的音量值。

通過sendmsg()發送消息msg_set_device_volume到maudiohandler。

調用sendvolumeupdate()函數,通知外界音量發生了變化。

我們将重點分析後面三項内容:adjustindex()、msg_set_device_volume消息的處理和sendvolumeupdate()。

volumestreamstate的adjustindex()分析

我們先看一下這個函數的定義:

[audioservice.java-->volumestreamstate.adjustindex()]

public b3oolean adjustindex(int deltaindex, int device) {

這個函數很簡單,下面再看一下setindex()的實作:

[audioservice.java-->volumestreamstate.setindex()]

public synchronized boolean setindex(int index, int device, boolean lastaudible) {

int oldindex = getindex(device, false / lastaudible /);

index = getvalidindex(index);

// 在volumestreamstate中儲存設定的音量值,注意使用了一個hashmap

mindex.put(device, getvalidindex(index));

if (oldindex != index) {

} else {

在這個函數中有三項工作要做:

首先儲存設定的音量值。這是volumestreamstate的本職工作,這和android 4.1之前的版本不一樣,音量值與裝置相關聯了。是以對同一種流類型來說,在不同的音頻裝置下将會擁有不同的音量值。

然後根據參數的要求儲存音量值到mlastaudibleindex中。從名字就可以看出,它儲存了靜音前的音量。當取消靜音時,audioservice就會恢複到這裡儲存的音量。

再就是對流映射的處理。既然a→b,那麼在設定b的音量的同時要改變a的音量。這就是後面那個循環的作用。

可以看出,volumestreamstate.adjustindex()除了更新自己所儲存的音量值外,沒有做其他的事情。接下來再看一下msg_set_device_volume的消息處理做了什麼。

msg_set_device_volume消息的處理

adjuststreamvolume()函數使用sendmsg()函數發送msg_set_device_volume消息給maudiohandler,這個handler運作在audioservice的主線程上。直接看一下在maudio-handler中負責處理msg_set_device_volume消息的setdevicevolume()函數:

[audioservice.java-->audiohandler.setindex()]

private void setdevicevolume(volumestreamstate streamstate, int device) {

sendmsg()是一個異步操作,這就意味着,完成adjustindex()更新音量資訊後adjuststreamvolume()函數就傳回了,但是音量并沒有立刻被設定到底層。不過由于handler處理多個消息的過程是串行的,這就隐含着一種風險:如果當handler正在處理某一個消息時發生了阻塞,那麼按下音量鍵,雖然調用adjuststreamvolume()可以立刻傳回,并且從界面上看或用getstreamvolume()擷取音量值都是沒有問題的,但是手機發出聲音時的音量大小并沒有改變。

sendvolumeupdate()分析

接下來,分析一下sendvolumeupdate()函數,它用于通知外界音量發生了變化。

[audioservice.java-->audioservice.sendvolumeupdate()]

private void sendvolumeupdate(int streamtype, int oldindex, int index, int flags) {

這個函數将音量的變化通過廣播的形式通知給其他感興趣的子產品。同時,它還特别通知了mvolumepanel。mvolumepanel是volumepanel類的一個執行個體。我們所看到的音量調節通知框就是它。

至此,從按下音量鍵開始的整個處理流程就完結了。在繼續分析音量調節通知框的工作原理之前,先對之前的分析過程進行總結,參考圖3-2的序列圖。

圖 3-2通過音量鍵調整音量的處理流程

結合上面分析的結果,由圖 3-2可知:

音量鍵處理流程的發起者是phonewindow。

audiomanager僅僅起到代理的作用。

audioservice接受audiomanager的調用請求,操作volumestreamstate的執行個體進行音量的設定。

volumestreamstate負責儲存音量設定,并且提供了将音量設定到底層的方法。

audioservice負責将設定結果以廣播的形式通知外界。

到這裡,相信大家對音量調節的流程已經有了一個比較清晰的認識。接下來我們将介紹音量調節通知框的工作原理。

7.音量調節通知框的工作原理

在分析sendvolumeupdate()函數時曾經注意到,它調用了mvolumepanel的post-volumechanged()函數。mvolumepanel是一個volumepanel的執行個體,作為一個handler的子類,它承接了音量變化的ui/聲音的通知工作。在繼續上面的讨論之前,先了解一下volumepanel工作的基本原理。

volumepanel位于android.view包下,卻沒有在api中提供,因為它隻能被audioservice使用,是以和audioservice放在一個包下可能更合理一些。從這個類的注釋上可以看到,谷歌的開發人員對它被放在android.view下也有極大不滿(what a mass! 他們這麼寫道……)。

volumepanel下定義了兩個重要的子類型,分别是streamresources和streamcontrol。streamresources實際上是一個枚舉,它的每一個可用元素儲存了一個流類型的通知框所需要的各種資源,如圖示、提示文字等。streamresources的定義就像下面這樣:

[volumepanel.java-->volumepanel.streamresources]

private enum streamresources {

};

這幾個枚舉項組成了一個名為stream的數組,如下:

[volumepanel.java-->volumepanel.streams]

private static final streamresources[] streams = {

volumepanel将從這個streams數組中擷取它所支援的流類型的相關資源。這麼做是不是有點啰嗦呢?事實上,在這裡使用枚舉并沒有什麼特殊的意義,使用一個普通的java類來定義streamresources就已經足夠了。

streamcontrol類則儲存了一個流類型的通知框所需要顯示的控件,其定義如下:

[volumepanel.java-->volumepanel.streamcontrol]

private class streamcontrol {

很簡單對不對?streamcontrol執行個體中儲存了音量調節通知框中所需的所有控件。關于這個類在volumepanel的使用,我們可能很直覺地認為隻有一個streamcontrol執行個體,在對話框顯示時,使其儲存的控件按需加載指定流類型的streamresources執行個體中定義的資源。其實不然,出于對運作效率的考慮,streamcontrol執行個體也是每個流類型人手一份,和streamresources執行個體形成一一對應的關系。所有的streamcontrol執行個體被儲存在一個以流類型的值為鍵的hashtable中,名為mstreamcontrols。我們可以在streamcontrol的初始化函數createsliders()中一窺端倪。

[volumepanel-->volumepanel.createsliders()]

private void createsliders() {

// 周遊stream中所有的streamresources執行個體

for (int i = 0; i < streams.length; i++) {

值得一提的是,這個初始化的工作并沒有在構造函數中進行,而是在postvolume-changed()函數中處理的。

既然已經有了通知框所需要的資源和通知框的控件,接下來就要有一個對話框承載它們。沒錯,volumepanel儲存了一個名為mdialog的dialog執行個體,這就是通知框的本身了。每當有新的音量變化到來時,mdialog的内容就會被替換為指定流類型對應的streamcontrol中所儲存的控件,并且根據音量變化情況設定其音量條的位置,最後調用mdialog.show()顯示出來。同時,發送一個延時消息msg_timeout,這條延時消息生效時,将會關閉提示框。

streamresource、streamcontrol與mdialog的關系就像圖3-3所示的那樣,streamcontrol可以說是mdialog的配件,随需拆卸。

圖 3-3streamresource、streamcontrol與mdialog的關系

接下來具體看一下volumepanel在收到音量變化通知後都做了什麼。我們在上一小節中說到了mvolumepanel.postvolumechanged()函數。它的内容很簡單,直接發送了一條消息msg_volume_changed,然後在handlemessage中調用onvolumechanged()函數進行真正的處理。

volumepanel在msg_volume_changed的消息處理函數中調用onvolume-changed()函數,而不是直接在postvolumechanged()函數中直接調用。這麼做是有實際意義的。由于android要求隻能在建立控件的線程中對控件進行操作。postvolumechanged()作為一個回調性質的函數,不能要求調用者位于哪個線程中。是以必須通過向handler發送消息的方式,将後續的操作轉移到指定的線程中。在設計具有ui controller功能的類時,volumepanel的實作方式有很好的參考意義。

下面看一下onvolumechanged()函數的實作:

[volumepanel.java-->volumepanel.onvolumechanged()]

protected void onvolumechanged(int streamtype, int flags) {

注意最後一個resettimeout()的調用,其實它重新延時發送了msg_timeout消息。當msg_timeout消息生效時,mdialog将被關閉。

之後就是onshowvolumechanged了。這個函數負責為通知框的内容填充音量、圖表等資訊,然後再顯示通知框(如果還沒有顯示)。以鈴聲音量為例,省略其他的代碼。

[volumepanel.java-->volumepanel.onshowvolumechanged()]

protected void onshowvolumechanged(int streamtype, int flags) {

至此,音量調節通知框就被顯示出來了,下面總結一下它的工作過程:

postvolumechanged() 是volumepanel顯示的入口。

檢查flags中是否有flag_show_ui。

volumepanel會在第一次被要求彈出時初始化其控件資源。

mdialog 加載指定流類型對應的streamcontrol,也就是控件。

顯示對話框并開始逾時計時。

逾時計時到達,關閉對話框。

到此為止,audioservice對音量鍵的處理流程介紹完畢。而 android還有另外一種改變音量的方式,即音量設定函數etstreamvolume(),下面對其進行介紹。

3.2.2通用的音量設定函數setstreamvolume()

除了可以通過音量鍵調節音量以外,使用者還可以在系統設定中進行調節。audio-manager.setstreamvolume()是系統設定界面中調整音量所使用的接口。

setstreamvolume()分析

setstreamvolume()是sdk中提供給應用的api,它的作用是為特定的流類型設定範圍内允許的任意音量。我們看一下它的實作:

[audioservice.java-->audioservice.setstreamvolume()]

public void setstreamvolume(int streamtype, int index, int flags) {

看明白這個函數了嗎?抛開被忽略掉的那個if塊可以歸納為:這個函數的工作其實很簡單,就執行了下面三方面的工作:

為調用setstreamvolumeint()準備參數。

調用setstreamvolumeint()。

廣播音量發生變化的通知。

下面分析的主線将轉向setstreamvolumeint()的内容。

setstreamvolumeint()分析

看一下setstreamvolumeint()函數的代碼,和前面一樣,暫時忽略目前與分析目标無關的部分代碼。

[audioservice.java-->audioservice.setstreamvolumeint()]

private void setstreamvolumeint(int streamtype,

此函數有兩個工作内容,一個是調用streamstate.setindex(),另一個則是根據setindex()的傳回值和force參數決定是否發送msg_set_device_volume消息。這兩項内容在3.2.1節中已經介紹過,在此不再贅述。

至此,setstreamvolume()的分析完成。

分析完setstreamvolume()的工作流程後,讀者是否覺得有些熟悉呢?如果我們用setstreamvolumeint()的代碼替換setstreamvolume()中對setstreamvolumeint()的調用,再和adjuststreamvolume()函數進行以下比較,就會發現它們的内容出奇得相似。android在其他地方也有這樣的情況。從這一點上來說,已經發展到4.1版本的android源代碼仍然不夠精緻。讀者可以思考一下,有沒有辦法把這兩個函數融合為一個函數呢?

到此,對于音量設定相關的内容就告一段落。接下來我們将讨論和音量相關的另一個重要的内容—靜音。

3.2.3靜音控制

靜音控制的情況與音量調節有很大的不同。因為每個應用都有可能進行靜音操作,是以為了防止狀态發生紊亂,就需要為靜音操作進行計數,也就是說多次靜音後需要多次取消靜音。

不過,進行了靜音計數後還會引入另外一個問題。如果一個應用在靜音操作(計數加1)後因為某種原因不小心崩潰了,那麼将不會有人再為它進行取消靜音的操作,靜音計數無法再回到0,也就是說這個“倒黴”的流将被永遠靜音下去。

那麼怎麼處理應用異常退出後的靜音計數呢?audioservice的解決辦法是記錄下每個應用自己的靜音計數,當應用崩潰時,在總的靜音計數中減去崩潰應用自己的靜音計數,也就是說,為這個應用完成它沒能完成的取消靜音這個操作。為此,volumestreamstate定義了一個繼承自deathrecepient的内部類,名為volumedeathhandler,并且為每個進行靜音操作的程序建立一個執行個體。volumedeathhandler的執行個體儲存了對應程序的靜音計數,并在程序死亡時進行計數清零的操作。從這個名字來看可能是google希望這個類将來能夠承擔更多與音量相關的事情吧,不過眼下它隻負責靜音。我們将在後續的内容中對這個類進行深入講解。

經過前面的介紹,我們不難得出audioservice、volumestreamstate與volumedeathhandler的關系,如圖3-4所示。

圖 3-4與靜音相關的類

setstreammute()分析

同音量設定一樣,靜音控制也是相對于某一個流類型而言的。正如本節開頭所提到的,靜音控制涉及引用計數和用戶端程序的死亡監控。是以相對于音量控制來說,靜音控制有一定的複雜度。還好,靜音控制對外入口隻有一個函數,就是audiomanager.setstreammute()。其第二個參數state為true,表示靜音,否則表示解除靜音。

[audiomanager.java-->audiomanager.setstreammute()]

public void setstreammute(int streamtype, boolean state) {

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機制時會遇到這種情況。

volumedeathhandler分析

我們繼續跟蹤audioservice.setstreammute()的實作,記得注意第三個參數cb,它代表特定用戶端的辨別。

[audioservice.java-->audioservice.setstreammute()]

public void setstreammute(int streamtype, boolean state, ibinder cb) {

接下來是volumestreamstate的mute()函數。volumestreamstate的确是音量相關操作的核心類型。

[audioservice.java-->volumestreamstate.mute()]

public synchronized void mute(ibinder cb, boolean 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) {

看明白了嗎?這個函數的條件嵌套比較多,仔細歸納一下,就會發現這段代碼的思路是非常清晰的。靜音操作根據條件滿足與否,完成三個任務:

無論在什麼條件下,隻要執行這個函數,靜音操作的引用計數都會加1。

如果這是用戶端第一次執行靜音,則開始監控其生命狀态,并且把自己加入volumestreamstate的mdeathhandlers清單中。這是這段代碼中很精練的一個操作,隻有在用戶端執行過靜音操作後才會對其生命狀态感興趣,才有儲存其volumedeathhandler的必要。

更進一步的是,如果這是這個流類型第一次被靜音,則設定流音量為0,這才是真正的靜音動作。

不得不說,這段代碼是非常精練的,不是說代碼量少,而是它的行為非常幹淨,決不會做多餘的操作,也不會儲存多餘的變量。

下面我們要看一下取消靜音的操作。取消靜音作為靜音的逆操作,相信讀者已經可以想象到它都做什麼事情了吧?這裡就不再對其進行說明了。

[audioservice.java-->volumedeathhandler.mute() part 2]

下面就剩下最後的binderdied()函數了。當用戶端發生異常,沒能取消其執行過的靜音操作時,需要替它完成它應該做卻沒做的事情。

[audioservice.java-->volumedeathhandler.binderdied()]

public void binderdied() {

這個實作不難了解,讀者可以自行分析一下為什麼這麼做可以消除意外退出的用戶端遺留下來的影響。

3.2.4音量控制小結

音量控制是audioservice最重要的功能之一。經過上面的讨論,相信讀者對audioservice的音量管理流程已經有了一定的了解。

總結一下我們在這一節裡所學到的内容:

audioservice音量管理的核心是volumestreamstate。它儲存了一個流類型所有的音量資訊。

volumestreamstate儲存了運作時的音量資訊,而音量的生效則是在底層audioflinger完成的。是以進行音量設定需要做兩件事情:更新volumestreamstate存儲的音量值,設定音量到audio底層系統。

volumedeathhandler是volumestreamstate的一個内部類。它的執行個體對應在一個流類型上執行靜音操作的一個用戶端,是實作靜音功能的核心對象。

繼續閱讀