Android SystemUI 架構詳解

Android SystemUI 架構詳解

 

本文描述Android系統中一個核心應用SystemUI,詳細贅述SystemUI中幾大模塊功能的實現過程。由於作者水平有限,如發現本文中錯誤的地方,歡迎指正。

1、SystemUI介紹

1.1、SystemUI摘要

在Android系統中SystemUI是以應用的形式運行在Android系統當中,即編譯SystemUI模塊會生產APK文件,源代碼路徑在frameworks/base/packages/SystemUI/,安裝路徑system/priv-app/-SystemUI。

1.2、什麼是SystemUI

在前文1.1章節中可知,SystemUI是一個普通的APK文件,即是一個普通的APP,但是,手機使用者看見的所有SystemUI的內容都不像是一個APP,爲什麼?既然是一個應用,就是完成特定的功能,SystemUI主要完成的功能有:
(1)、Status bars
(2)、Navigation bars
(3)、Notification
(4)、Lockscreen
(5)、Quick settings
(6)、Overview(recent task switcher)
(7)、VolumeUI

 

2、SystemUI的啓動過程

正如1.2中所述,SystemUI任何內容都不像一個APP,自然它的啓動也不像大多APP一樣啓動一個Activity。SystemUI顧名思義就是全局UI,必須在開機過程中完成啓動,並不可退出。
回顧Android系統開機的過程,會創建server進程維護系統各種服務,當服務啓動完成後調用systemReady方法(如果讀者不瞭解這個過程自行學習Android Boot Flow),如下圖:


繼續跟蹤startSystemUi()方法的實現如下圖:


上圖中可以看到通過熟悉的startServiceAsUser()方法啓動SystemUI中的SystemUIService。由APP的啓動過程可知(如果讀者不瞭解這個過程可自行學習),Android系統會給應用創建獨立的進程,並實例化Application對象,在SystemUI源碼中的AndroidManifest.xml文件可以看到下圖的配置:


上圖中在application標籤中指定了SystemUIApplication對象,因此在啓動SystemUI應用時會創建SystemUIApplication對象並回調onCreate()方法,如下圖:


onCreate()中註冊ACTION_BOOT_COMPLETED廣播,並調用mServices.onBootCompleted()方法,將在後面的內容中贅述該方法。應用成功啓動後便可執行上文中提到的SystemUIService服務,並回調onCreate()方法,如下圖:


上圖中通過獲取應用的Application實例SystemUIApplication對象,調用startServiceIfNeeded(),如下圖:

 


上圖中讀者可能會問:mService的實質是什麼?回顧1.2章節中的內容,SystemUI主要分幾大模塊,即在SystemUI中每個模塊是一個Service,這樣一來,各個模塊就非常獨立。上圖中用Java映射機制把每個Service的對象實例化,並調用start()方法啓動各個服務,start()方法的具體實現在分析各個服務時再贅述。先了解mService實質都包含哪些具體「對象」,如下圖:


如上圖所示,SystemUI需要啓動的Service包括KeyguardViewMediator、Recent、VolumeUI、SystemBars、StorageNotification、PowerUI、RingtongPlayer共7個,每個service的具體實現和功能將在下文中描述。在圖6中可以看到,實例化每個對象是向上轉型爲SystemUI對象,即圖7中的所有service統一繼承了SystemUI類,如下圖:


SystemUI類提供start()、onConfigurationChanged()、dump()等重要方法,每個方法在各個service中實現不一樣,下面將一一描述每個service在SystemUI類中的方法的實現。
至此,SystemUI的啓動基本完成,從上文可知,SystemUI是系統中非常核心的應用,在Android系統開機過程中Server進程直接發起SystemUI啓動,SystemUI也是固化程序,在保證系統正常運行發揮了非常重要的作用。

 

3、SystemUI的SERVICES

3.1、音量控制

3.1.1、音量控制簡介

如圖章節1.2中的VolumeUI所示,當用戶操作音量鍵時,會彈出相應的UI顯示,並可以設置音量大小和情景模式。VolumeUI的主要代碼在SystemUI/volume下。在不同模式下,音量鍵觸發的UI顯示樣式不一樣,分別是通話、鈴聲(通知)、音樂、鬧鈴、藍牙輸出等,如下圖 9-13


3.1.2、音量控制SERVICE的初始化

在第二章節中SystemUI的啓動過程提到,SystemUI的所有Service通過SystemUI類的start()方法啓動,並且通過圖7可以知道,volume service的VolumeUI繼承了SystemUI類,所以start()實質是執行VolumeUI中的方法,如下圖:


如上圖中的代碼,首先讀取VolumeUI的開關,如果mEnabled爲true,則調用initPanel()方法實例化UI等控件元素(如圖15),實例化VolumeController控制器(如圖17),調用putComponent()保存對象實例,調用updateController()設置控制器(如圖18)。


上圖中主要是new一個VolumePanel對象,VolumePanel是Handler的子類,且又是VolumeUI的Pannel,因此,VolumePanel負責繪製VolumeUI的內容和控制VolumeUI的顯示。先看看VolumeUI的創建過程:

從上圖中的代碼可見,VolumeUI是以Dialog的形式顯示UI,VolumePanel的實例化過程創建Dialog實例和初始化ZenModePanel,到此VolumePanel將會待命。上文中提到VolumePanel同時是Handler的子類,一旦VolumePanel收到相關的Message時,將會處理UI的顯示和關閉。


上圖是VolumeController的實現代碼,主要實現對VolumeUI的Panel的控制,例如上圖中的volumeChanged()控制Panel的顯示和變化,dismissNow()控制Panel的關閉。那麼VolumeController是被誰管控呢?如下圖:

圖中可以看到先通過設置Provider讀取是否允許systemui控制volume,如果允許,則設置通過AudioManager的實例設置VolumeController到AudioService(讀者如果不瞭解這個過程,可以自行閱讀Android Audio策略)。至此,VolumeUI的初始化全部完成。
通過本節的學習,VolumeUI的架構如下圖:


3.1.3、控制音量過程

當SystemUI的VolumeUI當前不是活動窗口時,一般情況下,音量的設置是通過音量鍵進行操作,當用戶操作音量鍵時,如果用戶不攔截音量鍵事件,那麼默認音量鍵的事件將會在Window中被消化,Window將捕獲到的音量鍵事件通過Binder機制將音量變化信息傳送到MediaSessionService,MediaSessionService同樣通過Binder機制接着傳送到AudioService,最後AudioService也同樣通過Binder機制把信息傳送給SystemUI(VolumeUI),VolumeUI將會作出相應的變化。下面將詳細瞭解這個過程:
當手機設備當前活動窗口在Laucher桌面,Laucher沒有對音量鍵事件作攔截操作,音量鍵事件將會在PhoneWindow中被消化。在Android的單次點擊事件中,分down和up兩種事件,分別被分發到PhoneWindow的onKeyUp()和onKeyDown()方法中,如下圖20-21:


圖20是消化down事件,圖21是消化up事件,但音量鍵還有上音量鍵(+)和下音量鍵(-),從這兩張圖可以看到,KEYCODE_VOLUME_UP沒有作任何處理,上音量鍵(+)的事件會在下音量鍵中消化(-), 在down和up事件中都是調用sendAdjustVolumeBy()同一個方法,傳遞三個參數,第一個參數是指定音量類型,mVolumeControlStream爲默認值,取值Integer.MIN_VALUE,圖20和圖21相同,第二個是delta,音量控制類型,即增加或減少,圖20傳遞direction,圖21傳遞0,direction取值1或-1,即1:增加、0:不變、-1:減少,第三個參數是flags,控制VolumeUI顯示,每個參數具體的控制的實現代碼將在下文中描述。繼續跟蹤流程,Laucher進程通過Binder機制把信息傳送到MediaSessionService,如下圖:


上圖中有獲取MediaSessionRecord的對象來控制音量,這裏的session變量的值是null,如果讀者對此感興趣可自行閱讀相關資料。繼續看dispatchAjustVolumeLocked()方法:

圖23中可以知道參數只是多了一個packageName,其它的都是圖20或圖21中的參數值。接着往下看:


在圖20或圖21中有描述suggestedStreamType的值是Integer.MIN_VALUE,在上圖中通過getActiveStreamType()方法對值進行轉換,變成streamType,它的取值可能是0到10,分別控制不同類型的音量,如3.1.1章節中所以,本例子streamType的值是2,即調整的是鈴聲(通知)的音量。接着又調用了adjustStreamVolume()方法,如下:

adjustStreamVolume()方法對direction和streamType的合法進行校驗,direction取值-1到1,streamType取值0到10。之後通過mStreamState獲取oldIndex、newIndex和index值,其中oldindex和index作爲sendVolumeUpdate()方法的參數,將會影響音量變化的廣播和AudioProfile,關於AudioProfile讀者感興趣可以自行學習。圖中還可以看到這裏還設置了HDMI接口輸出。繼續看sendVolumeUpdate()方法:


在sendVolumeUpdate()方法中處理了幾個事件,一個postVolumeChanged()方法,最終通知SystemUI,後面贅述。接着發送註冊到AudioManager.VOLUME_CHANGED_ACTION的action的廣播,通知音量改變並攜帶音量大小的原值oldIndex和新值index,最後通知AudioProfile。繼續看postVolumeChanged()方法:

這裏寫圖片描述

這裏通過mControlle對象調用volumeChanged(),mController實質是一個什麼類的實例,回顧3.1.2章節中的圖18,updateController()方法設置了VolumeController的實例,因此mController正是VolumeController在AudioService中的句柄,通過Binder機制,把音量變化的信息從AudioService傳輸到SystemUI進程。轉移到SystemUI,如下圖:

這裏寫圖片描述
從AudioService回調到volumeChanged()方法,接着調用mPanel的postVolumeChanged()方法,mPanel在前文3.1.2章節的圖15中有描述,是VolumePanel的實例,前文中提到,VolumePanel是Handler的子類,也是VolumeUI的Panel,下面結合代碼分析VolumePanel的具體功能:

這裏寫圖片描述
上文提到VolumePanel是Handler的子類,圖29中VolumePanel將發送MSG_VOLUME_CHANGED的Message到自身持有的線程,接着看MSG_VOLUME_CHANGED的代碼:

這裏寫圖片描述

上圖中直接又調用了onShowVolumeChanged()方法,顧名思義是顯示音量變化的UI,後面接着繼續調用resetTimeout()方法,先跟蹤onShowVolumeChanged()方法:

這裏寫圖片描述
上圖中首先調用getStreamVolume()方法獲取對應streamType當前的音量值,通過StreamControl匹配streamType的UI,StreamControl是一個容器,在VolumeUI的初始化時被實例化,裝載不同streamType的UI配置,並保存到mStreamControls數組對象中,因此streamType的值確定了StreamControl的類型,StreamControl確定了Dialog顯示的UI類型。確定StreamControl的類型後,調用updateSliderProgress()方法更新界面控件,最後調用mDialog.show()方法繪製界面。至此,從按下音量鍵到調用mDialog.show(),設備對點擊事件作出相應,並顯示相應的UI到界面上。
回顧圖31,resetTimeout()方法的實現如下:

這裏寫圖片描述
這裏會延時發送一個空消息到VolumePanel,what爲MSG_TIMEOUT,mTimeoutDelay的值爲3000,跟蹤MSG_TIMEOUT的處理過程:

這裏寫圖片描述
很簡單,實現的功能就是在VolumeUI顯示後,延時3秒自動把VolumeUI關閉。
到此SystemUI的VolumeUI service分析完畢,VolumeUI的流程簡單清晰,代碼簡潔,閱讀方便。回顧VolumeUI整個控制流程,可用下圖總結:

這裏寫圖片描述


上圖中PhoneWindow是當前活動窗口的進程的PhoneWindow實例捕獲音量鍵事件,當前活動窗口的可以通過自身的PhoneWindow對象實例設置streamType,即控制音量鍵事件觸發的VolumeUI類型。當應用程序攔截音量鍵事件,那麼PhoneWindow將無法捕獲到音量鍵事件,此時音量鍵事件將不遵行上圖的流程。如果當前活動窗口時SystemUI,則直接由SystemUI所在進程的活動窗口的PhoneWindow對象實例獲取到音量鍵事件,這時音量鍵事件的轉發和處理和在其它進程(如Laucher)中略有不同,讀者感興趣可以自行比較,本文不再論述。

 

3.2、RingtonePlayer

RingtonePlayer在SystemUI中扮演播放者的角色,代碼在SystemUI/media/目錄下,RingtonePlayer的代碼很少,功能很簡單,同VolumeUI,RingtonePlayer繼承SystemUI類,SystemUI應用啓動時調用start()方法:

這裏寫圖片描述
Start()方法很簡潔,通過AudioService的句柄mAudioService的setRingtonePlayer()方法設置mCallback,mCallback實質是IRingtonePlayer對象的實例,如下圖:

這裏寫圖片描述
IRingtonePlayer有play()、stop()等方法,分別是播放音樂和停止音樂。關於RingtonePlayer的作用,讀者可以參考下圖:

這裏寫圖片描述


SystemUI啓動後通過Binder機制把IRingtonePlayer的句柄設置到AudioServie,其它應用便可通過AudioService獲取IRingtonePlayer的句柄,在通過Binder機制操作RingtonePlayerService去播放聲音文件。如果讀者感興趣可自行深入學習Ringtone策略和架構。

3.3、電源管理

電源管理即PowerUI,負責監控電源的變化和通知,源碼路徑在SystemUI/power。同樣PowerUI繼承SystemUI類,start()方法如下:

這裏寫圖片描述
上圖可以知道,start()方法註冊鑑定Setting.Global.LOW_POWER_MODE_TRIGGER_LEVEL的變化,接着調用mReceiver.init()方法,如下:

這裏寫圖片描述
圖 39註冊一個action包含Intent.ACTION_BATTERY_CHANGED的廣播接收器,監聽電量的變化。然後調用updateSaverMode()方法:

這裏寫圖片描述
如上圖,updateSaverMode()的最終實現也是很簡單,根據不同狀態顯示不同的notification。這就是PowerUI主要的功能,關於PowerUI本文就描述到這裏,如果讀者對PowerUI的更多細節感興趣,可參考相關資料。

3.4、任務管理

任務管理即Recents,代碼路徑在SystemUI/recents。Recents是手機設備基本和常用的功能,主要功能表現爲:展示所有任務,可以進行任務切換,可以進行任務的清除。同樣Recents是SystemUI類的子類,同樣啓動時從start()方法開始啓動。

這裏寫圖片描述
Recents的start()方法很簡潔,主要是sAlternateRecents.onStart()方法,sAlternateRecent是AlternateRecentsComponent的實例,AlternateRecentsComponent作爲一個組件服務,擔負着管理Recents的變化過程,AlternateRecentsComponent是單例設計模式,但一般sAlternateRecent只創建一次,過程如下:

這裏寫圖片描述

這裏寫圖片描述
圖 42通過getRecentsComponent()方法new一個AlternateRecentsComponent的對象實例,forceInitialize值是false,AlternateRecentsComponent實例化是會執行圖43中的RecentsTaskLoader.initialize()方法實例化RecentsTaskLoader,RecentsTaskLoader是Recents讀取器。接着又new一個SystemServicesProxy的實例,mSystemServicesProxy具有ActivityManage和PackageManage的功能,在讀取Recents發揮了很重要的作用。回頭看RecentsTaskLoader.initialize()的方法:

這裏寫圖片描述

在RecentsTaskLoader.initialize()的方法中,除了實例化RecentsTaskLoader自己的同時,在RecentsTaskLoader的構造函數中同時實例化比較重要的兩個變量,一個是TaskResourceLoadQueue,Recents資源隊列,另外一個是TaskResourceLoader,Recents的資源讀取器。下面根據SystemUI(Recents)的啓動過程瞭解這些類的作用。
在大多設備當中,通過長按HOME鍵打開任務管理器,HOME長按事件觸發由輸入輸出事件派發者派發到系統進程(system_process)後被WindowManagerService攔截派發到PhoneWindowManager,如圖:

這裏寫圖片描述
上圖中handleLongPressOnHome()將事件進一步發送到StatusBarManagerService,如圖:

這裏寫圖片描述


StatusBarManagerService通過CommandQueue發送到SystemUI進程,關於StatusBarManagerService和CommandQueue在StatusBar service中論述。最後發送到AlternateRecentsComponent,如圖:

這裏寫圖片描述
到圖47,事件經過了幾個進程,終於到準備啓動任務管理器的界面了,topTask是ActivityManager.RunningTaskInfo的實例,即正在系統運行的Activity,第二參數值爲true。RecentsActivity啓動回調onCreate()方法:

這裏寫圖片描述
onCreate()方法完成了實例化RecentsTaskLoader、SystemServicesProxy、RecentsConfiguration,在前面的內容已提到,RecentsTaskLoader.initialize()方法同時實例化TaskResourceLoader和TaskResourceLoadQueue,然後註冊一個監聽Intent.ACTION_SCREEN_OFF action的廣播接收器,即當屏幕熄滅後關閉RecentsActivity。RecentsActivity還複寫了onStart()方法:

這裏寫圖片描述
上圖AlternateRecentsComponent.notifyVisibilityChanged(this, ssp, true)通知StatusBar RecentsActivity已經起來,StatusBar需要配合做相應的調整,之後調用updateRecentsTasks(),由於篇幅問題,下文將重點描述Tasks的管理,不再跟隨代碼的執行流程。
RecentsTasks的管理過程主要包括讀取task,讀取task資源,顯示在RecentsActivity上,清除task。主要包括下面這些類:
SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.ActivityInfoHandle
SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader. TaskResourceLoadQueue
SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader. TaskResourceLoader
SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.Options
RecentsTaskLoader是一個管理者,RecentsTaskLoader持有TaskResourceLoader,TaskResourceLoadQueue等實例,可以創建RecentsTaskLoadPlan實例,所以RecentsTaskLoader實質並沒有去取讀取task和task資源,只是負責發起讀取task任務。ActivityInfoHandle 唯一持有一個AcitivityInfo的對象,保存Activity的信息,Activity的信息會在RecentsTaskLoadPlan中被查詢。TaskResourceLoader 負責task的資源讀取,和RecentsTaskLoader一樣,它是一個管理者,實質實現讀取task資源的是TaskResourceLoadQueue 。RecentsTaskLoadPlan對象實例也是被RecentsTaskLoader持有,實質上RecentsTaskLoadPlan是task讀取的參與者和實現者,Options 是RecentsTaskLoadPlan的內部類,主要功能是控制RecentsTaskLoadPlan的讀取task的條件。RecentsTaskLoadPlan讀取task主要分兩個階段,第一個階段是

上圖中loader是RecentsTaskLoader的實例,調用preloadTasks()方法,即task讀取的第一階段,plan是RecentsTaskLoadPlan的實例,第二個參數mConfig.launchedFromHome是boolean值,作用是isTopTaskHome,在啓動RecentsActivity之前被賦值。這個階段主要完成對task的讀取。第二個階段是:

這裏寫圖片描述

上圖中loadTasks()方法就是啓動RecentsTaskLoadPlan的第二個階段,同上圖50,loader是RecentsTaskLoader的實例,第一個參數this是Context的實例,plan是RecentsTaskLoadPlan的實例,loadOpts是Options的實例,loadOpts在這裏的作用是控制RecentsTaskLoadPlan讀取task的規則,第二個階段必須基於第一個階段,也就是說在調用loadTasks()方法前,必須先執行preloadTasks()方法,loadTasks()基於preloadTasks()中讀取到的task,從task中讀取task的資源。
這兩個階段緊密連連,有先有後,完成不同的功能,下文將具體描述這兩個階段的過程。Loader. preloadTasks()實質調用RecentsTaskLoadPlan的如下方法:

這裏寫圖片描述

在這裏會實例化一個TaskStack的對象實例,TaskStack封裝了包含List類型的類,TaskStack包含兩種類型的List,一種是存儲所有的task,一種是filter task list,同時提供一個TaskFilter工具類接口,過濾主要通過PakcageName作爲匹配。然後調用preloadRawTasks()方法實例化mRawTasks,mRawTasks是一個ListActivityManager.RecentTaskInfo>實例,mRawTasks存儲了RecentTaskInfo類型的實例,所以mRawTasks是讀取task的關鍵,看mRawTasks的實例化過程:

這裏寫圖片描述

上圖中mSystemServicesProxy是SystemServicesProxy的實例,調用圖中的方法,再往下看:

這裏寫圖片描述

上圖中mAm是ActivityManager的實例,getRecentTasksForUser()方法實質是通過binder調用了遠程的ActivityManagerService的方法,關於getRecentTasksForUser()這個方法在ActivityManagerService中的實現,讀者可以自行閱讀。在這裏得到一個ListActivityManager.RecentTaskInfo>的對象實例tasks,RecentTaskInfo實質包含什麼數據呢?如圖:

這裏寫圖片描述

上圖中是RecentTaskInfo的變量,得到這些數據,回到前面圖52的地方,看下圖:

這裏寫圖片描述


上圖中獲取到RecentTaskInfo後,把信息重新打包封裝到TaskKey中,然後讀取Activity的名字,Acitivity的圖標,和thumbnail,然後把數據保存到TaskStack中。到此,第一個階段就完成了,在這個階段完成了讀取task的基本信息RecentTaskInfo,然後讀取Activity的基本信息ActivityInfo,把數據保存到TaskStack中。
回到圖50,第一階段完成後,接着就行圖51中的第二階段loader.loadTasks(),如下圖:

這裏寫圖片描述


上圖可以看到,這裏獲取icon和thumbnail都是在第一個階段就已經完成了動作,在這裏在根據inRunningTask等條件再次刷新數據罷了。
到這裏task和task資源讀取完成,上上文中提到TaskResourceLoadQueue和TaskResourceLoader,這兩個類在RecentsTaskLoadPlan的兩個階段都有使用,用於異步讀取Activity Icon,TaskResourceLoadQueue會管理這些任務。
所有的Task數據到這裏已準備就緒,接下下就是把數據顯示在Activity上(屏幕上),對於數據怎樣綁定到View,本文不再論述。

3.5、存儲通知

存儲通知即StorageNotification service,在SystemUI中主要完成對存儲器變化的通知,即USB連接的變化,存儲器的變化SystemUI發送相應的Notification。StorageNotification的啓動如下:


StorageNotification的啓動很簡單,創建一個StorageNotification-EventListener的實例,把StorageNotificationEventListener通過StorageManager註冊到到MountService。當USB連接或存儲器發生變化是遠程回調到StorageNotificationEventListener的方法,如下圖:

這裏寫圖片描述


USB變化回調到onUsbMassStorageConnectionChangedAsync()進行處理,存儲器發生變化回調到onStorageStateChangedAsync()處理,就是在發送notification通知USB和存儲器的變化。這個模塊本文就論述到這裏,在SystemUI中StorageNotification結構清晰,功能簡單,讀者感興趣可以自行了解。

3.6、鎖屏

              鎖屏(Keyguard)service在SystemUI是一個比較特殊的模塊,特殊在於SystemUI啓動的service只是一個信息傳遞者,也就是KeyguardViewMediator,並沒有做鎖屏或解屏的實質操作。在這裏,涉及到三個比較關鍵的類是:
/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
KeyguardViewMediator和KeyguardService在源碼中位於同一個GIT庫(SystemUI)中,而KeyguardUpdateMonitor則位於KeyGuard庫中。在KeyguardViewMediator的初始化中主要做了三件事,如圖

這裏寫圖片描述
實例化KeyguardUpdateMonitor的實例mUpdateMonitor,KeyguardUpdateMonitor負責更新已經鎖屏界面上的內容,如時間,當然,KeyguardUpdateMonitor只是一個信息傳遞着,實際去刷新界面的是StatusBar模塊。Keyguard模塊通知StatusBar刷新解密那通過KeyguardUpdateMonitorCallback這個類就行遠程回調,該類的實例在StatusBar模塊啓動時通過KeyguardService獲取到IKeyguardService的遠端實例,通過IKeyguardService遠程調用IKeyguardService的addStateMonitorCallback()方法實例化KeyguardUpdateMonitorCallback對象,如下圖

這裏寫圖片描述
SystemUI啓動的Keyguard模塊並沒有真正的去操作鎖屏界面,而是作爲一個信息傳遞着把信息傳遞給StatusBar模塊。這個模塊本文就介紹到這裏。

3.7、通知欄

        通知欄(SystemBars)service是SystemUI中最重要的service,代碼量最多,最複雜的,界面結構也複雜。根據前面的內容可知,啓動SystemBars是通過調用start()方法,如下圖:

這裏寫圖片描述
這裏實質是回調到到SystemBars的onNoService()方法(這裏涉及到安全設置啓動的模式不一樣,本文只討論正常模式),最後是調用SystemBars的createStatusBarFromConfig()方法:

這裏寫圖片描述


上圖可以看到,從string資源文件裏面讀取class name,通過java的映射機制實例化對象,然後調用start()方法啓動,class name的值如下圖:

這裏寫圖片描述

該配置文件在SystemUI/res/values/config.xml中。所以實質是PhoneStatusBar調用了start()方法。
SystemBars模塊本文分兩個階段論述,第一階段是SystemBars的初始化過程,第二階段是Notification的顯示過程。第一階段主要涉及的類是:
SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
PhoneStatusBar的父類是BaseStatusBar繼承於SystemUI,上文提到,SystemBars調用PhoneStatusBar中的start()方法,下面跟隨代碼看看這個過程。

這裏寫圖片描述


如上圖,調用父類中的start()方法,即BaseStatsuBar中的start()方法。然後調用addNavigationBar()方法實例化導航條,這裏不再贅述此功能。繼續看BaseStatsuBar中的方法。

這裏寫圖片描述
如上圖BaseStatsuBar中的start()方法,實例化一個StatusBarIconList的對象,此處iconList對象是「空值」,然後通過IStatusBarService的實例mBarService對象註冊到StatusBarManager¬Service。mCommandQueue是CommandQueue的實例,在Status¬Bar-ManagerService的遠程回調,實現StatusBarManagerService和SystemUI的通信。然後調用createAndAddWindows()方法,方法初始化status bar,notification,quick settings等的View控件。在這裏,還需要注意NotificationListenerService的實例mNotificationListener的registerAsSystemService()方法,該方法主要實現StatusBarManagerService和SystemUI的notification的控制通道,也就是說,StatusBarManagerService收到notification變化時,通過此通道通知SystemUI顯示notification的變化。下文將介紹notification從StatusBarManagerService到SystemUI的過程。
一個APP需要顯示notification首先需要實例化一個NotificationManager的對象,然後調用NotificationManager的方法notify()方法把創建好的Notification對象作爲參數傳進去。

這裏寫圖片描述
上圖中可以看到一個service的對象調用了enqueueNotification-WithTag()方法,該方法實質是遠程調用NotificationManagerService中的enqueueNotificationWithTag()方法,該方法如下:

這裏寫圖片描述
這裏會把NotificationManager傳遞過來的Notification對象進行很多處理,比如變換成NotificationRecord,實質就是把Notification緩存下來。在上圖的這個過程,還有一些其它的處理邏輯,在這裏就不詳細說明了,讀者可以自行了解。上圖中在代碼的末尾調用了buzzBeepBlinkLocked()方法,該方法主要處理Notification的聲音和震動的邏輯,本文也不再詳述。接着看mListeners調用了notifyPostedLocked()方法,此方法最終會執行到如下圖的代碼

這裏寫圖片描述
首先留意上圖中final INotificationListener listener = (INotificationListener)info.service;這句代碼,info.service返回一個INotificationListener的實例對象,該對象在上文中圖66中的mNotificationListener.registerAsSystemService()方法進行設置,所以listener.onNotificationPosted()方法實質是遠程回調SystemUI中的方法,如下圖:

這裏寫圖片描述


如上圖,這裏又調用了Notification.Builder.rebuild()方法,該方法主要把通過Binder機制傳遞過來的數據重新組裝一些顯示View所需要的數據,如notification的佈局文件等等。重新組裝notification數據後,調用NotificationListenerService.this.onNotificationPosted()方法,然後代碼會執行到BaseStatusBar中的如下代碼:

這裏寫圖片描述
上圖中,代碼運行又回到熟悉的BaseStatusBar.java類中,從APP調用NotificationManager的notify()方法到BaseStatusBar的addNotification()或updateNotification()方法,經歷了一個複雜的過程,涉及的模塊多,模塊交互複雜。到此,本文就不再往下詳情說明Notification到達SystemUI的處理過程了,讀者感興趣可自行閱讀代碼。

4、總結

       本文詳細描述了SystemUI中KeyguardViewMediator、Recents、VolumeUI、SystemBars、StorageNotification、PowerUI、RingtonePlayer 7個模塊(SERVICE),其中SystemBars是SystemUI中起到中樞作用的一個模塊,因爲這個模塊和其他模塊交互最緊密,而且SystemUI中大多數UI的View都是在SystemBars中初始化和控制顯示。不過,SystemUI的功能不止本文中說論述到的,SystemUI還有例如情景模式控制、流量警告和常用的屏幕截屏等功能,本文不再去說明它們,讀者可自行去研究SystemUI中的每個功能。