深入理解Android卷II

深入理解Android卷II

 

HAL(Hardware Abstract Layer 硬件抽象層)

ActivityThread.java    路徑位於:\frameworks\base\core\java\android\app\ActivityThread.java

Activity.java               路徑位於:\frameworks\base\core\java\android\app\Activity.java

Instrumentation.java  路徑位於 :\frameworks\base\core\java\android\app\ActivityThread.java

ActivityManagerService.java 路徑位於:\frameworks\base\services\java\com\android\server\am\ActivityManagerService.java

ProcessRecord.java  路徑:  \frameworks\base\services\java\com\android\server\am\ ProcessRecord.java

TaskRecord.java       路徑:\frameworks\base\services\java\com\android\server\am\TaskRecord.java

第1章    搭建Android源碼工作環境

本章主要內容

簡單介紹系統架構、編譯環境的搭建

簡單介紹利用Eclipse調試system_process進程的方法

 

第2章    深入理解Java Binder和MessageQueue
本章主要內容
分析Binder系統的Java層框架
分析MessageQueue
概述
本章作爲本書Android分析之旅的開篇,將重點關注兩個基礎知識點
1、Binder系統在Java世界是如何佈局和工作的
2、MessageQueue的新職責
一、Java層中的Binder架構分析
Java層Binder架構總結
1、對於代表客戶端的BinderProxy來說,Java層的BinderProxy在Native層對應一個BpBinder對象。凡是從Java層發出的請求,首先從Java層的BinderProxy傳遞到Native層的BpBinder,繼而由BpBinder將請求發送到Binder驅動。
2、對於代表服務端的Service來說,Java層的Binder在Native層有一個JavaBBinder對象。前面介紹過,所有Java層的Binder在Native層都對應爲JavaBBinder,而JavaBBinder僅起到中轉作用,即把來自客戶端的請求從Native層傳遞到Java層。
3、系統中依然只有一個Native的ServiceManager。
二、心繫兩界的 MessageQueue
MessageQueue總結
1、消息處理的大家族合照
MessageQueue只是消息處理大家族中的一員。
1.1 Java層提供了Looper類和MessageQueue類,其中Looper類提供循環處理消息的機制,MessageQueue類提供一個消息隊列,以及插入、刪除和提取消息的函數接口。另外,Handler也是在java層常用的與消息處理相關的類。
1.2 MessageQueue內部通過mPtr變量保存一個Native層的NativeMessageQueue對象,mMessage保存來自Java層的Message消息。
1.3 NativeMessageQueue保存一個native的Looper對象,該Looper從ALooper派生,提供pollOnce和addFd等函數。
1.4 Java層有Message類和Handler類,而Native層對應也有Message類和MessageHandler抽象類。在編碼時,一般使用的是MessageHandler的派生類WeakMessageHandler類。
2、MessageQueue處理流程總結
MessageQueue核心邏輯下移到Native層後,極大地拓展了消息處理的範圍,總結一下有以下幾點:
2.1 MessageQueue繼續支持來自Java層的Message消息,也就是早期的Message加Handler的處理方式。
2.2 MessageQueue在Native層的代表NativeMessageQueue支持來自Native層的Message,是通過Native的Message和MessageHandler來處理的。
2.3 NativeMessageQueue還處理通過addFd添加的Request。在後面分析輸入系統時,還會大量碰到這種方式。
2.3 從處理邏輯上看,先是處理Native的Message,然後是處理Native的Request,最後纔是處理Java的Message。
本章小結
    本章先對Java層的Binder架構做了一次較爲深入的分析。Java層的Binder架構和Native層Binder架構類似,但是Java層Binder架構在通信上還是依賴Native層的Binder架構,建議想進一步瞭解Native層Binder架構工作原理的讀者,閱讀卷I“第6章深入理解Binder”。另外,本章還對MessageQueue進行了較爲深入的分析。Android2.2中那個功能簡單的MessageQueue現在變得複雜了,原因是該類的核心邏輯下移到Native層,導致現在的MessageQueue除了支持Java層的Message派發外,還新增了支持Native層Message派發以及處理來自所監控的文件句柄的事件。另外,卷I“第5章深入理解常見類”對Android2.2中的MessageQueue和Looper有詳細介紹。

 

第3章    深入理解SystemServer
本章主要內容
分析SystemServer
分析EntropyServer(熵(shang)服務,它和隨機數的生成有關)、DropBoxManagerService(該服務和系統運行時日誌的存儲與管理有關)、DiskStatsService(用於查看和監測系統存儲空間)
分析DeviceStorageMonitorServvice(用於查看和監測系統存儲空間)、SamplingProfilerService(這個服務是Android4.9新增的)以及ClipboardService(剪貼板服務)、Watchdog(看門狗)
概述
SystemServer是什麼?它是Android Java的兩大支柱之一。另外一個支柱是專門負責孵化Java進程的Zygote。這兩大支柱倒了任何一個,都會導致Android Java的崩潰(所有同Zygote孵化的Java進程都會被銷燬,而SystemServer就是由Zygote孵化而來)。若Android Java真的崩潰了,則Linux系統中的進程init會重新啓動“兩大支柱”以重建Android Java。
SystemServer和系統服務有着重要的關係。Android系統中幾乎所有的核心服務都在這個進程中,如ActivityManagerService、PowerManagerService和WindowManagerService等。那麼,作爲這些服務的本營,SystemServer會是什麼樣的呢?
3.2 SystemServer分析
SystemServer是由Zygote孵化而來的一個進程,通過ps命令,可知其進程名爲system_server
3.2.2 Service羣英會
創建一個新的線程ServcerThread,Android平臺中衆多Service都彙集於此。共有7大類43個Service(包括Watchdog),7大類服務主要包括:
1、位於第一大類的是Android的核心服務,如ActivityManagerService、WindowManagerService等。
2、位於第二大類的是和通信相關的服務,如Wifi相關服務、Telephone相關服務。
3、位於第三大類的是和系統功能相關的服務,如AudioService、MountService、UsbService等。
4、位於第四大類的是BatteryService、VibratorService等服務。
5、位於第五大類的是EntropyService、DiskStatsService、Watchdog等相對獨立的服務。
6、位於第六大類的是藍牙服務。
7、位於第七大類的是和UI緊密相關的服務,如狀態欄服務、通知管理服務等。

 

第4章    深入理解PackageManagerService
本章主要內容
分析PackageManagerService
4.1 概述
PackageManagerService是本書分析的第一個核心服務,也是Android系統中最常用的服務之一。它負責系統中Package的管理,應用程序的安裝、卸載、信息查詢等。PackageManagerService及客戶端的家族如下:
1、IPackageManager接口類中定義了服務端和客戶端通信的業務函數,還定義了內部類Stub,該類從Binder派生並實現了IPackageManager接口。
2、PackageManagerService繼承自IPackageManager.Stub類,由於Stub類從Binder派生,因此PackageManagerService將作爲服務端參與Binder通信。
3、Stub類中定義了一個內部類Proxy,該類有一個IBinder類型(實際類型爲BinderProxy)的成員變量mRemote,根據第2章介紹的Binder系統的知識,mRemote用於和服務端PackageManagerService通信。
4、IPackageManager接口中定義了許多業務函數,但是出於安全等方面的考慮,Android對外(即SDK)提供的只是一個子集,該子集被封裝在抽象類PackageManager中,客戶端一般通過Context的getPackageManager函數返回一個類型爲PackageManager的對象,該對象的實際類型是PackaManager的子類ApplicationPackageManager。這種基於接口編程的方式,雖然極大降低了模塊之間的耦合性,卻給代碼分析帶來了不小的麻煩。
5、ApplicationPackageManager類繼承自PackageManager類。它並沒有直接參與Binder通信,而是通過mPM成員變量指向一個IPackageManager.Stub.Proxy類型的對象。

4.2初識PackmageManagerService(PKMS)

PKMS作爲系統的核心服務,由SystemServer創建

4.3 PKMS的main函數分析
PKMS構造函數的工作流程大體可分3個階段
1、掃描目標文件夾之前的準備工作
2、掃描目標文件夾
3、掃描之後的工作
4.3.1 構造函數分析之前期準備工作
1、初識Settings
可用adb shell 登錄到自己的手機,然後用busybox提供的ps命令查看進程uid。
2、XML文件掃描
(1) readPermission函數分析
    其函數名可猜測它種權限有關,筆者G7手機上/system/etc/permissions/platform.xml目錄下的內容
 platform.xml文件中主要使用瞭如下4個標籤
 permission和group用於建立Linux層gid和Android層permission之間的映射關係。
 assign-permission用於指定的uid賦予相應的權限。這個權限由Android定義,用字符串表示
 library用於指定系統庫。當應用程序運行時,系統會自動爲這些進程加載這些庫
3、第一階段工作總結
在繼續征程前,先總結一下PKMS構造函數在第一階段的工作,千言萬語匯成一句話;掃描並解析XML文件,將其中的信息保存到特定的數據結構中。
4.3.2 構造函數分析之掃描Package
PKMS構造函數第二階段的工作就是掃描系統中的APK了,由於需要逐個掃描文件,因些手機上裝的程序越多,PKMS的工作量就越大,系統啓動速度也就越慢。
PKMS將掃描以下幾個目錄
1、/system/frameworks;該目錄中的文件都是系統庫,例如framework.jar、services.jar、framework-res.apk,不過scanDirLI只掃描APK文件,所以framework-res.apk是該目錄中唯一"受寵"的文件。
2、/system/app;該目錄下全是默認的系統應用,例如Browser.apk、SettingsProvicer.apk等。
3、/vendor/app;該目錄中的文件由廠商提供,即全是廠商特定的APK文件,目前市面上的廠商都把自己的應用放在/system/app目錄下。
PKMS調用scanDirLI函數進行掃描
(1)scanDirLI函數分析
(2)初會scanPackageLI函數
(3)PackageParse分析:PackageParse主要負責APK文件的解析,即解析APK文件中的AndroidManifest.xml

4、第二階段工作總結
PKMS構造函數第二階段的工作任務非常繁重,要創建比較多的對象,所以它是一個耗時耗內存的操作。在工作中,我們一直想優化該流程以加快啓動速度,例如延時掃描不重要的APK,或者保存Package信息到文件中,然後在啓動時從文件中恢復這些信息以減少APK文件讀取並解析XML的工作量。但是一直沒有一個比較完滿的解決方案,原因有很多。比如APK之間有着比較微妙的依賴關係,因此到底延時掃描哪些APK,尚不能確定。另外,筆者感到比較疑惑的一個問題是:對於多核CPU架構,PKMS可以啓動多個線程以掃描不同的目錄,但是目前代碼中還沒有尋找到相關的蛛絲馬跡。難道此處真的就不能優化了嗎?
4.3.3 構造函數分析之掃尾工作
這部分任務比較簡單,就是將第二階段收集的信息再集中整理一次,比如將有些信息保存到文件中。
4.3.4 PKMS構造函數總結
從流程角度看,PKMS構造函數的功能還算清晰,無非是掃描XML或APK文件,但是其中涉及的數據結構及它們之間的關係卻較爲複雜。這裏有一些建議供讀者參與。
1、理解PKMS構造函數工作的3個階段及其各階段的工作職責
2、瞭解PKMS第二階段工作中解析APK文件的幾個關鍵步驟。
3、瞭解重點數據結構的名字和大體功能
如果對PKMS的分析就到此爲止,則未免有些太小視它了,下面將分析幾個重量級的知識點,期望能帶領讀者全方位認識PKMS。
4.4 APK Installation分析
本節將分析APK的安裝及相關處理流程,它可能比讀者想象得要複雜,我們的行程從adb install開始
4.4.1 adb install分析
adb install有多個參數,這裏僅考慮最簡單的,如adb install frameworktest.apk。adb是一個命令,install是它的參數
1、Android4.0新增了APK安裝過程中的Verification的功能。其實就是在安裝時,把相關信息發送給指定的Verification程序(另外一個APK),由它對要安裝的APK進行檢查(Verify)。這部分內容在後面分析APK安裝時會介紹。目前,標準代碼中還沒有從事Verification工作的APK。
2、調用pm_command進行安裝,這是一個比較有意思的函數,稍後對其進行分析。
3、安裝完成後,執行shell rm刪除剛纔傳送給手機的APK文件。爲什麼會刪除呢?因爲PKMS在安裝過程中會將該APK複製一份到/data/app目錄下,所以/data/local/tmp目錄下的對應文件可以刪除了。
4.4.2 pm分析
手機端的adbd在收到客戶端發來的shell pm命令時會啓動一個shell,然後在其中執行pm。pm是什麼?爲什麼可以在shell下執行?讀者可以通過adb shell 登錄到時自己的手機,然後執行pm,看看會發現什麼。pm實際上是一個腳本。
注意:android系統中常用的monkeytest、pm、am等(這些都是腳本文件)都是以這種方式啓動的,所以嚴格來說,app_process纔是Android Java進程的老祖宗。
Pm解析參數後,最終通過PKMS的Binder客戶端調用installPackageWithVerification以完成後續的安裝工作,所以,下面進入PKMS看看安裝到底是怎麼一回事。
4.4.3 installPackageWithVerfication函數分析
1、INIT_COPY處理
2、MCS_BOUND處理
3、handleStartCopy分析
4、handleReturnCode分析
5、POST_INSTALL處理
4.4.4 APK安裝流程總結
沒想到APK的安裝流程竟如此複雜,其目的無非是讓APK中的"私人財產"公有化。相比之下,在PKMS構造函數中進行公有化改造就非常簡單。另外,如果考慮安裝到SD卡的處理流程,那麼APK的安裝將會更加複雜。
這裏要總結APK安裝過程中的幾個重要步驟
1、安裝APK到內部存儲空間這一工作流程涉及的主要對象包括:PKMS、DefaultContainerService、InstallParams和FileInstallArgs。
2、此工作流程中每個對象涉及的關鍵函數。
3、對象之間的調用通過虛線表達,調用順序通過1\2\3等標明
4.4.5 Verification介紹
PKMS的Verification工作其實就是收集安裝包的信息,然後對應的校驗者發送廣播。Verification的目的主要是在APK安裝時先觸發校驗程序對該APK進行檢查,只有檢查通過才能進行真正的安裝。
4.5 queryIntentActivities分析
PKMS除了負責Android系統中Package的安裝、升級、卸載外,還有一項很重要的職責,就是對外提供統一的信息查詢功能,其中包括查詢系統中匹配某Intent的Activities,BroadCastReceivers或Services等。本節將以查詢匹配某Intent的Activities爲例,介紹PKMS在這方面提供的服務。
正式分析queryIntentActivites之前,先來認識一下Intent及IntetnFilter。
4.5.1 Intent及IntentFilter介紹
1、Intent介紹
Intent中文是“意圖”的意思,它是Android系統中一個很重要的概念,其基本思想來源於日常生活及行爲的高度抽象。我們結合用人單位招聘例子介紹Intent背後的思想。
#假設某用人單位現需招聘人員完成某項工作。該單位首先應將其需求發給獵頭公司。(這一步可爲意圖intent,需求傳達給獵頭)
#獵頭公司從其內部的信息庫中查找合適的人選。獵頭公司除了考慮用人單位的需求外,還需要考慮求職者本身的要求,例如有些求職者對工作地點、加班等有要求。(這一步可以intentFilter)
#二者匹配後,就會得到滿足要求的求職者。之後用人單位將工作交給滿足條件的人員來完成。

2、IntentFilter介紹
"求職方"需要填寫IntentFilter來表達自己的訴求。Android規定了3項內容。
#Action:"求職方"支持的Intent動作(和Intent中的Action對應)。
#Category:"求職方"支持的Intent種類(和Intent的Category對應)。
#Data:"求職方"支持的Intent數據(和Intent的Data對應,包括URI和MIME類型)。
到此,獵頭公司已經有了需求,現在又有了求職者的信息,馬上要做的工作就是匹配查詢。在Android中,該工作稱爲Intent Resolution。將以Inent Filter列出的3項內容爲參與標準,具體步驟如下:
#首先匹配IntentFilter的Action,如果Intent設置的Action不滿足IntentFilter的Action,則匹配失敗。如果IntentFilter未設定Action,則匹配成功。
#然後檢查IntentFilter的Category,匹配方法同Action的匹配,唯一有些例外的是Category爲CATEGORY_DEFAULT的情況。
#最後檢查Data。Data的匹配過程 比較煩瑣,因爲它和IntentFilter設置的Data內容有關。Data可以包含兩個內容 URI:完整的格式爲“scheme://host:port/path”,用於指明服務器網絡地址 Datatype:指定數據的MIME類型。
要特別注意的是,uri中也可以攜帶數據的類型信息,所以在匹配過程中,還需要考慮uri中指定的數據類型。

4.5.2 Activity信息管理
 PKMS掃描APK時,PKMS將解析得到的package私有的Activity值加入到自己的數據結構mActivities中保存。
4.5.3 Intent匹配查詢分析
1、客戶端查詢
  客戶端通過ApplicationPackageManager輸出的queryIntentActivities函數向PKMS發起一次查詢請求。
2、queryIntentActivities分析
#如果Intent指明瞭Component,則直接查詢該Component對應的ActivityInfo。
#如果Intent指明瞭Package名,則根據Package名找到該Package,然後再從該Package包含的Activities中進行匹配查詢。
#如果上面條件都不滿足,則需要在全系統範圍內進行匹配查詢,這就是queryIntent的工作。
4.6 install及UserManager介紹
4.6.1 install介紹
  前面對PKMS構造函數分析時介紹過一個Installer類型的對象mInstaller,它通過socket和後臺服務installd交互,以完成一些重要的操作。
 1、installd原貌
  installd是一個native進程,代碼非常簡單,其功能就啓動一個socket,然後處理來自Installer的命令。
 2、dexOpt命令解析
 PKMS在需要一個APK或Jar包做dex優化時,會發送dexopt命令給installd,相應的處理函數爲do_dexopt;
 dexopt進程由android源碼/dalvik/dexopt/OptMain.cpp定義
 3、movefiles命令分析
 PKMS掃描完系統Package後,將發送該命令給installd,movefiles的功能和系統升級有關。
 4、doFreeCache
 第3章介紹了DeviceStorageMonitorService,當系統空間不足時,DSMS會調用PKMS的freeStorageAndNotify函數進行空間清理。該工作真正實施者是installd,相應的處理命令爲do_free_cache。
4.6.2 UserManager介紹
 UserManager是Android 4.0新增的一個功能,其作用是管理手機上的不同用戶。這一點和PC上的Windows系統比較相似。在目前的Android版本中,該功能尚未完全實現。
4.7 本章學習指導
PKMS 是本書分析的第一個重要核心服務,其中的代碼量、關聯的知識點、涉及的數據結構都比較多。這裏提出一些學習建議供讀者參考。
#從工作流程上看, PKMS 包含幾條重要的主錢。一條是PKMS 自身啓動時構造函數的工作流程,另外幾條和APK 安裝、卸載相關。每一條主線的難度都比較大,讀者可結合日常工作的需求進行單獨研究。例如研究如何加快構造函數的執行時間等。
#從數據結構上看, PKMS 涉及非常多的數據類型。如果對每個數據結構進行孤立分析,很容易陷入不可自拔的狀態。筆者建議,不妨跳出各種數據結構的具體形態,只從目的及功能角度去考慮。這裏需要讀者仔細查看前面的重要數據結構及說明示意圖。
另外,由於篇幅所限,本章還有一些內容並沒有涉及,需要讀者在學習完本章內容的基礎上自行研究。這些內容包括:
#APK 安裝在SD 卡,以及APK 從內部存儲空間轉移到SD 卡的流程。
#和Package 相關的內容,例如簽名管理、dex 優化等。
權限管理相關的內容,讀者可自行閱讀(Application Security for the Android Platforrn>> 一書。
4.8 本章小結
  本章對PackageManagerService進行了較深入的分析,首先分析了PKMS創建時構造的函數的工作流程;接着以APK安裝爲例,較詳細地講解了這個複雜的處理流程;然後又介紹了PKMS另外一項功能,即根據Intent查找匹配的Activties;最後介紹了與installd和UserManager有關的知識。

 

第5章 深入理解PowerManagerService(PMS)
本章主要內容
深入分析PowerManagerService
深入分析BatteryService和BatteryStatsService
本章所涉及的源代碼文件名及位置
PowerManagerService.java(frameworks/base/services/java/com/android/server/PowerManagerSerνice.java)
5.1 概述
  PowerManagerService負責Android系統中電源管理方面的工作。作爲系統核心服務之一,PMS與其他服務及HAL層都有交互關係,所以PMS相對PKMS來說,其社會關係更復雜,分析難度也會更大。
  先來看直接與PowerManagerService有關的類家族成員,
  #PMS從IPowerManager.Stub類派生,並實現了Watchdog.Monitor及LocalPowerManager接口。PMS內部定義了較多的成員變量,在後續分析中,我們會對其中比較重要的成員逐一介紹。
  #根據第4章介紹的知識,IPowerManager.Stub及內部類Proxy均由aidl工具處理IPower-Manager.aidl後得到。
  #客戶端使用PowerManager類,其內部通過代表BinderProxy端的mService成員變量與PMS進行跨Binder通信。
5.2 初識PMS
  PMS由SystemServer在ServerThread線程中創建。
5.2.1 PMS構造函數分析
5.2.2 init分析
  第二個關鍵點是ini t 函數,該I函數將初始化PMS 內部的?些重要成員變量, 由於此函數代碼較長,此處將分段討論。二、
  從流程角度看γinit 大體可分爲3 段。
  1、init分析之一
  第一階段的工作可分爲3 步:
  #對一些成員變量進行賦值。
  #調用nativelnit 函數初始化Native 層相關資源。
  #調用updateNativePowerStateLocked 更新Native 層的電源狀態。這個函數的調用次數較爲頻繁,後續分析時會討論。
  2、init分析之二
  init第二階段工作將創建兩個HandlerThread 對象,即創建兩個帶消息循環的工作線程。
  PMS本身由ServerThread 線程創建,但它會將自己的工作委託給兩個線程,它們分別是:
  #mScreenOffThread:按Pøwer鍵關閉屏幕時,屏幕不是突然變黑的,而是一個漸暗的過程。mScreenOffThread線程就用於控制關閉屏幕過程中的亮度調節。
  #mHandlerThread:該線程是PMS的主要工作線程。
  (1)mScreenOffThread和mHandlerThread分析
  (2)initInThread分析
  config.xml(包含PMS使用的配置參數)文件的全路徑是Android4.0源碼中的/frameworks/base/core/res/values/config.xml
  3、init分析之三
  4、init函數總結
5.2.3 systemReady分析
  下面來分析PMS第三階段的工作。此時系統中大部分服務都已創建好,即將進入就緒階段。就緒階段的工作在systemReady中完成。
  systemReady主要工作爲:
  #PMS創建SensorManager,通過它可與對應的傳感器交互。關於Android傳感器系統,將放到本書後續章節討論。PMS僅僅啓用或禁止特定的傳感器,而來自傳感器的數據將通過回調的方式通知PMS,PMS根據接收到的傳感器事件做相應處理。
  #通過setPowerState函數設置電源狀態爲ALL_BRIGHT(不考慮UseSoftwareAutoBrightness的情況)。此時屏幕及鍵盤燈都會點亮。關於setPowerState函數,後文再作詳細分析。
  #調用BatteryStatsService提供的函數,以通知屏幕打開事件,在BatteryStatsService內部將處理該事件。
  當系統中的服務都在systemReady中進行處理後,系統會廣播一次ACTION_BOOT_COMPLETED消息,而PMS也將處理該廣播。
5.2.4 BootComplete處理
5.2.5 初識PowerManagerService總結
  這一節向讀者展示了PMS的大體面貌,包括:
  #主要的成員變量及它們的作用和來歷。
  #見識了PMS中幾個主要的函數,其中有一些將留到後文進行深入分析,現在只需要瞭解其大概作用即可。
5.3 PMS WakeLock分析
  WakeLock是Android提供給應用程序獲取電子資源的唯一方法。只要還有地方在使用WakeLock,系統就不會進入休眠狀態。
  WakeLock的一般使用方法如下:
  PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
  //(1)創建一個WakeLock,注意它的參數
  PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK,"My Tag");
  //(2)獲取資源
  wl.acquire();
  ......//完成其他工作
  //(3)釋放該鎖
  wl.release();
  以上代碼中共列出3個關鍵點,本章將分析前兩個,這3個函數都由PMS的Binder客戶端的PowerManager使用,所以將本次分析劃分爲客戶端和服務端兩大部分。
5.3.1 WakeLock客戶端分析
  1、newWakeLock分析
  客戶端僅通過acquireWakeLock函數交互。
5.3.2 PMS acquireWakeLock分析
  1、acquireWakeLockLocked分析之一
  開始分析之前,有必要先介紹另外一個數據結構,它爲PowerManagerService的內部類,名字也爲WakeLock;
  2、acquireWakeLockLocked分析之二
  兩個關鍵函數,一個是gatherState,另外一個是setPowerState,
  (1)gatherState分析
  gatherState將統計當前系統內部活躍的WakeLock的minState。
  (2)setPowerState分析
  setPowerState用於設置電源狀態
  (3)sendNotificationLocked函數分析
  sendNotificationLocked函數用於觸發SCREEN_ON/OFF廣播的發送
  (4)acquireWakeLocked第二階段工作總結
  acquireWakeLocked第二階段工作是處理和屏幕相關的WAKE_LOCK方面的工作(isScreenLock返回爲true的情況)。其中一個重要的函數就是setPowerState,該函數將根據不同的狀態設置屏幕光、鍵盤燈等硬件設備。
  注意,和硬件交互相關的工作是通過Power類提供的接口完成的。
  3、acquireWakeLockLocked分析之三
  這部分acquireWakeLocked主要處理WAKE_LOCK爲Partial_wake_lock的情況。
  當客戶端使用Partial_wake_lock時,PMS會調用Power.acquireWakeLock申請一個內核的WakeLock;
  4、acquireWakeLock總結
  #如果對應的WakeLock 不存在,則創建一個WakeLock 對象,同時將WAKE_LOCK標誌轉換成對應的minState;否則,從mLocks中查找對應WakeLock對象,然後更新其中的信息。
  #當WAKE_LOCK 標誌和屏幕有關時,需要做相應的處理,例如點亮屏幕、打開按鍵燈等。實際上這些工作不僅影響電源管理,還會影響到用戶感受,所以其中還穿插了一些和用戶體驗有關的處理邏輯(如上面註釋的mPrevèntScreenOn 變量)。
  #當WAKE_LOCK 和PARTEAL_WAKE_LOCK有關時,僅簡單調用Power的acquireWakeLock即可,其中涉及和Linux kernel電源管理系統的交互。
5.3.3 Power類及LightService類介紹
  根據前面的分析,PMS有時需要進行點亮屏幕、打開鍵盤燈等操作,爲此Android提供了Power類及LightService滿足PMS的要求。這兩個類比較簡單,但是其背後的Kernel層相對複雜一些。
  1、Power類介紹
  2、LightService介紹
  LigthService,這裏直接介紹Native層的實現,主要關注HAL層的初始化函數init_native及操作函數setLight_native。
5.3.4 WakeLock總結
  相信讀者此時已經對WakeLock機制有了比較清晰的認識,此處以flages標籤爲出發點,對WakeLock的知識點進行總結。
  #如果flags和屏幕有關(除PARIAL_WAKE_LOCK外),則需要更新屏幕、燈光狀態,其中,屏幕操作通過Power類來完成,燈光操作則通過LightService類來完成。
  #如果FLAGS是pARIAL_WAKE_LOCK,則需要通過POWER提供的接口獲取Kernel層的WakeLock。
  #在WakeLock工作流程中還混雜了用戶體驗、光傳感器、接近傳感器等方面的處理邏輯。這部分代碼集中體現在setPowerState函數中。
  #WakeLock還要通知BatteryStatsService,以幫助其統計電量使用情況。
  另外,PMS在JNI層也保存了當前屏幕狀態信息,這是通過updateNativePowerStateLocked完成的。
5.4 userActivity及Power按鍵處理分析
5.4.1 userActivity分析
  前面曾經提到過userActivity 的作用,此處舉一個例子以加深讀者對它的印象。
  扛開手機,並解鎖進入桌面。如果在規定時間內不操作手機,那麼屏幕將變暗,是後關閉。在此過程中,如果觸動屏幕,屏幕又會重新變亮。這個觸動屏幕的操作將導致userActivity函數被調用。
  在上述例子中實際上包含了兩方面的內容:
  #不操作手機,屏幕將變暗,最後關閉。在PMS這是一個狀態切換的過程。
  #操作手機,將觸發userActivity,此後屏幕的狀態將重置。
5.4.2 Power按鍵處理分析
5.5 BatteryService及BatteryStatsService分析
  從前面介紹PMS的代碼中發現,PMS和系統中其他兩個服務(BatterService及BatteryStatsService)均有交互,其中:
  #BatterService提供接口用於獲取電池信息,充電狀態等。
  #BatterStatsService主要用於用電統計,通過它可知誰是系統中的“耗電大戶”。
5.5.1 BatteryService分析
  1、native_update函數分析
  讀者可通過dumpsys battery查看自己手機的電池信息
  2、processValues分析
  獲取了電池信息後,BatteryService就要做一些處理,此項工作通過processValues完成。
  另外,當電池信息發生改變時,系統會發送uevent事件給BatteryService,此時BatteryService只要重新調用update即可完成工作。
5.5.2 BatteryStatsService(BSS)分析
  BSS主要功能是收集系統中各模塊和應用進程用電量情況。抽象地說,BSS就是一塊“電錶”,不過這塊“電錶”不只是顯示總的耗電量,而是分門別類地顯示耗電量,力圖做到更爲精準。
  和其它服務不太一樣的是,BSS的創建和註冊是在ActivityManagerService中進行的。
  1、BatteryStatsService介紹
  讓人大跌眼鏡的是,BSS其實只是一個殼,具體功能委託BatteryStatsImpl(簡稱BSImpl)來實現
  2、初識BSImpl
  BSImpl功能是進行電量統計,那麼是否存在計量工具呢?答案是肯定的,並且BSImpl使用了不止一種計量工具。
  (1)計量工具和統計對象介紹
  BSImpl一共使用了4種計量工具
  #一共有兩大類計量工具,Counter用於計數,Timer用於計時
  #BSImpl實現了StopwatchTimer(秒錶)、SamplingTimer(抽樣計時)、Counter(計數器)和SamplingCounter(抽樣計數)等4個具體的計量工具。
  #BSImpl中還定義了一個Unpluggable接口。
  (2)BatteryStatus.Uid介紹
  在Android4.0中,和進程相關的用電量統計並非以單個PID爲劃分單元,而是以Uid爲組。
  #Wakelock用於統計該Uid對應進程使用WakeLock的用電情況。
  #Proc用於統計Uid中某個進程的電量使用情況。
  #Pkg用於統計某個特定Package的使用情況,其內部類Serv用於統計該Pkg中Service的用電情況。
  #Sensor用於統計傳感器用電情況。
  3、BSImpl流程分析
  (1)構造函數分析
  (2)ActivityManagerService和BSS交互
  PowerProfile類,它將解析Android4.0源代碼/frameworks/base/core/res/xml/power_profile.xml文件,此XML文件存儲的是各種操作(和硬件相關)的耗電情況。
  (3)BatterService和BSS交互
  (4)PowerManagerService和BSS交互
5.5.3 BatteryService及BatteryStatsService總結
  本節重點討論了BatteryService和BatteryStasService。其中,BatteryService和系統中的供電系統交互,通過它可獲取電池狀態等信息。而BatteryStatsService用於統計系統用電量的情況。就難度而言,BSS較爲複雜,原因是Android試圖對系統耗電量作非常詳細的統計,導致統計項目非常繁雜。另外,電量統計大多采用被動通知的方式(即需要其他服務主動調用BSS提供的onteXXXOn/noteXXXOff函數),這種實現方法一方面加重了其他服務的負擔,另一方面影響了這些服務未來的功能擴展。
5.6 本章學習指導
  本章最難的部分其實在BSS中,PMS和BatteryService相對較簡單。在這3項服務中,PMS是核心。讀者在研究PMS時,要注意把握以下幾個方面。
  #PMS的初期工作流程,即構造函數、init函數、systemReady函數和BootComaleted函數等。
  #PMS功能爲根據當前系統狀態(包括mUserState和mWakeLockState)去操作屏幕和燈光。而觸發狀態改變的有WakeLock的獲取和釋放、userActivity函數的調用,因此讀者也要搞清楚PMS在這兩個方面的工作原理。
  #PMS還有一部分功能和傳感器有關,其功能無非還是根據狀態操作屏幕和燈光。除非工作需要,否則只需要簡單瞭解這部分的工作流程即可。
  對BSS來說,複雜之處在於它定義了很多成員變量和數據類型,並且沒有一份電量統計標準的說明文檔,因此筆者認爲,讀者只要搞清楚那幾個計量工具和各個統計項的作用即可,如果在其他服務的代碼中看到和BSS交互的函數,那麼只需知道原因和目的即可。
  另外,電源管理需要HAL(Hardware Abstract Layer 硬件抽象層)層和Linux內核提供支持.
5.7 本章小結
  電源管理系統的核心是PowerManagerService,還包括BatteryService和BatteryStatsService。本章對Android平臺中的電源管理系統進行了較詳細的分析,其中:
  #對於PMS,本章分析了它的初始化流程、WakeLock獲取流程、userActivity函數的工作流程及Power按鍵處理流程。
  #BatteryService功能較爲簡單,讀者大概瞭解即可。
  #對於BatteryStatsService,本章對它的內部的數據結構、統計對象等進行了較詳細的介紹,並對其工作流程展開了分析。建議讀者結合Settinsg應用中的相關代碼,加深對各種計量工具及統計對象的理解。
  Settings中和電量相關的文件在Android4.0源代碼的/packages/apps/Settings/src/com/android/settings/fuelgauge/目錄中。

 

第6章 深入理解ActivityManagerService
本章主要內容:
  詳細分析ActivityManagerService
本章所涉及的源代碼文件名及位置:
  SystemServer.java(frameworks/base/services/java/com/android/server/SystemServer.java)
  ActivityManagerService.java(frameworks/base/services/java/com/android/server/am/ActivityManagerService.java)
6.1 概述
  相信絕大部分讀者對本書提到的ActivityManagerService(以後簡稱AMS) 都有所耳聞。
  AMS是Android中最核心的服務,主要負責系統中四大組件的啓動、切換、調度及應用進程的管理和調試等工作,其職責與操作系統中的進程管理和調度模塊類似,因此它在Android中非常重要。
  AMS是本書碰到的第一塊"難啃的骨頭",涉及的知識點較多。爲了幫助讀者更好地理解AMS,本章將帶領讀者按5條不同的線來分析它。
  #第一條線: 同其他服務一樣,將分析system_server中AMS的調用軌跡。
  #第二條線:以am命令啓動一個Activity爲例,分析應用進程的創建、Activity的啓動以及它們和AMS之間的交互知識。
  #第三條線和第四條線: 分別以Broadcast和Service爲例,分析AMS中Broadcast和Service的相關處理流程。其中,Service的分析將只給出流程圖,希望讀者能在前面學習的基礎上自己分析並掌握相關知識。
  #第五條線:以一個Crash的應用進程爲出發點,分析AMS如何打理該應用進程的“身後事”。
  除了這5條線外,還將統一分析在這5條線中頻繁出現的與AMS中應用進程的調度、內存管理等相關的知識。
  #AMS由ActivityManagerNative(以後簡稱AMN)類派生,並實現Watchdog.Monitor和BatteryStasImpl.BatteryCallback接口。而AMN由Binder派生,實現了IActivityManager接口。
  #客戶端使用ActivityManager類。由於AMS是系統核心服務,很多API不能開放供客戶端使用,因此設計者沒有讓ActivityManager直接加入AMS家族。ActivtyManager類內部通過調用AMN的getDefault函數得到一個ActivityManagerProxy對象,通過它可與AMS通信。
6.2 初識ActivityManagerService
  AMS由system_server的ServerThread線程創建,提取它的調用軌跡。
6.2.1 ActivityManagerService的main函數分析
  AMS的main函數將返回一個Context類型的對象,該對象在system_server中被其他服務大量使用。
  1、AThread分析
  雖然AMS的main函數由ServerThread線程調用,但是AMS自己的工作並沒有放在ServerThread中去做,而是新創建一個線程,即AThread線程。
  (1)AThread分析
  (2)AMS的構造函數分析
  2、ActivityThread.systemMain函數
  ActivityThread是Android Framework中一個非常重要的類,它代表一個應用進程的主線程(對於應用程序來說,ActivityThread的main函數確實是由該進程的主線程執行),其職責就是調度及執行在該線程中運行的四大組件。
  (1)attach函數分析
  (2)getSystemContext函數分析
  (3)systemMain函數總結
  systemMain函數調用結束後,我們得到了什麼?
  #得到一個ActivityThread對象,它代表應用進程的主進程。
  #得到一個Context對象,它背後所指向的Application環境與framework-res.apk有關。
  #進程:來源於操作系統,是在OS中看到的運行體。我們編寫的代碼一定要運行在一個進程中。
  #Android運行環境:Android努力構築了一個自己的運行環境。在這個環境中,進程的概念被模糊化了。組件的運行及它們之間的交互均在該環境中實現。
  3、ActivityThread.getSystemContext函數分析
  4、AMS的startRunning函數分析
  5、ActivityManagerService的main函數總結
  AMS的main函數的目的有兩個
  #第一個也是最容易想到的目的是創建AMS對象
  #另外一個目的比較隱晦,但是非常重要,那就是創建一個供system_server進程使用的Android運行環境。
  根據目前所分析的代碼,Android運行環境將包括兩個類成員:ActivityThread和ContextImpl(一般用它的基類Context)
6.2.2 AMS的setSystemProcess分析
  1、ActivityThread的installSystemApplicationInfo函數
  2、ProcessRecord和IApplicationThread
  分析ProcessRecord之前,先來思考一個問題:AMS如何與應用進程交互?例如AMS啓動一個位於其他進程的Activity,由於該Activity運行在另外一個進程中,因此AMS勢必要和該進程進行跨進程通信,答案自然是通過Binder進行通信。
  3、AMS的setSystemProcess總結
  現在來總結回顧setSystemProcess的工作:
  #註冊AMS、meminfo、gfxinfo等服務到ServiceManager中。
  #根據PKMS返回的ApplicationInfo初始化Android運行環境,並創建一個代表system_server進程的ProcessRecord,從此,system_server進程也併入AMS的管理範圍內。
6.2.3 AMS的installSystemProvider函數分析
  1、AMS的generateApplicationProvidersLocked函數分析
  (1)PMS中queryContentProviders函數分析
  (2)關於ContentProvider的介紹
  2、ActivityThread的installSystemProvider函數分析
  (1)ActivityThread的installProvider函數分析
  (2)AMS的publishContentProviders分析
  3、AMS的installSystemProviders總結
  AMS的installSystemProviders函數其實就是用於啓動SettingsProvider,其中比較複雜的是ContentProvider相關的數據結構。

6.2.4 AMS的systemReady分析
  作爲核心服務,AMS的systemReady會做什麼呢?由於該函數內容較多,我們將它的工作分爲三個階段。
  1、systemReady第一階段的工作
  systemReady第一階段的工作並不輕鬆,其主要職責是發送並處理與PRE_BOOT_COMPLETED廣播相關的事情。目前代碼中還沒有接收該廣播的地方,不過從代碼中的註釋中可猜測到,該廣播接收者的工作似乎和系統升級有關。
  2、systemReady第二階段的工作
  #殺死那些在AMS還未啓動完畢就先啓動的應用進程。
  #從Settings數據庫中獲取配置信息,目前只取4個配置參數,分別是:debug_app(設置需要debug的app的名稱)、always_finish_activityes(當一個activity不再有地方使用時,是否立即對它執行destroy)、font_scale(用於控制字體放大倍數,這是Android4.0新增的功能)。以上配置項由Settings數據庫的System提供。
  3、systemReady第三階段的工作
  #調用systemReady設置的回調對象goingCallback的run函數。
  #啓動那些聲明瞭persistent的APK。
  #啓動桌面。
  先看回調對象goingCallback的run函數的工作
  (1)goingCallback的run函數分析
  #執行startSystemUi,在該函數內部啓動SystemUIService,該Service和狀態欄有關。
  #調用一些服務的systemReady函數
  #啓動Vatchdog(看門狗)
  注意 在精簡ROM時,也不能刪除SystemUi.apk。
  (2)啓動Home界面
  如前所述,resumeTopActivtyLocked將啓動Home界面,此函數非常重要也比較複雜。
  至此,AMS的Service都啓動完畢,Home也靚麗登場,整個系統準備完畢,只等待用戶的體驗了。不過在分析邏輯上還有一點沒涉及,那會是什麼呢?
  (3)發送ACTION_BOOT_COMPLETED廣播
  原來,在Home啓動成功後,AMS才發送ACTION_BOOT_COMPLETED廣播。
  4、AMS的systemReady總結
  systemReady函數完成了系統就緒的必要工作,然後它將啓動HomeActivity。至此,Android系統就全部啓動了。
6.2.5 初識ActivityManagerService總結
  本節所分析的4個關鍵函數均較複雜,與之相關的知識點總結如下:
  #AMS的main函數:創建AMS實例,其中最重要的工作是創建Android運行環境,得到一個ActivityThread和一個Context對象。
  #AMS的setSystemProcess函數:該函數註冊AMS和meminfo等服務到ServiceManager中。另外,它爲system_server創建了一個ProcessRecord對象。由於AMS是Java世界的進程管理及調試中心,要做到對Java進程一視同仁,儘管system_server貴爲系統進程,此時也不得不將其併入AMS的管理範圍內。
  #AMS的installSystemProviders函數:爲system_server加載SettingsProvider。
  #AMS的systeReady函數:做系統啓動完畢前最後一些掃尾工作。該函數調用完畢後,HomeActivity將呈現在用戶眼前。
  對AMS調用軌跡分析是我們破解AMS的第一條線,希望讀者反覆閱讀,以真正理解其中涉及的知識點,尤其是和Android運行環境及Context相關的知識。
6.3 startActivity分析
 本節將重點分析Activity的啓動過程,它是五條線中最難分析的一條,只要用心,相信讀者能啃動這塊“硬骨頭”。
6.3.1 從am說起
  am和pm(見4.4.2節)一樣,也是一個腳本,它用來和AMS交互,如啓動Activity、啓動Service、發送廣播等。其核心文件在Am.java中。
  可通過abb shell 登錄手機,然後執行am,例:am start -W -n com.dfp.test/.TestActivity
  通過am的run函數代碼可知,am最終將調用AMS的startActivityAndWait函數來處理這次啓動請求。
6.3.2 AMS的startActivityAndWait函數分析
  建議讀者先閱讀SDK文檔中關於Activity類定義的幾個函數,如startActivity、startActivityForResult、及onActivityResult等。
  1、Task、Back Stack、ActivityStack及Launch mode
  (1)關於Task及Back Stack的介紹
  例如:用戶在Android系統上想幹的三件事件,分別用A(發短信)、B(拍照片)、C(發郵件)表示,將每一件事情稱爲一個Task。一個Task還可細分爲多個子步驟,即Activity。
  提示:爲什麼叫Activity?它是一個有組織的單元,用於完成某項指定功能。
  對於多個Tack的情況來說,系統只支持一個處於前臺的Tack,即用戶當前看到的Activity所屬的Task的,其餘Task均處於後臺,這些後臺Task內部的Activity保持順序不變。用戶可以一次將整個Task挪到後臺或者置爲前臺。
  提示:用過Android手機的讀者應該知道,長按Home鍵,系統會彈出近期Task列表,使用戶能快速在多個Task間切換。
  (2)關於ActivityStack的介紹
  如何用代碼來實現這一設計呢?此處有兩點需要考慮:
  #Task內部Activity的組織方式。Android通過先入後出的方式來組織Activity。數據結構中的Stack即以這種方式工作。
  #多個Task的組織及管理方式。
  Android設計了一個ActivityStack類來負責上述工作,它的組成如下:
  #Activity由ActivityRecord表示,Task由TaskRecord表示。ActivityRecord的task成員指向該Activity所在的Task。state變量表示該Activity所處的狀態(包括Initializing、Resumed、Paused等狀態)。
  #ActivityStack用mHistory這個ArrayList保存ActivityRecord。令人大跌眼鏡的是,該mHistory保存了系統中所有Task的ActivityRecord,而不是針對某個Task進行保存。
  #ActivityStack的mMainStack成員比較有意思,它代表此ActivityStack是否爲主ActivityStack。有主必然有從,但是目前系統中只有一個ActivityStack,並且它的mMainStack爲true。從ActivityStack的命名可推測,Android在開發之初也想用ActivityStack來管理單個Task中的ActivityRecord,但不知何故現在的代碼實現中將所有Task的ActivityRecord都放到mHistory中了,並且依然保留mMainStack。
  #ActivityStack中沒有成員用於保存TaskRecord。
  由上述內容可知,ActivityStack採用數組的方式保存所有Task的ActivityRecord,並且沒有成員保存TaskRecord。這種實現方式有優點亦有缺點。
  #優點是少了TaskRecord一級的管理,直接以ActivityRecord爲管理單元。這種做法能降低管理方面的開銷。
  #缺點是弱化了Task的概念,結構不夠清晰。
  ActivityStack還提供findActivityLocked函數以根據Intent及ActivityInfo來查找匹配的ActivityRecord,同樣查找也是從mHistory尾端開始。其實,findTaskLocked是根據mHistory中ActivityRecord所屬的Task的情況來進行相應的查找工作。
  (3)關於Launch Mode的介紹
  Launch Mode用於描述Activity的啓動模式,目前一共有4種模式,分別是standard、singleTop、singleTask和singleInstance.初看它們,較難理解,實際上不過是Android玩的一個"小把戲"而已。啓動模式就是用於控制Activity和Task關係的。
  #standard:一個Task中可以有多個相同類型的Activity。注意,此處是相同類型的Activity,而不是同一個Activity對象。例如在Task中有A、B、C、D等4個Activity,如果再啓動A類Activity,Task就會變成A、B、C、D、A。最後一個A和第一個A是同一類型,卻並非同一對象。另外,多個Task中也可以有同類型的Activity。
  #singleTop:當某Task中有A、B、C、D等4個Activity時,如果D想再啓動一個D類型的Activity,那麼Task將是什麼樣子呢?在singleTop模式下,Task中仍然是A、B、C、D,只不過D的onNewIntent函數將被調用,而在standard模式下,Task將變成A、B、C、D、D,最後的D爲新創建的D類型Activity對象。在singleTop模式下,只有目標Activity當前正好在棧頂時纔有效,例如只有啓動處於棧頂的D時纔有用,如果啓動不處於棧頂的A、B、C等,則無效。
  #singleTask:在這種模式下,該Activity只存在一個實例,並且將和一個Task綁定。當需要此Activity時,系統會以onNewIntent方式啓動它,而不會新建Task和Activity。注意,該Activity雖只有一個實例,但是在Task中除了它之外,還可以有其他的Activity。
  #singleInstance:它是singleTask的加強版,即一個Task只能有這麼一個設置了singleInstance的Activity,不能再有別的Activity。而在singleTask模式中,Task還可以有其他的Activity。
  注意,Android建議一般的應用開發者不要輕易使用最後兩種啓動模式。因爲這些模式雖然名義上爲Launch Mode,但是它們也會影響Activity出棧的順序,導致用戶按返回鍵返回時不一致的用戶體驗。
  除了啓動模式外,Android還有其他一些標誌用於控制Activity及Task之間的關係。這裏只列舉其中一部分,詳細信息請參閱SDK文檔中Intent的相關說明。
  #Flag_Activity_New_Task:將目標Activity放到一個新的Task中。
  #Flag_Activity_Clear_Task:當啓動一個Activity時,先把和目標Activity有關聯的Task"幹掉",然後啓動一個新的Task,並把目標Activity放到新的Task中。該標誌必須和Flag_Activity_New_Task標誌一起使用。
  #Flag_Activity_Clear_Top:當啓動一個不處於棧頂的Activity時,先把排在它前面的Activity"幹掉"。例如Task有A、B、C、D等4個Activity,要啓動B,應直接把C、D“幹掉”,而不是新建一個B。

  2、ActivityStack的startActivityMayWait函數分析
  startActivityMayWait函數的目的是啓動com.dfp.test.TestActivity,假設系統之前沒有啓動過該Activity,本例最終的結果將是:
  #由於在am中設置了Flag_Activity_New_Task標誌,因此除了會創建一個新的ActivityRecord外,還會創建一個TaskRecord。
  #還需要啓動一個新的應用程序以加載並運行com.dfp.test.TestActivity的一個實例。
  #如果TestActivity不是Home,還需要停止當前正在顯示的Activity。
  我們可將這個函數分三部分進行介紹,下面先來分析第一部分
  (1)startActivityMayWait分析之一
  startActivityMayWait第一階段的工作內容相對較簡單,主要包括以下幾方面:
  #首先需要通過PKM查找匹配該Intent的ActivityInfo.
  #處理Flag_cant_save_state的情況,但系統目前不支持此情況
  #另外,獲取調用者的pid和uid.由於本例的caller爲null,故所得到的pid和uid均爲am所在進程的uid和pid。
  (2)startActivityMayWait分析之二
  啓動Activity的核心函數是startActivityLocked,該函數異常複雜,後面將用一節專門分析。
  (3)startActivityMayWait分析之三
  第三階段的工作就是根據返回值做一些處理,那麼res返回成功(即res==IActivityManager.START_SUCESS的時候)後爲何還需要等待呢?
  這是因爲目標Activity要運行在一個新應用進程中,就必須等待那個應用進程正常啓動並處理相關請求。注意,只有am設置了-W選項,纔會進入wait狀態
6.3.3 startActivityLocked分析
  startActivityLocked是startActivityMayWait第二階段的工作重點,該函數有點長。ActivityStac.java::startActivityLocked
  startActivityLocked函數的主要工作包括:
  #處理sourceRecord及resultRecord.其中,sourceRecord表示發起本次請求的Activity,resultRecord表示接收處理結果的Activity(啓動一個Activity肯定需要它完成某項事情,當目標Activity將事情成後,就需要告知請求者該事情的處理結果)。在一般情況下,sourceRecord和resultRecord應指向同一個Activity.
  #處理app switch。(檢查調用進程是否有權限切換Application)如果AMS當前禁止app switch,則只能把本次啓動請求保存起來,以行動允許app switch時再處理。從代碼中可知,AMS在處理本次請求前,會先調用doPendingActivityLaunchrLocked函數,在該函數內部將啓動之前因系統禁止app switch而保存的Pending請求。
  #調用startActivityUncheckedLocked處理本次Activity啓動請求。
  先來看app switch,它雖然是一個小變量,但是意義重大。
  1、關於resume/stopAppSwitches的介紹
  AMS提供了兩個函數,用於暫時(注意,是暫時)禁止App切換。爲什麼會有這種需求呢?因爲當某些重要(例如設置賬號等)Activity處於前臺(即用戶當前所見的Activity)時,不希望系統因用戶操作之外的原因而切換Activity(例如恰好此時收到來電信號而彈出來電界面)。
  先來看stopAppSwitches。ActivityManagerService.java::stopAppSwitches
  以上代碼中有兩點需要注意:
  #此處控制機制名爲app switch ,而不是activity switch 。爲什麼呢?因爲如果從受保護的Activity 中啓動另一個Activity ,那麼這個新Activity 的目的應該是牡對同一任務,這次啓動就不應該受app switch的制約,反而應該對其大開綠燈。目前,在執行Settings中設置設備策略(DevicePolicy)時就會stopAppSwitch。
  #執行stopAppSwitch後號,應用程序應該調用resumeAppSwitches以允許app switch, 但是爲了防止應用程序有意或無意忘記resume app switch,系統設置了一個超時時間( 5 秒),過了此超時時間,系統將處理相應的消息,其內部會resume app switch。
  再來看resumeAppSwitches函數.ActivityManagerService.java::resumeAppSwithces。
  在resumeAppSwitches中只設置mAppSwitchesAllowedTime的值爲0,它並不處理在stop的resume這段時間內積攢起的Pending請求,那麼這些請求是在何時被處理的呢?
  #從前面代碼可知,如果在執行resume app switch後,又有新的請求需要處理,則先處理那些pending請求(調用doPendingActivityLaunchesLocked).
  #在resumeAppSwitches中並未撤銷stopAppSwitches函數中設置的超時消息,所以在處理那些超時消息的過程中,也會處理pendign的請求。
  在本例中,由於不考慮app switch的情況,那麼接下來的工作就是調用startActivityUncheckedLocked函數本處理本次Activity的啓動請求。此時,我們已創建了一個ActivityRecord用於保存目標Activity的相關信息。
  2、startActivityUncheckedLocked函數分析
  startActivityUncheckedLocked函數很長,但是目的比較簡單,即爲新創建的ActivityRecord找到一個合適的Task.本例最終的結果是創建一個新的Task,其中startActivityUncheckedLocked函數的處理邏輯卻比較複雜,對它的分析可分爲三個階段,下面先看第一階段。
  (1)startActivityUncheckedLocked分析之一
  startActivityUncheckedLocked第一階段的工作還算簡單,主要確定是否需要爲新的Activity創建一個task,即是否設置Flag_Activity_New_Task標誌。
  (2)startActivityUncheckedLocked分析之二
  在本例中,目標Activity首次登場,所以前面的邏輯處理都沒有起作用,建議讀者根據具體情況分析該段代碼
  (3)startActivityUncheckedLocked分析之三
  startActivityUncheckedLocked的第三階段工作也比較複雜,不過針對本例,它將創建一個新的TaskRecord,並調用startActivityLocked函數進行處理。
  (4)startActivityLocked函數分析 ActivityStack.java::startActivityLocked
  (5)startActivityUncheckedLocked總結
  創建ActivityRecord和TaskRecord,並將ActivityRecord添加到mHistory末尾,然後調用resumeTopActivityLocked啓動它。
  3、resumeTopActivityLocked函數分析 ActivityStack.java::resumeTopActivityLocked
  resumeTopActivityLocked函數中有兩個非常重要的關鍵點:
  #如果mResumedActivity不爲空,則需要先暫停(pause)這個Activity。由代碼中的註釋可知,mResumedActivity代表上一次啓動的(即當前正顯示的)Activity.現在要啓動一個新的Activity,須先停止當前Activity,這部分工作由startPausnigLocked函數完成。
  #mResumedActivity什麼時候爲空呢?當然是在啓動全系統第一個Activity時,即啓動Home界面的時候。除此之外,該值都不會爲空。
  resumeTopActivityLocked最後將調用另外一個startSpecificActivityLocked,該函數將真正創建一個應用進程。
  (1)startSpecificActivityLocked分析 ActivityStack.java::startSpecificActivityLocked
  AMS的startProcessLocked函數將創建一個新的應用進程,下面分析這個函數
  (2)startProcessLocked分析:ActivityManagerService.java::startProcesLocked
  在以上代碼中列出兩個關鍵點,其中第一個和Flag_From_Background有關,相關知識點如下:
  #Flag_From_Background標誌發起這次啓動的Task屬於後臺任務。很顯然,手機中沒界面供用戶操作位於後臺Task中的Activity.如果沒有設置該標誌,那麼這次啓動請求就是由前臺Task因某種原因而觸發的(例如,用戶單擊某個按鈕).
  #如果一個應用進程在1分鐘內連續崩潰超過2次,則AMS會將其ProcessRecord加入所謂的mBadProcesses中。一個應用崩潰後,系統會彈出一個警告框以提醒用戶。但是,如果一個後臺Task啓動了一個"Bad Process",然後該Process崩潰,結果彈出一個警告框,那麼用戶就會覺得奇怪:“爲什麼突然彈出一個框?”因此,此處將禁止後臺Task啓動"Bad Process"。如果用戶主動選擇啓動(例如,單擊一個按鈕),則不能禁止該操作,並且要把應用進程從mBadProcesses中移除,以給它們"重新做人"人機會。當然,若作爲測試工作者,要是該應用每次啓動後都會崩潰,就需要其不停地去啓動該應用以達到測試的目的。
  提示:這其實是一種安全機制,防止不健全的程序不斷啓動可能會崩潰的組件,但是這種機制並不限制用戶的行爲。
  第二個關鍵點,即另一個startProcessLocked函數
  startProcessLocked通過發送消息給Zygote以派生一個應用進程,讀者仔細研究所發消息的內容,會發現此處並未設置和Activity相關的信息,也就是說,該進程啓動後,將完全不知道自己幹什麼,怎麼辦?下面就此進行分析。
  4、startActivity分析之半程總結
  到此,我們已經走完了startActivity分析之旅的一半路程,一路走來,我們越過了很多“險灘惡途”。此處用圖6-14(見書)來記錄前半程的各個關鍵點。
  5、應用進程的創建及初始化
  如前所述,應用進程的入口是ActivityThread的main函數,它是在主線程中執行的,其代碼請見ActivityThread.java::main
  我們知道,AMS創建一個應用進程後,會設置一個超時時間(一般是10秒)。如果超過這個時間,應用進程還沒有和AMS交互,則斷定該進程創建失敗。所以,應用進程啓動後,需要儘快和AMS交互,即調用AMS的attchApplication函數。在該函數內部將調用attachApplicationLocked,所以此處直接分析attachApplicationLocked.
  (1)attchApplicationLocked分析之一 ActivityManagerService.java::attachApplicationLocked
  attchApplicationLocked第一階段的工作比較簡單:
  #設置代表該應用進程的ProcessRecord對象的一些成員變量,如用於和應用進程交互的thread對象、進程調度優先級及oom_adj的值等。
  #從消息隊列中撤銷PROC_START_TIMEOUT_MSG.
  至此,該進程啓動成功,但是這一階段的工作僅針對進程本身(如設置調度優先級,oom_adj等),還沒有涉及和Activity啓動相關的內容,這部分工作將在第二階段完成。
  (2)attchApplicationLocked分析之二
  由以上代碼可知,第二階段的工作主要是爲調用ApplicationThread的bindApplication做準備,將在後面的章節中分析該函數的具體內容。
  (3)attchApplicationLocked分析之三
  attchApplicationLocked第三階段的工作就是通知應用進程啓動Activity和Service等組件,其中用於啓動Activity的函數是ActivityStack.realStartActivityLocked.
  (4)ApplicationThread的bindApplication分析 ActivityThread.java::bindApplication
  由以上代碼可知,ApplicationThread接收到來自AMS的指令後,會將指令中的參數封裝到一個數據結構中,然後通過發送消息的方式轉交給主線程去處理。Bind_Application最終將由handleBinApplication函數處理。該函數並不複雜,但是其中有些點是值得關注的,這些點主要是初始化應用進程的一些參數。
  handleBindApplication分析 ActionThread.java::handleBindApplication
  由以上代碼可知,bindApplication函數將設置一些初始化變量,其中最重要的有:
  #創建一個Application對象,該對象是本進程中運行的第一個Application.
  #如果該Application有ContentProvider,則應安裝它們。
  (5)應用進程的創建及初始化總結
  本節從應用進程的入口函數main開始,分析了應用進程和AMS之間的兩次重要交互,它們分別是:
  #在應用進程啓動後,需要儘快調用AMS的attachApplication函數,該函數是這個剛創建的應用進程第一次和AMS交互。此時的它還默默"無名",連一個確定的進程名都沒有。不過沒關係,attachApplication函數將根據創建該應用進程之前所保存的ProcessRecord爲其準備一切"手續"。
  #attachApplication準備好一切後,將調用應用進程的bindApplication函數,在該函數內部將發送消息給主線程,最終該消息由handleBindApplication處理。handleBindApplication將爲該進程設置進程名,初始化一些策略和參數信息等。另外,它還創建一個Application對象,同時,如果該Application聲明瞭ContentProvicer,還需要爲該進程安裝ContentProvider。
  提示:這個流程有點類似生孩子,一般生之前需要到醫院去登記,生完後又去註冊戶口如此這般,這個孩子纔會在社會中有合法的身份。
  6、ActivityStack realStartActivityLocked分析 ActivityStack.java::realStartActivityLocked
  如前所述,AMS調用完bindApplication後,將通過realStartActivityLocked啓動Activity.除此之前,要創建完應用進程並初始化Android運行環境(除此之外,連ContentProvider都安裝好了)。
  在以上代碼中有兩個關鍵函數,分別是:scheduleaunchActivity和completeResumeLocked.其中,scheduleLaunchActivity用於和應用進程交互,通知它啓動目標Activity.而completeResumeLocked將繼續AMS的處理流程。先來看第一個關鍵函數。
  (1)scheduleLaunchActivity函數分析
  handleLaunchActivity的工作包括:
  #首先調用performLaunchActivity,該在函數內部通過java反射機制創建目標Activity,然後調用它的onCreate及onStart函數。
  #調用handleResumeActivity,會在其內部調用目標Activity的onResume函數。除此這外,handleResumeActivity還完成了一件很重要的事情.
  根據第2章對MessageQueue的分析,當消息隊列中沒有其他要處理的消息時,將處理以上代碼中通過addIdleHandler添加的Idler對象,也就是說,Idler對象的優先級最低,這是不是說它的工作不重要呢?非也,至少在handlerResumeActivity函數中添加的這個Idler並不簡單。
  Idler類分析 ActivityThread.java::Idler
  由以上代碼可知,Idler將爲那些已經完成onResume的Activty調用AMS的activityIdle函數。該函數是Activity成功創建並啓動的流程中與AMS交互的最後一步。雖然對應用進程來說,Idler處理的優先級最低,但AMS似乎不這麼認爲,因爲它還設置了超時等待,以處理應用進程沒有及時調用activityIdle的情況。這個超時等待即由realStartActivityLocked中最後一個關鍵點completeResumeLocked函數設置。
  (2)completeResumeLocked函數分析
  由以上代碼可知,AMS給了應用進程10秒的時間,希望它在10秒內調用activityIdle函數。這個時間不算長,和前面AMS等待應用進程啓動的超時時間一樣。所以,筆者有些困惑,爲什麼要把這麼重要的操作放到Idler中去做。
  下面來看activityIdle函數,在其內部將調用ActivityStace.activityIdleInternal.
  (3)activityIdleInternal函數分析
  在activityIdleInternal中有一個關鍵點,即處理那些因爲本次Activity啓動而被暫停的Activity.有兩種情況需要考慮:
  #如果被暫停的Activity處於finishing狀態(如Activity在其onPause中調用了finish函數),則調用finishCurrentActivityLocked.
  #否則,要調用stopActivityLocked處理暫停的Activity.
  此處涉及除AMS和目標進程外的第三個進程,即被切換到後臺的那個進程。不過至此,我們的目標Activity終於正式登上了歷史舞臺。
  7、startActivity分析之後半程總結
  總結startActivity後半部分的流程,主要涉及目標進程和AMS的交互。如時6-15(見書)
  8、startPausingLocked函數分析
  根據前面的介紹,當啓動一個新Activity時,系統將先進行處理當前的Activity,即調用startPausingLocked函數來暫停當前Activity.
  (1)startPausingLocked分析 ActivityStack.java::startPausingLocked
  startPausingLocked將調用應用進程的schedulePauseActivity函數,並設置500毫秒的超時時間,所以應用進程需要儘快完成相關處理。和scheduleLaunchActivity一樣,schedulePauseActivity將向ActivityThread主線程發送Pause_Activity消息,最終該消息由handlePauseActivity處理。
  (2)handlePauseActivity分析
  (3)completePauseLocked分析
  以上代碼最終還是通過resumeTopActivityLocked來啓動目標Activity.由於之前設置了mPausingActivity爲null.
  (4)stopActivityLocked分析
  根據前面的介紹,此次目標Activity將走完onCreate、onStart和onResume流程,但是被暫停的Activity纔剛走完onPause流程,那麼它的onStop什麼時候調用呢?答案就在activityIdelInteranl中,它將爲mStoppingActivities中的成員調用stopActivityLocked函數。
  對應進程的sheduleStopActivity函數將根據visible的情況,向主線程消息循環發送H.Stop_Activity_Hide或H.Stop_Activity_Show消息。不論哪種情況,最終都由handleStopActivity處理。
  雖然AMS沒有爲stop設置超時消息處理,但是嚴格來說,還是有超時限制的,只是這個超時處理與activityIdleInternal結合起來了。
  (5)startPausingLocked總結(見書圖6-16)
  9、startActivity總結
  Activity的啓動就介紹到這裏。先回顧一下此次旅程。
  #行程的起點是am。am是Android中很重要的程序,讀者務必要掌握它的用法。我們利用am start命令,發起本次目標Activity的啓動請求。
  #接下來進入ActivityManagerService和ActivityStack這兩個核心類。對於啓動Activity來說,這段行程又可細分爲兩個階段:
  第一階段的主要工作就是根據啓動模式和啓動標誌找到或創建ActivityRecord及對應的TaskRecord;
  第二階段工作就是處理Activity啓動或切換相關的工作。
  #然後討論了AMS直接創建目標進程並運行Activity的流程,其中涉及目標進程的創建,目標進程中Android運行環境的初始化,目標Activity的創建以及onCreate、onStart及onResume等其生命週期中重要函數的調用等相關知識點。
  #接着又討論了AMS先暫停當前Activity,然後再創建目標進程並運行Activity的流程。其中牽扯到兩個應用進程和AMS的交互,其難度之大可見一斑。
  讀者在閱讀本節時,務必要區分此旅程中兩個階段工作的重點:
  其一是找到合適的ActivityRecord和TaskRcord;
  其二是調度相關進程進行Activity切換。
  在SDK文檔中,介紹最爲詳細的第一階段中系統的處理策略,例如啓動模式、啓動標誌的作用等。第二階段工作其實是與Android組件調度相關的工作。SDK文檔只是針對單個Activity進行生命週期方面的介紹。
  此處列出幾個供讀者深入研究的點:
  #各種啓動模式、啓動標誌的處理流程。
  #Configuration發生變化時Activity的處理,以及在Activity中對狀態保存及恢復的處理流程。
  #Activity生命週期各個階段的轉換及相關處理。Android2.3以後新增的與 Fragment的生命週期相關的轉換及處理。
  建議:在研究代碼前,先仔細閱讀SDK文檔相關的內容,以獲取必要的感性認識,否則直接看代碼很容易迷失方向。

6.4.4 Broadcaset和BroadcastReceiver分析
  Broadcast,中文意思爲"廣播"。它是Android平臺中的一種通知機制。從廣義角度來說,它是一種進程間通信的手段。有廣播,就對應有廣播接收者。Android中四大組件之一BroadcaseReceiver即代表廣播接收者。目前,系統提供兩種方式來聲明一個廣播接收者。
  # 在AndroidManifest.xml中聲明<receiver>標籤。在應用程序運行時,系統會利用java反射機制構造一個廣播接收者實例。本書將這種廣播接收者稱爲靜態註冊或靜態接收者。
  # 在應用程序運行過程中,可調用Context提供的registerReceiver函數註冊一個廣播接收者實例。本書將這種廣播接收者稱爲動態註冊者或動態接收者。與之相對應,當應用程序不再需要監聽廣播時(例如當應用程序退到後臺時),則要調用unregisterReceiver函數撤銷之前註冊的BroadcaseReceiver實例。
  當系統將廣播派發給對應的廣播接收者時,廣播接收者的onReceive函數會被調用。在此函數中,可對該廣播進行相應處理。
  另外,Android定義了3種不同類型的廣播發送方式,它們分別是:
  # 普通廣播發送方式,由sendBroadcast及相關函數發送。以工作中的場景爲例,當程序員們正在埋頭工作之時,如果有人大喊一聲"吃午飯去",前刻還在專心編碼的人會馬上站起來走向餐廳。這種方式即爲普通廣播發送方式,所有對"吃午飯"感興趣的接收者都會響應。
  # 串行廣播發送方式,即ordered廣播,由sendOrdedBroadcast及相關函數發送。在該類型方式下,按接收者的優先級將廣播一個一個地派發給接收者。只有等上一個接收者處理完畢後,系統纔將該廣播派發給一下個接收者。其中,任意一個接收者都可中止後續的派發流程。還是以工作的場景爲例:經常有項目經理(PM)深夜組織一幫人跟蹤bug的狀態。PM看見一個BUG,問某程序員,”這個bug你能改嗎“?如果得到的答案是”暫時不會“或”暫時沒時間“,他會將目光轉向一下個神情輕鬆者,直到找到一個擔當者爲止。這種方式即爲orderd廣播發送方式,很明顯,它的特點是”一個一個來“。
  # sticky(粘)廣播發送方式,由sendStickyBroadcast及相關函數發送。Sticky的意思是"粘",其背後有一個很重要的考慮。我們舉個例子:假設某廣播發送者每5秒發送一次攜帶自己狀態信息的廣播,此時某個應用進程註冊了一個動態接收者來監聽該廣播,那麼該接收者的OnReceive函數何時被調用呢?在正常情況下需要等這輪的5 秒週期結束後才調用(因爲發送者在本週期結束後會主動再發一個廣播)。而在Sticky模式下,系統將馬上派發該廣播給剛註冊的接收者。注意,這個廣播是系統發送的,其中存儲的是上一次廣播發送者的狀態信息。也就是說,在 Sticky模式下,廣播接收者能立即得到一個廣播,而不用等到這一週期結束。其實就是系統會保存Sticky的廣播,當有新廣播接收者來註冊時,系統就把Sticky廣播發給它。
  下面將以動態廣播接收者爲例,分析Android對廣播的處理流程。
6.4.1 registerReceiver流程分析
  1、ContextImpl.registerReceiver分析android.app.ContextImpl
  registerReceiver函數用於註冊一個動態廣播接收者,該函數在Context.java中聲明。根據本章前面對Context家族的介紹(見書圖6-3)可知,其功能最終將通過ContextImpl類的registerReceiver函數來完成。此處可直接去看ContextImpl是如何實現此函數的。
  殊途同歸,最終的功能由registerReceiverInternal來完成
  以上代碼列出了兩個關鍵點:其一是準備一個IIntentReceiver對象;其二是調用AMS的registerReceiver函數。
  先來看IIntentReveiver,它是一個Interface,(見書圖6-17)
  由圖6-17可知:
  # BroadcasetReceiver內部有一個PendingResult類。該類是Android2.3以後新增的,用於異步處理廣播消息。例如,當BroadcastReceiver收到一個廣播時,其onReceive函數將被調用。一般都是在該函數中直接處理該廣播。不過,當該廣播處理比較耗時,還可採用異步的方式進行處理,即先調用BroadcastReveiver的goAsync函數得到一個PendingResult對象,然後將該對象放到工作線程中去處理,這樣onRecevice函數就可以立即返回而不至於耽誤太長時間(這一點對於onReceive函數被主線程調用的情況尤爲有用)。在工作線程處理完這條廣播後,需調用PendingResult的finish函數來完成整個廣播的處理流程。
  # 廣播由AMS發出,而接收及處理工作卻在另個一個進程中進行,整個過程一定涉及進程間通信。雖然在BroadcastReceiver中定義了一個廣播接收者,但是它與Binder沒有任何關係,故其並不直接參與進程間通信。與之相反,IIntentReceiver接口則和Binder有密切關係,故可推測廣播的接收是由IIntentReceiver接口來完成的。確實,在整個流程中,首先接收到來自AMS的廣播的將是該接口的Bn端,即LoadedApk.ReceiverDispather的內部類InnerReceiver。
  接收廣播的處理將放到本節最後再來分析,下面先來看AMS的registerReceiver函數。
  2、AMS的registerReceiver分析
  AMS的registerReceiver函數比較簡單,但是由於其中將出現一些新的變量類型和成員,因些接下來按分兩部分進行分析。
  (1)registerReceiver分析之一 ActivityManagerService.java::registerReceiver
  registerReceiver的返回值是一個Intent,它指向一個匹配過濾條件(由filter參數指明)的Sticky Intent.即使有多個符合條件的Intent,也只返回一個。
  BroadcastFilter及相關成員變量(見書圖6-18)
  結合代碼,對圖6-18中各數據類型和成員變量的作用及關係的解釋如下:
  # 在AMS中,BroadcastReceiver的過濾條件由BroadcastFilter表示,該類從IntentFilter派生。由於一個BroadcastReceiver可設置多個過濾條件(即多次爲同一個BroadcastReceiver對象調用registerReceiver函數以設置不同的過濾條件),故AMS使用ReceiverList(從ArrayList<BroadcastFilter>派生)這種數據類型來表達這種一對多的關係。
  # ReceiverList除了能存儲多個BroadcastFilter外,還應該有成員指向某一個具體BroadcastReceiver。如果不這樣,那麼又是如何知道到底是哪個BroadcastReceiver設置的過濾條件呢?前面說過,BroadcastReceiver接收廣播是通過IIntetnReceiver接口進行的,故ReceiverList中有receiver成員變量指向IIntentReceiver。
  # AMS提供mRegisterReceiver用於保存IIntentReceiver和對應ReceiverList的關係。此外,AMS還提供mReceiverResolver變量用於存儲所有動態註冊的BroadcastReceiver所設置的過濾條件。
  清楚這些成員變量和數據類型之間的關係後,接着來分析registerReceiver第二階段的工作。
  (2)registerReceiver分析之二
  這一階段的工作用一句話就能說清楚:爲每一個滿足IntentFilter的Sticky的Inent創建一個BroadcastRecord對象,並將其保存到mParllelBroadcasts數組中,最後,根據情況調度AMS發送廣播。
  從上邊的描述中可以看出,一旦存在滿足條件的Sticky的Intent,系統需要儘快調度廣播發送。說到這裏,想和讀者分享一種筆者在實際工作中碰到的情況。
  我們註冊了一個BroadcastReceiver,用於接收USB的連接狀態。在註冊完後,它的onReceiver函數很快就會被調用。當時筆者的一些同事認爲是註冊操作觸發USB模塊又發送了一次廣播,卻又感到有些困惑,USB模塊應該根據USB的狀態變化去觸發廣播發送,而不應理會廣播接收者的註冊操作,這到底是怎麼一回事呢?
  這種困惑的原因是,對於Sticky的廣播,一旦有接收者註冊,系統會馬上將該廣播傳遞給它們。
  和ProcessRecord及ActivityfRecord類似,AMS定義了一個BroadcastRecord數據結構,用於存儲和廣播相關的信息,同時還有兩個成員變量,它們作用和關係如圖(見書6-19)。
  在代碼中,registerReceiver將調用scheduleBroadcastsLoceked函數,通知AMS立即着手開展廣播發送工作,在其內部就發送Broadcast_Intent_Msg消息給AMS。相關的處理工作將放到後面來討論。
6.4.2 sendBroadcast流程分析 ContextImpl.java::sendBroadcast
  在SDK中同樣定義了幾個函數用於發送廣播。不過,根據之前的經驗,最終和AMS交互的函數可能通過一個接口就能完成。AMS的broadcastIntent函數的主要工作將交由AMS的broadcastIntentLocked來完成,此處直接分析broadcastIntentLocked。
  1、broadcastIntentLocked分析之一 ActivityManagerService.java::broadcastIntentLocked
  第一階段的工作主要是處理一些特殊的廣播消息。處理Time_Zone變化廣播、處理Clear_Dns_Cache廣播、處理Proxy_Change廣播
  2、broadcastIntentLocked分析之二
  第二階段的工作有兩項:
  # 查詢滿足條件的動態廣播接收者及靜態廣播接收者。
  # 當本次廣播不爲ordered時,需要儘快發送該廣播。另外,非ordered的廣播都被AMS保存在mParallelBroadcasts中。
  3、broadcastIntentLocked分析之三
  由以上代碼可知,AMS將動態註冊和靜態註冊者都合併到receiver中去了。注意,如果本次廣播不是ordered,那麼表明動態註冊者就已經在第二階段工作中被處理了。因此在合併時,將不會有動態註冊者被加到receivers中。最終所創建的廣播記錄存儲在mOrderedBroadcasts中,也就是說,不管是否串行發送,靜態接收者對應的廣播記錄都將保存在mOrderedBroadcasts中。
  爲什麼不將它們保存在mParallelBroadcasts中呢?結合代碼會發現,保存在mParallelBroadcasts中的BroadcastRecord所包含的都是動態註冊的廣播接收者信息,這是因爲動態接收者所在的進程是已經存在的(如果該進程不存在,如何去註冊動態接收者呢?),而靜態廣播接收者就不能保證它已經和一個進程綁定在一起了(靜態廣播接收者此時可能還僅僅是在AndroidManifest.xml中聲明的一個信息,相應的進程並未創建和啓動)。根據前一節分析的Activity啓動流程,AMS還需要進行創建應用進程,初始化Android運行環境等一系列複雜的操作。讀到後面的內容時你會發現,AMS將在一個循環中逐條處理mParallelBroadcasts中的成員(即派發給接收者)。如果將靜態廣播接收者也保存到mParallelBroadcasts中,會有什麼後果呢?假設這些靜態接收者所對應的進程全部未創建和啓動,那麼AMS將在那個循環中創建多個進程!這樣,系統壓力一下就上去了。所以,對於靜態註冊者,它們對應的廣播記錄都被放到mOrderedBroadcasts中保存。AMS在處理這類廣播信息時,一個進程一個進程的處理,只有處理完一個接收者,才繼續下一個接收者。這種做法的好處是,避免了驚羣效應的出現,壞處則是延時較長。
  下面進入AMS的Broadcast_Intent_Msg消息處理函數,看看情況是否如上所說。
6.4.3 Broadcast_Intent_Msg消息處理函數
  Broadcast_Intent_Msg消息將觸發processNextBroadcast函數,下面分階段來分析該函數。
  1、processNextBroadcast分析之一 \frameworks\base\services\java\com\android\server\am\ActivityManagerService.java::processNextBroadcast
  deliverToRegisteredReceiverLocked函數的功能就是派發廣播給接收者
  下面來看performReceiveLocked函數
  對於動態註冊者而言,在大部分情況下會執行上述代碼中的if分支,所以應用進程ApplicationThread的scheduleRegisteredReceiver函數將被調用。稍後再分析應用進程的廣播處理流程。
  2、processNextBroadcast分析之二
  至此,processNextBroadcast已經在一個while循環中處理完mParallelBroadcasts的所有成員了,實際了,這種處理方式也會造成驚羣效應,但影響相對較少。這是因爲對於動態註冊都來說,它們所在的應用進程已經創建並初始化成功。此處的廣播發送只是調用應用進程的一個函數而已。相比於創建進程,再初始化Android運行環境所需要的工作量,調用scheduleRegisteredReceiver的工作就比較輕鬆了。
  下面來看processNextBroadcast第二階段的工作。
  processNextBroadcast第二階段的工作比較簡單:
  # 首先根據是否處於pending狀態(mPendingBrocast不爲null)進行相關操作。讀者要認真體會代碼中的重要說明。
  # 處理超時的廣播記錄。這個超時時間是2*Broadcast_TimeOut*numReceivers。Broadcast_TimeOut默認爲10秒,初始化Android運行環境等"重體力活",故此處超時時間還乘以一個固定倍數2。
  3、processNextBroadcast分析之三
  下面來看processNextBroadcast第三階段的工作。
  對ProcessNextBroadcast第三階段的工作總結如下:
  # 如果廣播接收者爲動態註冊對象,則直接調用deliverToRegisteredReceiverLocked處理它。
  # 如果廣播接收者爲靜態註冊對象,並用該對象對應的進程已經存在,則調用processCurBroadcastLocked處理它。
  # 如果廣播接收者這靜態註冊對象,並且該對象對應的進程還不存在,則需要創建該進程。這是最糟糕的情況。
  此處,不再討論新進程創建及與Android運行環境初始化相關的邏輯,讀者可返回閱讀"attachApplicationLocked分析之三",其中有處理mPendingBroadcast的內容。
6.4.4 應用進程處理廣播分析
  下面來分析當應用進程收到廣播後的處理流程,以動態接收者爲例。
  1、ApplicationThread scheduleRegisteredReceiver函數分析
  如前所述,AMS將通過scheduleRegisteredReceiver函數將廣播交給應用進程,該函數代碼如下:ActivityThread.java::scheduleRegisteredReceiver
  就本例而言,receiver對象的真實類型爲LoadedApk.ReceiverDispatcher,來看它的performReceive函數
  scheduleRegisteredReceiver最終向主線程的Handler投遞了一個Args對象,這個對象的run函數將在主線程中被調用。
  2、Args.run分析 LoadedApk.java::Args.run
  finish的代碼很簡單,此處不再贅述,在其內部會通過sendFinished函數調用AMS的finishReceiver函數,以通知AMS。
  3、AMS的finishReceiver函數分析
  不論是ordered還是非ordered廣播,AMS的finishReceiver函數都會被調用,它的代碼如下:ActivityManagerService.java::finishReceiver
  由以上代碼可知,finishReceiver將根據情況調度下一次廣播發送。
6.4.5 廣播處理總結
  廣播處理的流程及相關知識點還算比較簡單,可以用圖(見書297頁圖6-20)來表示本例中廣播的處理流程。
  在圖6-20中,將調用函數所屬的實際對象類型標註了出來,其中第11步的MyBroadcastReceiver爲本例中所註冊的廣播接收者。
  注意:任何廣播對於靜態註冊者來說,都是ordered,而且該order是全局性的,並非只針對該廣播的接收者,故從廣播發出到靜態註冊者的onReceive函數被調用中間經歷的這段時間相對較長。
6.5 startService之按圖索驥(ji)(按照畫像去尋求好馬,比喻按照線索尋找,也比喻辦事機械、死板)
  Serivce是Android的四大組件之一。和Activity,BroadcastReceiver相比,Service定位於業務層邏輯處理,而Activity定位於前端UI層邏輯處理,BroadcastReceiver定位於通知邏輯的處理。做爲業務服務提供者,Service自有一套規則,先來看有關Service的介紹。
6.5.1 Service知識介紹
  四大組件之一的Service,其定義非常符合C/S架構中的Service的概念,即爲Client服務,處理Clicen的請求。在Android中,目前接觸最多的是Binder中的C/S架構。在這種架構中,Client通過調用預先定義的業務函數向對應的Service發送請求。作爲四大組件之一的Service,其響應Client的請求方式有兩種:
  # Client通過調用startService向Service端發送一個Intent,該Intent推薦請求信息。而Service的onStartCommand會接收該Intent,並處理之。 該方式是Android平臺特有的,藉助Intnet來傳遞請求。
  # Client調用bindService函數和一個指定的Service建立Binder關係,即綁定成功後,Client端將得到處理業務邏輯的Binder Bp端。此後Client直接調用Bp端提供的業務函數向Service端發出請求。注意,在這種方式中,Service的onBind函數被調用,如果該Service支持Binder,則需要返回一個IBinder對象給客戶端。
  以上介紹的是Service響應客戶端請求的兩種方式,讀者務必將兩者分清楚。此處,這兩種方式還影響Service對象的生命週期,簡單總結如下:
  # 對於以startService方式啓動的Service對象,其生命週期一直延續到stopSelf或stopService被調用爲止。
  # 對於以bindService方式啓動的Service對象,其生命週期延續到最後一個客戶端調用完unbindService爲止。
  注意:生命週期控制一般都涉及引用計數的使用。如果某Service對象同時支持這兩種請求方式,那麼當總引用計數減爲零時,其生命就走向終點。
  和Service相關的知識還有,當系統內存不足時,系統如何處理Service。如果Service和UI某個部分綁定(例如類似通知欄中Music播放的信息),那麼此Service優先級較高(可通過調用startForeground把自己變成一個前臺Service),系統不會輕易殺死這些Service來回收內存。
  以上這些內容都比較簡單,閱讀SDK文檔中Service的相關說明即可瞭解,具體路徑爲SDK路徑 /docs/guide/topics/fundamentals/services.html。網址:http://developer.android.com/guide/components/services.html
  本章不分析和Service相關的函數的原因有二:
  # Service的處理流程和本章重點介紹的Activity的處理流程差不多,並且Service的處理邏輯更簡單。能閱讀到此處的讀者,想必地拿下Service信心滿滿。
  # "授人以魚,不如授人以漁"。希望讀者在經歷過如此大量而又複雜的代碼分析考驗後,能學會和掌握分析方法。
6.5.2 startService流程圖
  本節將以startService爲分析對象,把相關的流程圖描繪出來,旨在幫讀者根據該流程圖自行研讀與Service相關的處理邏輯。startService調用軌跡如圖(見書圖6-21和6-22)所示。
  圖6-21列出了和startService相關的調用流程。在這個流程中,可假設Service所對應的進程已經存在。
  單獨提取圖6-21中Service所在進程對H.CREATE_SERVICE等消息的處理流程具體如圖6-22所示。
  注意:圖6-21和圖6-22中也包含了bindService的處理流程。在實際分析時,讀者可分開研究bindService和startService的處理流程。
6.6 AMS中的進程管理
  前面曾反覆提到,Android平臺中很少能接觸到進程的概念,取而代之的是明確定義的四大組件。但是作爲運行在Linux用戶空間內的一個系統或框架,Android不僅不能脫離進程,反而要大力利用Linux操作系統提供的進程管理機制和手段,更好地爲自己服務。作爲Android平臺中組件運行管理的核心服務,ActivityManagerService當仁不讓地接手了這方面的工作。目前,AMS對進程的管理僅涉及兩個方面:
  # 調節進程的調度優先級和調度策略。
  # 調節進程的oom值。
  先來看在Linex操作系統中這兩方面的進程管理和控制手段。
6.6.1 Linux進程管理介紹(詳情請見書300頁)
  1、Linux進程調度優先級和調度策略
  調度優先級和調度策略是操作系統中一個很重要的概念。簡而言之,它是系統中CPU資源的管理和控制手段。這又該如何理解?此處進行簡單介紹。讀者可自行閱讀操作系統方面的書籍以加深理解。
  # 相對於在OS(操作系統)上運行的應用進程個數來說,CPU的資源非常有限。
  # 調度優先級是OS分配CPU資源給應用進程時(即調度應用進程運行)需要參考的一個指標。一般而言,優先級高的進程將更有機會得到CPU資源。
  2、關於Linux進程oom_adj的介紹
  從Linux kernel 2.6.11開始,內核提供了進程的OOM控制機制,目的是當系統出現內存不足(Out Of Memory,OOM)的情況時,kernel可根據進程的oom_adj值的大小來選擇並殺死一些進程,以回收內存。
  簡而言之,oom_adj標示Linux進程內存資源的優先級,其可取範圍從-16到15,另外有一具特殊值-17用於禁止系統在OOM情況下殺死該進程。和nicer值一樣,oom_adj的值越高,那麼在OOM情況下,該進程越有可能被殺掉。每個進程的oom_adj初值爲0。
  另外,有必要簡單介紹一下Android爲Linux Kernel新增的lowmemorykiller(以後簡稱LMK)模塊的工作方式 。LMK的職責是根據當前內存大小去殺死對應oom_adj及以上的進程以回收內存。
6.6.2 關於Android中的進程管理的介紹
  前面介紹了Linux操作系統中進程管理(包括調度和OOM控制)方面的API,但AMS是如何利用它們的呢?這就是涉及AMS中的進程管理規則了,這裏簡單介紹相關規則。
  Android將應用進程分爲五大類,分別爲Forground類、Visible類、Service類、Background類、Empty類。這五大類的劃分各有規則。
  1、進程分類
  (1)Forground類
  該類中的進程重要性最高,屬於該類的進程包括下面幾種情況:
  # 含一個前端Activity(即onResume函數被調用過了,或者說當前正在顯示的那個Activity).
  # 含一個Service,並且該Service和一個前端Activity綁定(例如Music應用包括一個前端界面和一個播放Service,當我們一邊聽歌一邊操作Music界面時,該Service即和一個前端Activity綁定)。
  # 含一個調用了startForground的Service,或者該進程的Service正在調用其生命週期的函數(onCreate、onStart或onDestroy)。
  # 該進程中有BroadcastReceiver實際正在執行onReceive函數。
  (2)Visible類
  屬於Visible類的進程中沒有處於前端的組件,但是用戶仍然能看到它們,例如位於一個對話框的Activity界面。目前該類進程包括兩種:
  # 該進程包含一個僅onPause被調用的Activity(即它還在前臺,只不過部分界面被遮蓋)。
  # 包含一個Service,並且該Service和一個Visible(或Forground)的Activity綁定(從字面意義上看,這種情況不太好和Forground進程中第二種情況區分)。
  (3)Service類、Baskground類及Empty類
  這三類進程都沒有可見的部分,具體情況如下.
  # Service進程:該類進程包含一個Service.此Service通過startService啓動,並且不屬於前面兩類進程。這種進程一般在後臺默默地幹活,例如前面介紹的MediaScannerService。
  # Background進程:該類進程包含當前不可見的Activity(即它們的onStop被調用過)。系統保存這引起進程到一個LRU(最近最少使用)列表。當系統需要回收內存時,該列表中那些最近最少使用的進程被殺死。
  # Empty進程:這類進程中不包含任何組件。爲什麼會出現這種不包括任何組件的進程呢?其實很簡單,假設該進程僅創建一個Activity,它完成工作後主動調用finish函數銷燬(destroy)自己,之後該進程就會成爲Empty進程。系統保留Empty進程的原因是當又重新需要它們時(例如用戶在別的進程中通過startActivity啓動了它們),可以省去fork進程、創建Android運行環境等一系列漫長而艱苦的工作。
  建議:可閱讀SDK/doc/guide/topics/fundamentals/processes-and-threads.html獲取更多信息。http://developer.android.com/guide/components/processes-and-threads.html
  2、Process類API介紹
  下面介紹Android平臺中進程調度和OOM控制的API,它們統一被封裝在Process.java中,代碼:\core\java\android\os
  注意:Process.java中的大多數函數是由JNI層實現的,其中Android在調度策略設置這一功能上還有一些特殊的地方,請閱讀system/core/libcutils/sched_policy.c文件。
  3、關於ProcessList類和ProcessRecord類的介紹
  (1)ProcessList類的介紹
  ProcessList類有兩個主要功能:
  # 定義一些成員變量,這些成員變量描述了不同狀態下進程的oom_adj值。
  # 在Android4.0之後,LMK的配置參數由ProcessList綜合考慮手機總內存大小和屏幕尺寸後再行設置(在Android2.3中,LMK的配置參數在init.rc中由init進程設置,並且沒有考慮屏幕尺寸影響)。讀者可自行閱讀updateOomLevels函數。
  (2)ProcessRecord中相關成員變量的介紹
6.6.3 AMS進程管理函數分析
  在AMS中,和進程管理有關的函數只有兩個,分別是updateLruProcessLocked和updateOomAdjLocked。這兩個函數的調用點有多處,本節以attachApplication爲切入點,嘗試對它們分析。
  注意:AMS一共定義了3個updateOomAdjLocked函數,此處將其歸一類。
  先回顧一下attachApplication函數被調用的情況:AMS新創建一個應用進程,該進程啓動後最重要的就是調用AMS的attachApplication。
  attachApplication主要工作由attachApplicationLocked完成,故直接分析attachApplicationLocked。代碼如下:ActivityManagerService.java::attachApplicationLocked
  在以上代碼中調用了兩個重要函數,分別是updateLruProcessLocked函數和updateOomAdjLocked函數。
  1、updateLruProcessLocked函數分析 ActivityManagerService.java::updateLruProcessLocked
  根據前方所述,我們知道了系統中所有應用進程(同時包括system_server)的ProcessRecord信息都保存在mPidsSelfLocked成員中。除此之外,AMS還有一個成員變量mLruProcess也用於保存ProcessRecord.mLruProcesses的類型雖然是ArrayList,但其內部成員卻是按時ProcessRecord的lruWeight大小排序的。在運行過程中,AMS會根據lruWeight的變化調整mLruProcess成員的位置。
  由以上代碼可知,updateLruProcessLocked的主要工作是根據app的lruWeight值調整它在數據中的位置,lruWeight值越大,其在數組中的位置就越靠後。如果該app和某些Service(僅考慮通過bindService建立關係的那些Service)或ContentProvider有交互關係,那麼這些Service或ContentProvider所在的進程也需要調節lruWeight的值。
 2、updateOomAdjLocked函數分析

(1)unpdateOomAdjLocked分析之一
  unpdateOomAdjLocked第一階段工作包含一些較難理解的內容,具體如下:
  # 處理hidden adj,劃分9個級別
  # 根據mLruProcesses中進程個數計算每個級別平均會存在多少進程。在這個計算過程中出現了一個魔數4令人極度費解。
  # 利用一個循環從mLruProcesses末端開始對每個進程執行另一個updateOomAdjLocked函數。關於這個函數的內容,我們放到下一節再討論。
  # 判斷處於Hidden狀態的進程數是否超過限制,如果超過限制,則會殺死一些進程。
  (2)unpdateOomAdjLocked分析之二
  通過上述代碼,可獲得兩個信息。
  # Android4.0增加了新的接口類ComponentCallbacks2,其中只定義了一個函數onTrimMemory,從以上描述中可知,它主要通知應用進程一定的內存釋放。
  # Android4.0 Settings新增了一個開放人員選項,通過它可控制AMS對後臺Activity的操作。
  接下來分析在以上代碼中出現的針對每個ProcessRecord都調用updateOomAdjLocked函數
  3、第二個updateOomAdjLocked分析
  主要完成兩項工作:
  # 調用computeOomAdjLocked計算獲得某個進程的oom_adj和調度策略。
  # 調整進程的調度策略和oom_adj。
  提示:思考一個問題:爲何AMS只設置進程的調度策略,而不設置進程的調度優先級?
  看來AMS調度算法的核心就在computeOonAdjLocked中。
  4、computeOomAdjLocked分析
  這段代碼較長,其核心思想是綜合考慮各種情況以計算進程的oom_adj和調度策略。
  computeOomAdjLocked的工作比較瑣碎,實際也談不上什麼算法,僅僅是簡單地根據各情況來設置幾個值。
  5、updateOomAdjLocked調用點統計
  updateOomAdjLocked調用點很多,這裏給出其中一個updateOomAdjLocked(ProcessRecord)函數的調用點統計。如圖(見書6-23),這也說明AMS非常關注應用進程的狀況。
6.6.4 AMS進程管理總結
  本節首先向讀者介紹了Linux平臺中和進程調度、OOM管理相關的API,然後介紹了AMS如何利用這些API完成Android平臺中進程管理方面的工作,從中可以發現,AMS設置的檢查點比較密集,也就是說經常會進行進程調度方面的操作。
6.7 APP的Crash處理
  在Android平臺中,應用進程fork出來後會爲虛擬機設置一個未截獲異常處理器,即在程序運行時,如果有任何一個線程拋出了未被截獲的異常,那麼該異常最終會拋給未截獲異常處理器去處理。設置未截獲異常處理器的代碼如下:RuntimeInit.java::commonInit
  應用程序有問題是再平常不過的事情了,不過,當拋出的異常沒有被截獲時,系統又會做什麼處理呢?來看UncaughtHandler的代碼。
6.7.1 應用進程的Crash處理
  UncaughtHandler的代碼如下:RuntimeInit.java::UncaughtHandler
6.7.2 AMS的handleApplicationCrach分析
  AMS handleApplicationCrash函數的代碼如下:ActivityManagerService.java::handleApplicationCrash
  上述代碼中的crashApplication函數的代碼如下:ActivityManagerService.java::crashApplication
  以上代碼中還有一個關鍵函數makeAppCrashingLocked,其代碼如下:ActivityManagerService.java::makeAppCrashingLocked
  當App的Crash處理完後,事情並未就此結束,因爲該應用進程退出後,之前AMS爲它設置的訃告接收對象被喚醒。接下來介紹AppDeathRecipient binderDied的處理流程。
6.7.3 AppDeathRecipient binderDied分析
  1、binderDied函數分析
  binderDied函數的代碼如下:ActivityManagerService.java::AppDeathRecipient binderDied
  最終的處理函數是appDiedLocked,其中所傳遞的3個參數保存了對應死亡進程的信息。
  下面分析appDiedLocked的代碼 ActivityManagerService.java::appDiedLocked
  以上代碼中有一個關鍵函數handleAppDiedLocked,下面來看它的處理過程。
  2、handleAppDiedLocked函數分析
  該函數的代碼如下:ActivityManagerService.java::handleAppDiedLocked
  重點看上邊代碼中的cleanUpApplicationRecordLocked函數,該函數的主要功能就是處理Service、ContentProvicer及BroadcastReceiver相關的收尾工作。先來看Service方面的工作。
  (1)cleanUpApplicationRecordLocked之處理Service
  cleanUpApplicationRecordLocked函數首先處理幾個對話框(dialog),然後調用killServiceLocked函數做相關處理。作爲Service流程的一部分,讀者需要深入研究。
  (2)cleanUpApplicationRecordLocked之處理ContentProvider
  再來看cleanUpApplicationRecordLocked下一階段的工作,這一階段的工作主要和ContentProvicer有關。
  從以上的描述可知,ContentProvider所在進程和其客戶端進程實際上有着非常緊密而隱晦(之所以說其隱晦,是因爲SDK中沒有任何說明)的關係。在目前軟件開發追求模塊間儘量保持鬆耦合關係的大趨勢下,Android中的ContentProvider和其客戶端這種緊耦合的設計思路似乎不夠明智。
  (3)cleanUpApplicationRecordLocked之處理BroadcastReceiver
  在這段代碼中,除了處理BroadcastReceiver方面的工作外,還包括其他方面的收尾工作。最後,如果要重啓該應用,則需要調用startProcessLocked函數進行處理。
6.7.4 App的Crash處理總結
  分析完整個處理流程,有些讀者或許會咋舌。應用進程的誕生是一件很麻煩的事情,沒想到應用進程的善後工作居然也很費事,希望各個應用進程能“活得”更穩健點。
  圖(見書6-24)326頁,展示了應用進程Crash後的處理的流程。
6.8 本章學習指導
  本章內容較爲複雜,即使用了這麼長的篇幅來講解AMS,依然只能覆蓋其中一部分內容。讀者在閱讀本章時,一定要注意文中的分析脈絡,以搞清楚流程爲主旨。以下是本章思路總結:
  # 首先搞清楚AMS初創時期的一系列流程,這對理解Android運行環境和系統啓動流程等很有幫助。
  # 搞清楚一個最簡單的情形下Activity啓動所歷經的"磨難"。這部分流程最複雜,建議讀者在搞清書中所闡述內容的前提下結合具體問題進行分析。
  # BoradcastReceiver的處理流程相對簡單。讀者務必理解AMS發送廣播的處理流程,這對實際工作非常有幫助。例如最後在處理一個廣播時,發現靜態註冊的廣播接收者收到廣播的時間較長,研究了AMS廣播發送的流程後,將其改成了動態註冊,結果響應速度就快了很多。
  # 關於Service的處理流程希望讀者根據流程圖自行分析和研究。
  # AMS中的進程管理這一部分內容最難看懂。此處有多方面的原因,筆者覺得和缺乏相關說明有重要關係。建議讀者只瞭解AMS進程管理的大概面貌即可。另外,建議讀者不要試圖通過修改這部分代碼來優化Android的運行效率。進程調度規則向來比較複雜,只有在大量實驗的基礎上才能得到一個合適的模型。
  # AMS在處理應用進程的Crash及死亡的工作上也是不遺餘力的。這部分工作相對比較簡單,相信讀者能輕鬆掌握。
6.9 本章小結
  本章對AMS進行了有針對性的分析:
  # 分析了AMS的創建及初始化過程。
  # 以啓動一個Activity爲例,分析了Activity的啓動,應用進程的創建等一系列比較複雜的處理流程。
  # 介紹了廣播接收者及廣播發送的處理流程。Service的處理流程讀者可根據流程圖並結合代碼自行開展研究。
  # 還介紹了AMS中的進程管理及相關的知識。重點是在AMS中對LRU、oom_adj及scheduleGroup的調整。
  # 最後介紹了應用進程Crash及死亡後,AMS的處理流程。

 

第7章 深入理解ContentProvider
本章主要內容:
  # 深入分析ContentProvider的創建和啓動,以及SQLit相關的知識點。
  # 深入分析Cursor query和close函數的實現。
  # 深入分析ContentResolver openAssetFileDescriptor函數的分析
本章所涉及的源代碼文件名及位置:(見書329頁)
  MediaStore.java(base/core/java/android/provider/MediaStore.java)
  ContentResolver.java(base/core/java/android/content/ContentResolver.java)
  ContentProvider.java(base/core/java/android/content/ContentProvider.java)
  MediaProvider.java(package/providers/MediaProvider/src/java/com/android/MediaProvider/MediaProvider.java)
7.1 概述
  ContentProvider簡稱(CP)、ContentResolver簡稱(CR)
  本章重點分析ContentProvider(CP)、SQLite、Cursor query、close函數的實現及ContentResolver-openAssetFileDescriptor函數。爲了幫助讀者進一步理解本章的知識點,筆者特意挑選了4條分析路線。
  第一條:以客戶端進程通過MediaStore.Image.Media類的靜態函數query來查詢MediaProvider中Image相關信息爲入口點,分析系統如何創建和啓動MediaProvider。此分析路線着重關注客戶端進程、ActivityManagerService及MediaProvider所在進程間的交互。
  第二條:沿襲第一條分析路徑,但是將關注焦點轉換到SQLiteDatabase如何創建數據庫的分析上。另外,本條路線還將對SQLite進行相關介紹。
  第三條:重點研究Cursor query和close函數的實現細節。
  第四條:分析ContentResolver openAssetFileDescriptor函數的實現。
7.2 MediaProvider的啓動及創建
  第一、二、三條分析路線都將以下面這段示例爲參考。
  MediaProvider客戶端示例。
  先介紹一下這段示例的情況:客戶端(即運行本示例的進程)查詢(query)的目標ContentProvider是MediaProvider,它運行於進程android.process.media中。假設目標進程此時還未啓動。
  本節的關注點集中在:
  # MediaProvider所在進程是如何創建的?MediaProvider又是如何創建的?
  # 客戶端通過什麼和位於目標進程中的MediaProvider交互的?
  先來看第一個關鍵函數getContentResolver。
7.2.1 Context的getContentResolver函數分析 ContextImpl.java::getContentResolver
  根據第6章對Context的介紹,Context的getContentResolver最終會調用它所代理的ContextImpl對象的getContentResolver函數,此處直接看ContextImpl的代碼。
  該函數直接返回mContentResolver,此變量在ContextImpl初始化時創建.
  由以上代碼可知,mContentResolver的真實類型是ApplicationContentResolver,它是ContextImpl定義的內部類並繼承了ContentResolver.
7.2.2 MediaStore.Image.Media的query函數分析
  第二個關鍵函數是在MediaProvider客戶端示例中所調用的MediaStore.Image.Media的query函數。MediaStore是多媒體開發中常用的類,其中內部定義了專門針對Image、Audio、Video等不同多媒體信息的內部類來幫助客戶端開發人員更好地和MediaProvider交互。這些類及相互之間的關係如圖(見書332)7-1所示。
  由圖可知,MediaStore定義了較多的內部類,我們重點展示作爲內部類之一的Image的情況,其中:
  # MediaColumns定義了所有與媒體相關的數據庫表都會用到的數據庫字段,而ImageColumns定義了單獨針對Image的數據庫字段。
  # Image類定義了一個名爲Media的內部類用於查詢和Image相關的信息,同時Image類還定義了一個名爲Thumbnails的內部類用於查詢和Image相關的縮略圖的信息(在Android平臺上,縮略圖的來源有兩種,一種是Image,另一種是Video,故Image定義了名爲Thumbnails的內部類,而Video也定義了一個名爲Thumbnails的內部類)。
  下面來看Image.Media的query函數。MediaStore.java::Media.query
  Image.Media的query函數直接調用CR的query函數,雖然CR的真實類型是ApplicationContentResolver,但是此函數卻由其基類CR實現。
  1、ContentResolver的query函數分析 ContentResolver.java::query
  CR的query將調用acquireProvider,該函數定義在CR類中
  如上所述,acquireProvider由CR的子類實現,在本例中該函數由ApplicationContentResolver定義。ContextImpl.java::acquireProvider
  如以上代碼所示,最終ActivityThread的acquireProvider函數將被調用,希望它不要再被層層轉包了。
  2、ActivityThread的acquireProvider函數分析 ActivityThread.java::acquireProvider
  在acquireProvider內部調用getProvider得到一個IContentProvider類型對象,該函數非常重要。ActivityThread.java::getProvider
  以上代碼中讓人比較頭疼的是其中新出現的幾種數據類型,如IContentProvicer、ContentProviderHolder.先來分析AMS的getContentProvider.
  3、AMS的getContentProvider函數分析
  getContentProvider的功能主要由getContentProviderImpl函數實現,故此處可直接對它進行分析。
  (1)getContentProviderImpl啓動目標進程 ActivityManagerService.java::getContentProviderImpl
  getContentProviderImpl函數較長,可分段來看
  以上代碼主要是爲目標CP(即MediaProvider)創建一個ContentProviderRecord對象。結合第6章的知識,AMS爲四大組件都設計了對應的數據結構,如ActivityRecord、BroadcastRecord等。
  接着看getContentProviderImpl,其下一步的工作就是啓動目標進程,
  通過對以上代碼的分析發現,getContentProviderImpl將等待一個事件,想必讀者也能明白,此處一定是在等待目標進程啓動並創建好MediaProvide。目標進程的這部分工作用專業詞語來表達就是發佈(publish)目標ContentProvider(即本例的MediaProvider).
  (2)MediaProvider的創建
  根據第6章的介紹,目標進程啓動後要做的第一件大事就是調用AMS的attachApplication函數,該函數的主要功能由attachApplicationLocked完成。代碼:ActivityManagerService.java::attachApplicationLocked
  再來看目標進程bindApplication的實現,其內部最終會通過handleBindApplication函數處理,代碼:ActivityThread.java::handleBindApplication
  AMS傳遞過來的ProviderInfo列表將由目標進程的installContentProviders處理,代碼:ActivityThread.java::installContentProviders
  以上代碼列出兩個關鍵點,分別是:
  # 調用installProvider得到一個IContentProvider類型的對象。
  # 調用AMS的publishContentProviders發佈本進程所運行的CP。
  在繼續分析之前,筆者要特別強調installProvider,該函數既在客戶端進程中被調用,又在目標進程中被調用。與客戶端進程的調用相比,只在一處有明顯的不同:
  # 客戶端進程調用installProvider函數時,該函數的第二個參數不爲null.
  # 目標進程調用installProvider函數時,該函數的第二個參數硬編碼爲null.
  不面來看installProvider函數,ActivityThread.java::installProvider
  由以上代碼可知,installProvider最終返回的是一個IContentProvider類型的對象。對於目標進程而言,該對象是通過調用CP的實例對象的(本例就是MediaProvider)getIContentProvider函數得到的。而對於客戶端進程而言,該對象是由installProvider第二個參數傳遞進來的,那麼,這個IContentProvider到底是什麼?
  (3)IContentProvider的真面目
  要說清楚IContentProvider,就要先來看ContentProvider家族的類圖,如圖(見書340)所示。
  (4)AMS pulishContentProvider分析
  要把目標進程的CP信息發佈出去,需要藉助AMS的pulishContentProvider函數,其代碼:ActivityManagerService.java:;publishContentProviders
  至此,客戶端進程將從getContentProvider中返回,並調用installProvider函數。根據前面的分析,客戶端進程調用installProvider時,其第二個參數不爲null,即客戶端進程已經從AMS中得到了能直接和目標進程交互的IContentProvider Bp端對象。此後,客戶端就可直接使用該對象向目標進程發起請求。
7.2.3 MediaProvider的啓動及創建
  回顧一下整個MediaProvider的啓動和創建流程,如圖7-3(見書342)
  整個流程相對簡單。讀者在分析時只要注意installProvider這個函數在目標進程和客戶端進程中被調用時的區別即可。這裏再次強調:
  # 目標進程調用installProvider時,傳遞的第二個參數爲null,使內部通過java反射機制真正創建目標CP實例。
  # 客戶端調用installProvider時,其第二個參數已經通過查詢AMS得到。該函數真正的工作只不過是引用計數控制和設置訃告接收對象罷了。
  到此,客戶端進程和目標進程通信的通道IContentProvider已經登場。除此之外,客戶端進程和目標CP還建立了非常緊密的關係,這種關係造成的後果就是一旦目標CP進程死亡,AMS會殺死與之有關的客戶端進程。回顧一下與之有關的知識點:
  # 該關係的建立是在AMS getContentProviderImpl函數中調用incProviderCount完成的,關係的確立以ContentProviderRecorder保存客戶端進程的ProcessRecord信息爲標誌。
  # 一旦CP進程死亡,AMS能根據該ContentProviderRecorder中保存的客戶端信息找到使用該CP的所有客戶端進程,然後再殺死它們。
  客戶端能否撤銷這種緊密關係呢?答案是肯定的,但這和Cursor是否關閉有關。這裏先簡單描述一下流程:
  # 當Cursor關閉時,ContentImpl的releaseProvider會被調用。根據前面的介紹,它最終會調用ActivityThread的releaseProvider函數。
  # ActivityThread的releaseProvider函數會導致completeRemoveProvider被調用,在其內部根據該CP的引用計數判斷是否需要調用AMS的removeContentProvicer.
  # 通過AMS的removeContentProvider將刪除對應ContentProviderRecord中此客戶端進程的信息,這樣一來,客戶端進程和目標CP進程的緊密關係就蕩然無存了。
  至此,本章第一條分析路線就介紹完畢了。
7.3 SQLite創建數據庫分析
  作爲Android多媒體系統中媒體信息的倉庫,MediaProvider使用了SQLite數據庫來管理系統中多媒體相關的數據信息。作爲第二條分析路線,本節的目標是分析MediaProvider如何利用SQLite創建數據庫,同時還將介紹和SQLite相關的一些知識點.
  下面先來看大名鼎鼎的SQLite及java層的SQLiteDatabase家族。
7.3.1 SQLite及java層的SQLiteDatabase家族
  1、SQLite輕裝上陣
   SQLite是一個輕量級的數據庫,它和筆者之產接觸的SQLServer或Oracle DB比起來,猶如螞蟻和大象的區別。它“輕”到什麼程序呢?筆者總結了SQLite具有的兩個特點:
   # 從代碼上看,SQLite所有的功能都實現在Sqlite3.c中,而頭文件Sqlite3.h(這裏的3是SQLite的版本號)定義了它所支持的API。其中,Sqlite3.c文件包含12萬行左右的代碼,相當於一箇中等偏小規模的程序。
   # 從使用者角度的來說,SQLite編譯完成後將生成一個libsqlite.so,大小僅爲300KB左右。
   SQLite API的使用主要集中在以下幾點上:
   # 創建代表指定數據庫的sqlite3實例。
   # 創建代表一條SQL語句的sqlite3_stmt實例,在使用過程中首先要調用sqlite3_prepare將其和一條代表SQL語句的字符串綁定。如該字符串包含有通配符"?",後續就需要通過sqlite3_bind_xxx函數爲通配符綁定特定的值以生成一條完整的SQL語句。最終調用sqlite3_step執行這條語句。
   # 如果是查詢(即SELECT命令)命令,則需調用sqlite3_step函數遍歷結果集,並通過sqlite3_column_xx等函數取出結果集中某一行指定列的值。
   # 最後需調用sqlite3_finalize和sqlite3_close來釋放sqlite3_stmt實例及sqlite3實例所佔據的資源。
   SQLite API的使用非常簡單明瞭。不過很可惜,這份簡單明瞭所帶來的快捷,只供那些Native層程序開發者獨享。對於java程序員,他們只能使用Android在SQLite API之上所封裝的SQLiteDatabase家族提供的類和相關API.(參閱frameworks/base/core/java/android/database 目錄中的文件),包括龐大而複雜的SQLiteDatabase家族,其成員有61個之多。
   2、SQLiteDatabase家族介紹
   圖(見書347)7-4展示了SQLiteDatabase家族中的幾位重要成員。
   相關類的說明如下:
   # SQLiteOpenHelper是一個幫助(Utility)類,用於方便開發者創建和管理數據庫。
   # SQLiteQueryBuilder是一個幫助類,用於幫助開發者創建SQL語句。
   # SQLiteDatabase代表SQLite數據庫,它內部封裝了一個Nativie層的sqlite3實例。
   # Android提供了3個類,即SQLiteProgram、SQLiteQuery和SQLiteStatement用於描述和SQL語句相關的信息。從圖7-4可知,SQLiteProgram是基類,它提供了一些API用於參數綁定。SQLiteQuery主要用於query查詢操作,而SQLiteStatement用於query之外的一些操作(根據SDK的說明,如果SQLiteStatement用於query查詢,其返回的結果集只能是1行X1列的)。注意,在這3個類中,基類SQLiteProgram將保存一個指向Native層的sqlite3_stmt實例的變量,但是這個成員變量的賦值卻和另外一個對開發者隱藏的類SQLiteCompiledSql有關。從這個角度看,可以認爲Native層sqlite3_stmt實例的封裝是由SQLiteCompiledSql完成的。這方面的知識在後文進行分析時即能見到。
   # SQLiteClosable用於控制SQLiteDatabase家族中一些類的實例的生命週期,例如SQLiteDatabase實例和SQLiteQuery實例。每次使用這些實例對象前都需要調用acquireReference以增加引用計數,使用完畢後都需要調用releaseReference以減少引用計數。
   下面來看MediaProvider是如何使用SQLiteDatabase的,重點關注SQLite數據庫是如何創建的。
7.3.2 MediaProvider創建數據庫分析
  在MediaProvider中觸發數據庫的是attach函數,其代碼如下:MediaProvicer::attach
  以上代碼中列出了兩個關鍵點,分別是:
  # 構造一個DatabaseHelper對象
  # 調用DatabaseHelper對象的getWritableDatabase函數得到一個代表SQLite數據庫的SQLiteDatabase對象。
  1、DatabaseHelper分析
  DatabaseHelper是MediaProvider的內部類,它從SQLiteOpenHelper派生。
  (1)DatabaseHelper構造函數分析
  DatabaseHelper構造函數的代碼如下:MediaProvicer.java::DatabaseHelper
  SQLiteOpenHelper作爲DatabaseHelper的基類,其構造函數的代碼如下:SQLiteOpenHelper.java::SQLiteOpenHelper
  上面這些函數都比較簡單,其中卻蘊含一個較爲深刻的設計理念,具體如下:從SQLiteOpenHelper的構造函數中可知,MediaProvider對應的數據庫對象(即SQLiteDatabase實例)並不在該函數中創建。那麼,代表數據庫的SQLiteDatabase實例是何時創建呢?此處使用了所謂的延遲創建(lazy creation)的方法,即SQLiteDatabase實例真正創建的時間是在第一次使用它的時候。也就是第二個關鍵函數getWritableDatabase.
  (2)getWritableDatabase函數分析,代碼如下:SQLiteDatabase.java::getWritableDatabase
  由以上代碼可知,代表數據庫的SQLiteDatabase對象是由Context openOrCreateDatabase創建的,下面分析此函數
  2、ContextImpl openOrCreateDatabase分析
  (1)openOrCreateDatabase函數分析 ContextImpl.java::openOrCreateDatabase
  openDatabase將真正創建一個SQLiteDatabase實例 SqiteDatabase.java::openDatabase
  其實openDatabase主要就幹了兩件事情,即創建一個SQLiteDatabase實例,然後調用該實例的dbopen函數。
  (2)SQLiteDatabase的構造函數及dbopen函數分析
  前面說過,java層的SQLiteDatabase對象會和一個Native層sqlite3實例綁定,從以上代碼中可發現,綁定的工作並未在構造函數中進行。實際上,該工作是由dbopen函數完成的:android_database_SQLiteDatabase.cpp:dbopen
  從上述代碼可知,使用dbopen函數其實就是爲了得到Native層的一個sqlite3實例。另外,Android對SQLite還設置了一些與平臺相關的函數,這部分後文分析。
  3、SQLiteCompiledSql介紹
  4、Android SQLite自定義函數介紹
  本節將介紹Android在SQLite上自定義的一些函數。一切還得從SQL的觸發器說起。
  (1)觸發器介紹
    觸發器(Trigger)是數據庫開發技術中一個常見的術語。其本質非常簡單,就是在指定表上發生特定事情時,數據庫需要執行的某些操作。
 # 如果沒有定義名爲images_cleanup的觸發器,就創建一個名爲images_cleanup的觸發器。
 # 設置該觸發器的觸發條件。顯然,當對images表執行delete操作時,該觸發器將被觸發。
 # 觸發器執行的操作:1、刪除表中對應的信息。
 (2)register_android_functions介紹
7.3.3 SQLiteDatabase創建數據庫的分析總結
  本節以MediaProvider創建數據庫爲入口,對SQLite及java層的SQLiteDatabase進行了介紹。其中,應重點閱讀SQLiteTest中的示例代碼,通過它可以掌握SQLite API的用法。在此基礎了,還介紹了SQLiteDatabase家族並分析了MediaProvider中數據庫創建的相關代碼。
7.4 Cursor的query函數實現分析
  本節將分析CP中另一個比較複雜的知識點,即query函數的實現。下面從CR的query函數開始分析,其代碼: ContentResolver.java::query
  上面代碼揭示了CR的query函數的工作流程:
  # 調用遠程CP的query函數,返回一個CursorWrapperInner類型的對象。
  # 該函數最終給客戶端的是一個CursorWrapperInner類型的對象。
  Cursor和CursorWrapperInner這兩個新出現的數據類型嚴重干擾了我們的思考。
7.4.1 提取query關鍵點
  1、客戶端query關鍵點
  按照以前的分析習慣,碰到Binder調用時會馬上轉入服務端(即Bn端)去分析,但是這個思想在query函數中行不通,爲什麼?來看IContentProvider Bp端的query函數,它定義在ContentProviderProxy中,代碼如下: ContentProiderNative.java::ContentProviderProxy.query
  2、服務端query關鍵點
  根據第2章對Binder的介紹,客戶端發來的請求先在Bn端的onTransact中得到處理。代碼如下:ContentProviderNative.java::onTransact
  和客戶端對應,服務端的query處理也比較複雜,在掃清這此複雜之前,應先把客戶端和服務端query調用的關鍵點總結一下。
  3、提取query關鍵點總結
  我們提取query兩端的調用關鍵點,如圖(見書361)7-5所示。
  再來總結一下前面提到的幾個“攔路虎”,它們分別是:
  # 客戶端創建的BulkCursorToCursorAdapter,以及從服務端query後得到的IBulkCursor.
  # 服務端創建的CursorToCursorAdapter,以及從子類query函數返回的Cursor.
  從名字上看,這幾個類都和Cursor有關,所以有必要先搞清MediaProvider-query返回的Cursor到底是什麼。
7.4.2 MediaProvider的query分析
  本節將分析MediaProvider的query函數。此次分析的焦點不是MediaProvider本身的業務邏輯,而是要搞清query函數返回的Cursor到底是什麼,其代碼:MediaProvider.java::query
  上邊代碼列出的兩個關鍵點分別是:
  # 調用SQLiteQueryBuilder的query函數得到一個Cursor類型的對象
  # 調用Cursor類型對象的setNotificationUri函數。從名字上看,該函數是爲Cursor對象設置通知URI。和ContentObserver有關的內容留到第8章分析。
  下面來看SQLiteQueryBuilder的query函數
  1、SQLiteQueryBuilder的query函數分析 代碼如下:SQLiteQueryBuilder.java::query
  以上代碼中又出現一個新類型,即SQLiteCursorDriver,cursor變量是其query函數的返回值。
  (1)SQLiteCursorDriver的query函數分析:代碼:SQLiteCursorDriver.java::query
  SQLiteCursorDriver的query函數的主要功能就是創建一個SQLiteQuery實例和一個SQLiteCursor實例。至此,我們終於搞清了MediaProvider的query返回的遊標對象其真實類型是SQLiteCursor.
  下面來看SQLiteQuery和SQLiteCursor爲何方神聖。
  (2)SQLiteQuery介紹
  在圖7-4中曾介紹過SQLiteQuery,它保存了和查詢(即SQL的select命令)命令相關的信息,代碼如下:SQLiteQuery.java::構造函數
  SQLiteQuery的基類是SQLiteProgram 代碼如下:SQLiteProgram.java::構造函數
  來看compileAndbindAllArgs函數,其代碼是:SQLiteProgram.java::compileAndbindAllArgs
  conpileSql函數將綁定java層SQLiteQuery對象到一個Native的sqlite3_stmt實例。根據前文的分析,這個綁定是通過SQLiteCompileSql對象實施的,代碼如下:SQLiteProgram.java::compileSql
  通過以上分析可以發現,SQLiteQuery將和一個代表select命令的sqlite3_stmt實例綁定。
  (3)SQLiteCursor分析 函數的代碼如下:SQLiteCursor.java::構造函數
  2、Cursor分析
  至此,我們已經知道了MediaProvider query返回的遊標對象的真實類型了,現在,終於可以請Cursor家族登臺亮相了,如圖7-6所示。(見書367)
  圖7-6中元素較多,包含的知識點較爲複雜,因此見書。
7.4.3 query關鍵點分析
  本節將按如下順序分析query函數中的關鍵點:
  # 介紹服務端的CursorToBulkCursorAdapter及其count函數。
  # 跨進程共享數據的關鍵類CursorWindow。
  # 客戶端的BulkCursorToCursorAdapter及其initialize函數,以及返回給客戶端使用的CursorWrapperInner類。
  1、CursorToBulkCursorAdapter函數分析
   (1)構造函數分析 代碼:CursorToBulkCursorAdapter
   構造函數簡單,看下一個重要函數,即CursorToBulkCursorAdapter的count.該函數返回本次查詢結果集所包含的行數。
   (2)count函數分析
   count最終調用SQLiteCursor的getCount函數,代碼:SQLiteCursor.java::getCount
   getCount函數將調用一個非常重要的函數,即fillWindow。顧名思義,讀者可以猜測到它的功能:將結果數據保存到CursorWindow的那塊共享內存中。
   2、CursorWindow分析
   CursorWindow的創建源於前邊代碼中對fillWindow的調用。fillWindow的代碼如下:SQLiteCursor.java::fillWindow
    (1)clearOrCreateLocalWindow函數分析 代碼:SQLiteCursor.java::clearOrCreateLocalWindow
 調用CursorWindow的構造函數 代碼如下:CursorWindow.java::CursorWindow
 nativeCreate是一個native函數,其真正實現在android_database_CursorWindow.cpp中,代碼:android_database_CursorWindow.cpp::nativeCreate
 再來看看CursorWindow的create函數,代碼:CursorWindow.cpp::create
 由以上代碼可知,CursorWindow的create函數將構造一個Native的CursorWindow對象。最終,java層的CursorWindow對象會和此Native的CursorWindow對象綁定。
 到此,用於承載數據的共享內存已創建完畢,但我們還沒有執行SQL的select語句。這個工作由SQLiteQuery的fillWindow函數完成。
 (2)SQLiteQuery fillWindow分析
 前面曾說過,SQLiteQuery保存了一個Native層的sqlite3_stmt實例,那麼它的fillWindow函數是否就是執行SQL語句後將結果信息填充到CursorWindow中了呢?可以通過以下代碼來驗證。SQLiteQuery.java::fillWindow
 來看nativeFillWindow的實現函數,其代碼是:android_database_SQLiteQuery.cpp::nativeFillWindow
 通過以上代碼可確認,fillWindow函數實現的就是將SQL語句的執行結果填充到了CursorWindow的共享內存中。
 (3)CursorWindwo分析總結
 本節向讀者介紹了CursorWindow相關的知識點。其實,CursorWindow就是對一塊共享內存的封裝。另外我們也看到了如何將執行Select語句後得到的結果集填充到這塊內存中。但是這塊內存現在還屬於服務端進程,只有客戶端進程得到這塊內存後,客戶端才能真正獲取執行select後的結果。那麼,客戶端是何時得到這塊內存的呢?讓我們回到客戶端進程。
  3、(客戶端)BulkCursorToCursorAdaptor和CursorWrapperInner分析
   客戶端的工作是先創建BulkCursorToCursorAdaptor,然後根據遠端查詢(query)結果調用BulkCursorToCursorAdaptor的intialize函數。代碼:BulkCursorToCursorAdaptor.java
   由以上代碼可知,BulkCursorToCursorAdaptor僅簡單保存了來自遠端的信息,並沒有什麼特殊操作。看來客戶端進程沒有在上面代碼的執行過程中共享內存。
   客戶端通過Image.Media.query函數,將得到一個CursorWrapperInner類型的遊標對象。當然,客戶端並不知道這麼重要的細節,它只知道自己用的接口類Cursor.根據前面的分析,此時客戶端通過空上游標對象可與服務端的CursorToBulkCursorAdaptor交互,即進程間Binder通信的通道已經打通。但是此時客戶端還未拿到那塊至關重要的共享內存,即進程間的數據通道還沒打通。那麼,數據通道是何時打通的呢?
   數據通道打通的時間和lazy creation(延遲創建)有關,即只在使用它時纔打通。
  4、moveToFirst函數分析
   根據前文的分析,客戶端從Image.Media.query函數得到的遊標對象,其真實類型是CursorWrapperInner。遊標對象的使用有一個特點,即必須先調用它的move家族的函數。這家族包括moveToFirst、moveToLast等函數。爲什麼一定要調用它們呢?來分析最常見的moveTOFirst函數,該函數實際上由CursorWrapperInner的基類CursorWrapper來實現,代碼如下:CursorWrapper.java::moveToFirst
   mCursor成員變量的真實類型是BulkCursorToCursorAdaptor,但其moveToFirst函數卻是該類的“老祖宗”AbstractCursor實現的。代碼如下:AbstractCursor.java::moveToFirst
   在上邊代碼中moveToPosition將調用子類實現的onMove函數。在本例中,子類就是BulkCursorToCursorAdaptor,接下來看它的onMove函數。
   (1)BulkCursorToCursorAdaptor的onMove函數分析 代碼如下:BulkCursorToCursorAdaptor.java::onMove
   建立數據通道的關鍵函數是lBulkCursor的getWindow。對於客戶端而言,lBulkCursor-Bp端對象的類型是BulkCursorProxy,下面介紹它的getWindow函數。
   (2)BulkCursorProxy的getWindow函數分析 代碼如下:BulkCursorNative.java::BulkCursorProxy::getWindow
   再來看lBulkCursor-Bn端的getWindow函數,此Bn端對象的真實類型是CursorToBulkCursorAdaptor.
   (3)CursorToBulkCursorAdaptor的getWindow函數分析 代碼如下:CursorToBulkCursorAdaptor.java::getWindow
   服務端返回的CursorWindow對象正是之前在count函數中創建的那個CursorWindow對象,其內部已經包含了執行本次query的查詢結果。
   另外,在將服務端的CursorWindow傳遞到客戶端之前,系統會調用CursorWindow的writeToParcel函數進行序列化工作。
   (4)SQLiteCursor的moveTOPostion函數分析
   該函數由SQLiteCursor的基類AbstractCursor實現。前面已經看過它的代碼了,其內部的主要工作就是調用AbstractCursor子類(此處就是SQLiteCursor自己)實現onMove函數,因此可直接看SQLiteCursor的onMove函數。代碼如下:SQLiteCursor.java::onMove
   以上代碼中的if判斷很重要,具體解釋如下:
   # 當mWindow爲空,即服務端未創建CursorWindow時(當然,就本例而言,CursorWindow早已在query時就創建好了),需要調用fillWindow.該函數內部將調用clearOrCreateLocalWindow。如果CursorWindow不存在,則創建一個CursorWindow對象。如果已經存在,則清空CursorWindow對象的信息。
   # 當newPostion小於上一次查詢得到的CursorWindow的起始位置,或者newPosition大於上一次查詢得到的CursorWindow的最大行位置,也需要調用fillWindow。由於此時CursorWindow已經存在,則clearOrCreateLocalWindow會調用它的clear函數以清空之前保存的信息。
   # 調用fillWindow後將執行SQL語句,以獲得正確的結果集。例如,假設上次執行query時設置了查詢是從第10行開始的90條記錄(即10-100行的記錄),那麼,當新的query若指定了從0行開始或從101行開始時,就需要重新調用fillWindow,即將新的結果填充到CursorWindow中。如果新query查詢的行數位於10-100之前,則無需再次調用fillWindow了。
   這是服務端針對query做的一些優化處理,即當CursorWindow已經包含了所要求的數據時,就沒有必要再次查詢了。按理說,客戶端也應該做類似的判斷,以避免發起不必要的Binder請求。我們回頭來看客戶端BulkCursorToCursorAdaptor的onMove函數。代碼如下:BulkCursorToCursorAdaptor.java::onMove
   (5)moveToFirst函數分析總結
   moveToFrist及其相關的兄弟函數(如moveToLast和move等)的目的是移動遊標位置到指定行。通過上面的代碼分析,我們發現它的工作其實遠不止移動遊標位置這麼簡單。對於還未擁有CursorWindow的客戶端來說,moveTOFirst將導航客戶端反序列化來自服務端的CursorWindow信息,從而使客戶端和服務端之間的數據通道真正建立起來。
7.4.4 Cursor query實現分析總結
  本節的知識點是除ActivityManagerService之外難度最大的,當我們回頭來看這一路分析旅程時,可能會有如下感知:
    AMS的難度體現在它的遊戲規則上;而query的難度體在它所做的層層封裝上(包括其創建的類和它們之間的派生關係)。從本質上說,query要做的工作其實很簡單,就是將數據複製到共享內存。這份工作的內空談不上很難,因爲它既沒有複雜的遊戲規則,也沒較高的技術門檻。
7.5 Cursor close函數實現分析
   如果寫代碼時不重視資源回收,等最後出問題進再來查找泄露點的做,會極大地增加了軟件開發的成本。雖然java提供了垃圾回收機制,但希望讀者不要把資源回收的意識丟失。
7.5.1 客戶端Close的分析
  客戶端拿到的遊標對象的真實類型是CursorWrapperInner,其close函數的代碼如下:ContentResolver.java::CursorWrapperInner.close
  下面來看CursorWrapperInner的基類CursorWrapper的close函數。這裏必須提醒讀者,後文對函數分析會頻繁從基類轉到子類,又從子類轉到基類。造成這種局面是因爲對類封裝太厲害。
  對於Cursor close函數來說,筆者更關注其中所包含的CursorWindow資源是如何釋放的。根據以上代碼中的註釋可知,BulkCursorToCursorAdaptor的close調用的基類close函數會釋放CursorWindow.
  接下來來看super.close這個函數。這個close由BulkCursorToCursorAdaptor的父類AbstractWindowedCursor的父類AbstractCursor實現,代碼如下:AbstractCursor.java::close
  onDeactivateOrClose由AbstractCursor的子類AbstractWindowedCursor實現,代碼如下:AbstractWindowedCursor.java::onDeactivateOrClose
  close函數涉及的調用居然在派生樹中如此反覆出現,這讓人很無奈!AbstractWindowedCursor.java::close
  CursorWindow從SQLiteClosable派生,根據前面的介紹,釋放SQLiteClosable代表的資源會利用引用計數來控制,這是如何實現的呢?代碼如下:CursorWindow.java::close
  releaseReference由CursorWindwo的基類SQLiteClosable實現,代碼如下:SQLiteClosable.java::releaseReference
  資源釋放的工作由子類實現的onAllReferencesReleased完成,對CursorWindow來說,該函數的代碼如下:CursorWindow.java::onAllReferencesReleased
  至此,客戶端遊標對象的Close函數已分析完畢。
7.5.2 服務端close的分析
  服務端close函數的觸發是因爲客戶端通過IBulkCursor.close函數發送的Binder請求。IBulkCursor的Bn端就是目標CP進程的CursorToBulkCursorAdaptor,其close函數的代碼如下:CursorToBulkCursorAdaptor.java::close
  調用CursorToBulkCursorAdaptor.java::disposeLocked
  調用SQLiteCursor的close函數
  至此,服務端的close函數就分析完畢,現在來回答本節最開始提出的問題,如果沒顯式調用遊標對象的close函數,那麼該對象被垃圾回收時是否會調用close函數呢?我們將在下一節用代碼來回答這個問題。
7.5.3 finalize函數分析
  遊標對象被回收前,其finalize函數將被調用。來看CursorWrapperInner的finalize函數,代碼如下:ContentResolver.java::CursorWrapperInner.finalize
  很可惜,我們寄予厚望的super.finalize函數也不會做出什麼特殊的處理。難道CursorWindow資源就沒地方處理了?這個問題的答案如下:
  # 客戶端所持有的CursorWindow資源會在該對象執行finalize時被回收。讀者可查看CursorWindow的finalize函數。
  # 前面分析過,服務端的close函數由BulkCursorToCursorAdaptor調用IBulkCursorClose函數觸發。但BulkCursorToCursorAdaptor卻沒有實現finalize函數,故BulkCursorToCursorAdaptor被回收時,並不會觸發服務端的Cursor釋放。所以,如客戶端不顯式調用close,將導致服務端進程的資源無法釋放。
7.5.4 Cursor close函數總結
 Cursor close函數並未涉及什麼較難的知識點。希望通過這一節介紹的知識點幫助讀者建立及時回收資源的意識。
7.6 ContentResolver openAssetFileDescriptor函數分析
  通過對Cursor query的分析可知,客戶端進程可像查詢本地數據庫那樣,從目標CP進程獲取信息。不過,這種方法也有其侷限性:
   # 客戶端只有按照結果集的組織方式來獲取數據,而結果集的組織方式是行列式的,即客戶端須移動遊標到指定行,才能獲取自己感興趣的列的值。在實際生活中,不是所有的信息都能組織成行列的格式。
   # query查詢得到的數據的數據量很有限。通過分析可知,用於承載數據的共享內存只有2MB大小。對於較大數據量的數據,通過query方式來獲取顯然不合適。
   考慮到query的侷限性,ContentProvider還支持另外一種更直接的數據傳輸方式,筆者稱之爲“文件流方式”。因爲通過這種方式客戶端將得到一個類似文件描述符的對象,然後在其上創建對應的輸入或輸出流對象。這樣,客戶端就可通過它們和CP進程交互數據了。
   不面來分析這個功能是如何實現的。先向讀者介紹客戶端使用的函數,即CR的openAssetFileDescriptor.
7.6.1 openAssetFileDescriptor之客戶端調用分析,代碼如下:ContentResolver.java::openAssetFileDescriptor
  如上代碼所述,openAssetFileDescriptor是一個通用函數,它支持3種scheme(方案)類型的URI.
   # Scheme_Android_Resource:字符串表達爲android.resource.通過它可以讀取apk包(其實就是一個壓縮文件)中封裝的資源。假設在應用進程的res/raw目錄下存放一個test.ogg文件,最終生成的資源ID由R.raw.test來表達,那麼如果應用進程想要讀取這個資源,創建的URI就是android.resource://com.package.name/R.raw.test。讀者可試試。
   # Scheme_File:字符串表達爲file.通過它可以讀取普通文件。
   # 除上述兩種scheme之外的URI:這種資源背後到底對應的是什麼數據需要由目標CP來解釋。
   接下來分析在第3種scheme類型下調用的openAssetFileDescriptor函數。
   1、openAssetFileDescriptor函數分析,代碼如下:ContentResolver.java::openTypedAssetFileDescriptor
   以上代碼中,阻礙我們思維的依然是新出現的類,先解決它們,再分析代碼中關鍵調用。
   2、FileDescriptot家族介紹
   本節涉及的類家族圖譜如圖7-7所示。(見書388)
   解釋:
    # FileDescriptot類是java的標準類,它是對文件描述符的封裝。進程打開的每一個文件都有一個對應的文件描述符。在Native語言開發中,它用一個int型變量來表示。
   # 文件描述符作爲進程的本地資源,如想超過進程邊界將其傳遞給其他進程,則需要藉助進程間共享技術。在Android平臺上,設計都封裝了一個ParcelFileDescriptor類。此類實現了Parcel接口,自然就支持了序列化和反序列化的功能。從圖7-7可知,一個ParcelFileDescriptor通過mFileDescriptor指向一個文件描述符。
   # AssetFileDescriptor也實現了Parcel接口,其內部通過mFd成員變量指向一個ParcelFileDescriptor對象。從這裏可看出,AssetFileDescriptor是對ParcelFileDescriptor類的進一步封裝和擴展。實際上,根據SDK文檔中對AssetFileDescriptor的描述可知,其作用在於從AssetManager(後續分析資源管理的時候會介紹它)中讀取指定的資源數據。
 提示:簡單向讀者介紹一下與AssetFileDescriptor相關的知識。它用於讀取APK包中指定的資源數據。以前面提到的test.ogg爲例。如果通過AssetFileDescriptor讀取它,那麼其mFd成員則指向一個ParcelFileDescriptor對象。
 下面來看ContentProvicer的openTypedAssetFile函數
7.6.2 ContentProvider的openTypedAssetFile函數分析 代碼如下:ContentProvider.java::openTypedAssetFile
  調用ContentProvider.java::openAssetFile
  下面分析MediaProvider實現的openFile函數
  1、MediaProvider openFile分析 代碼如下:MediaProvider.java::openFile
  在以上代碼中,MediaProvider將首先通過客戶端指定的音樂文件的_id去查詢它的專輯信息。此處給讀者一個示例,如圖7-8所示(見書391)
  2、ContentProvider openFileHelper函數分析 代碼如下:ContentProvider.java::openFileHelper
  至此,服務端已經打開指定文件了。那麼,這個服端的文件描述符是如何傳遞到客戶端的呢?我們單起一節來回答這個問題。
7.6.3 跨進程傳遞文件描述符的探討
  在回答上一節的問題之前,不知讀者是否思考過下面的問題:實現文件描述符跨進程傳遞的目的是什麼?
  以上節讀取音樂專輯的縮略圖爲例,問題的答案就是,讓客戶端能夠讀取專輯的縮略圖文件。爲什麼客戶端不先獲得對應專輯縮略圖的文件存儲路徑,然後直接打開這個文件,卻要如此大費周章呢?原因有二:
   # 出於安全的考慮,MediaProvider不希望客戶端繞過它去直接讀取存儲設備上的文件。另外,客戶端須額外聲明相關的存儲設備讀寫權限,然後才能直接讀取其上面的文件。
   # 雖然本例針對的是一個實際文件,但是從可擴展性角度看,我們希望客戶端使用一個更通用的接口,通過這個接口可讀取實際文件的數據,也可讀取來自網絡的數據,而作爲該接口的使用者無須關心數據到底從何而來。
  1、序列化ParcelFileDescriptor
   先來看ParcelFileDescriptor的序列化函數writeToParcel,代碼如下:ParcelFileDescriptor.java::writeToParcel。
   Parcel的writeFileDescriptro是一個native函數,代碼如下:android_util_Binder.cpp::android_os_Parcel_writeFileDescriptor
   Native Parcel類的writeDupFileDescriptro函數的代碼如下:Parcel.cpp::writeDupFileDescriptor
   由上邊的代碼可知,ParcelFileDescriptor的序列化過程就是將其內部對應文件的文件描述符取出,並存儲到一個由Binder驅動flat_binder_object對象中。該對象最終會發送給Binder驅動。
  2、反序列化ParcelFileDescriptor
   假設客戶端進程收到了來自服務端的回覆,客戶端要做的就是根據服務端的回覆包構造一個新的ParcelFileDescriptor.我們重點關注文件描述符的反序列化,其中調用的函數是Parcel的readFileDescriptor,代碼如下:ParcelFileDescriptor.java::readFileDescriptor
   internalReadFileDescriptor是一個native函數,其實現代碼如下:android_util_Binder_cpp::android_os_Parcel_readFileDescriptor
   來看Parcel的readFileDescriptor函數,代碼如下:Parcel.cpp::readFileDescriptor
   筆者在以上代碼中提到了一個較深刻的問題:此fd是彼fd嗎?這個問題的真實含義是:
   # 服務端打開了一個文件,得到一個fd。注意,fd是一個整型。在服務端上,這個fd確實對應了一個已經打開的文件。
   # 客戶端得到的也是一個整型值,它對應的是一個文件嗎?
   如果說客戶端得到一個整型值,就認爲它得到了一文件,這種說法未免有些草率。在以上代碼中,我們發現客戶端確實根據收到的那個整型值創建了一個FileDescriptor對象。那麼,怎樣纔可知道這個整型值在客戶端中一定代表一個文件呢?
   這個問題的終極解答在Binder驅動的代碼中。來看它的binder_transaction函數。
  3、文件描述符傳遞之Binder驅動的處理.代碼如下:binder.c::binder_transaction
   一切真相大白!原來,Binder驅動代替客戶端打開了對應的文件,所以現在可以肯定,客戶端收到的整型值確確實實代表一個文件。
  4、深入討論
   Android 3.0以後爲CP提供了管道支持,相關函數:ContentProvider.java::openPipeHelper
   由以上代碼可知, 採用管道這種方式的開銷確實比客戶端直接獲取文件描述符的開銷大。
7.6.4  openAssetFileDescriptor函數分析總結
   本節討論了CP提供的第二種數據共享方式,即文件流方式。通過種方式,客戶端可從目標CP中獲取想要的數據。
   本節所涉及的內容較雜,從java層一直到驅動層。
7.7 本章學習指導
   本章圍繞CP進行了較爲深入的討論。相比而言,CP的啓動和創建的難度及重要性要小,而SQLite的使用、進程間的數據共享和傳遞等就複雜得多。建議讀者閱讀完本章後,能在此基礎上,對下列問題做進一步的深入研究:
   # 客戶端進程是如何撤銷它和目標CP進程之間的緊密關係的。
   # 嘗試自行封裝一個輕量級的、面向對象的SQLite類庫。
   # 針對序列化和反序列化相關的知識,最好能編寫一些簡單的例子,用於實踐。
   # java程序員應閱讀《高質量java程序設計》一書,樹立良好的資源管理和回收意識。
7.8 本章小結
   本意圍繞CP進行了較爲深入細緻的討論。首先介紹了目標CP進程是如何啓動並創建CP實例的;接着又介紹了SQLite及SQLiteDatabase家族;最後三節分別圍繞Cursor的query、close函數的實現及文件描述符跨進程傳遞的內容進行了專題討論。

 

第8章 深入理解ContentService和AccountManagerService(見書400)
  本章主要內容:
    # 介紹ContentService
   # 介紹AccountManagerService
  本章所涉及的源代碼文件名及位置:
    # StsemServer.java(frameworks/base/services/java/com/android/server/SystemServer)
    # ContentService.java(frameworks/base/core/java/android/content/ContentService)
    # UsbSettinngs.java(packages/apps/Settings/src/com/android/settings/deviceinfo/UsbSettins.java)
    # Development.java(packages/apps/Settings/src/com/android/settings/deviceinfo/DevelopmentSettings.java)
    # UsbDeviceManager.java(frameworks/base/services/java/com/android/server/usb/UsbDeviceManager.java)
   # AccountAuthcnticatorCache.java(frameworks/base/core/java/android/accounts/AccountAuthcnticatorCache.java)
8.1 概述
  本章第一個要分析的是ContentService.ContentService包含以下兩個主要功能:
  # 它是Android平臺中數據更新通知的執行者。數據更新通知與第7章分析Cursorquery函數實現時提到的ContentObserver有關。這部分內容將在8.2節中介紹。
  # 它是Android平臺中數據同步服務的管理中樞。當用戶通過Android手機中的Contacts應用將聯繫人信息同步到遠端服務器時,就需要和ContentService交互。這部分內容是本章的難點,將在8.4節中介紹。
  本章第二個要分析的是AccountManagerService,它負責管理Android手機中用戶的賬戶,這些賬戶是用戶的online賬戶,例如用戶在Google、Facebook上註冊的賬戶。
  本章將先分析ContentService中數據通知機制的實現,然後分析AccountManagerService,最後再介紹ContentService中的數據同步服務。
8.2 數據更新通知機制分析
  何爲數據更新通知?先來看日常生活中的一個例子。
  筆者所在公司採用BugZilla來管理Bug 。在日常工作中,筆者和同事們的一部分工作就是登錄BugZilla查詢各自名下的Bug並修改它們。如何跟蹤自己的Bug呢?其實,以上描述中已擇提到了一種方法,即登錄BugZilla並查詢。除此之外, BugZilla還支持另一種方法,即爲每個Bug設置一個關係人列表,一旦該Bug的狀態發生變化,BugZilla就會給該Bug關係人列表中的人發送郵件。
  上例中提到的第二種方法就是本節要分析的數據更新通知機制。
  現在回到Android平臺,如果程序需要監控某數據項的變化,可以採用一個類似while循環的語句不斷查詢它以判斷其值是否發生丁變化。顯而易見,這種方式的效率很低。有了通知機制以後,程序只需註冊一個ContentObserver 實例即可。一且該項數據發生變化,系統就會通過ContentObserver的onChange函數來通知我們。與前面所述的輪詢相比,此處的通知方式明顯更高效。
  通過上面的描述可以知道,通知機制的實施包括兩個步驟:第一步, 註冊觀察者;第二步:通知觀察者。在Android平臺中,這兩步都離不開ContentService,下面來認識一下它。
  提示:在設計模式中,通知機制對應的模式是Observer模式,即觀察者模式。
8.2.1 初始ContentService
  SystemServer創建ContentService的代碼非常簡單,如下:SystemServer.java::ServerThread.run
  以上代碼中直接調用了ContentService的main函數。在一般情況下,該函數第二個參數爲false.此main函數的代碼如下:ContentService.java::main
  ContentService構造函數代碼:ContentService.java::ContentService
  讀取同步服務管理對象,代碼:ContentService.java::getSyncManager
  看完以上代碼讀者可能會覺得ContentService比較簡單。其實,ContentService中最難實現的功能是數據同步服務,不過該功能的實現都封裝在SyncManager及相關類中了,所以在分析通知機制時不會和數據同步服務有太多瓜葛。
  下面來分析通知機制實施的第一步,即註冊ContentObserver.該步驟由ContentResovler提供的registerContentObserver函數來實現。
8.2.2 ContentResolver的registerContentObserver分析 代碼如下:ContentResolver.java::registerContentObserver
  registerContentObserver最終將調用ContentService的registerContentObserver函數,其中第三個參數是ContentObserver getContentObserver的返回值。這個返回值是什麼呢?要搞明白這個問題,需請出ContentObserver家族成員。
  1、ContentObserver介紹
  ContentObserver家族成員如圖8-1(見書403)
  圖8-1中的ContentObserver類和第7章中介紹的ContentProvider非常類似,內部都定義了一個Transprot類參與Binder通信。Transprot類從IContentObserver.Stub派生。從Binder通信角度來看,客戶端進程中的Transprot將是Bn端。如此,通過registerContentObserver傳遞到ContentService所在進程的就是Bp端。IContentObserver Bp端對象的真實類型是IContentObserver.Stub.Proxy.
  注意:IContentObserver.java由aidl處理IcontentObserver.aild生成。
  2、registerContentObserver函數分析,代碼如下:ContentService.java::registerContentObserver
  mRootNode是ContentService的成員變量,其類型爲ObserverNode.ObserverNode的組織形式是數據結構中的樹,其葉子節點的類型爲ObserverEntry,它保存了uri和對應的IContentObserver對象。
  至此,客戶端已經爲某些數據項設置了ContentObserver.再來看通知機制實施的第二步,即通知觀察者。
8.2.3 ContentResolver的notifyChange分析
  數據更新的通知由ContentResolver的notifyChange函數觸發。看MediaProvider的update函數的代碼如上:MediaProvider.java::update
  由以上代碼可知,MediaProvider update函數更新完數據後,將通過notifiyChange函數來通知觀察者。notifiyChanage函數的代碼如下:ContentResolver.java::notifiyChange
  由以上代碼可知,ContentService的notifiyChange函數將被調用,其代碼如下:ContentService.java::notifiyChange
8.2.4 數據更新通知機制總結和深入探討
  總結上面所描述的數據更新通知機制的流程,如圖8-2(見書406)
  從前面的代碼介紹和圖8-2所示的流程來看,Android平臺中的數據更新通知機制較爲簡單。不過此處尚有幾問題想和讀者探討。(見書)
  問題1:
  問題2:假設服務端有一項功能,需要客戶端通過某種方式來控制它的開閉(即禁止或使用該功能),考慮一下有幾種方式能實現這個控制機制?
  Android平臺上至少有3種方法可吧實現這個控制機制。
  第一種:服務端實現一個API函數,客戶端直接調用這個函數來控制。
  第二種:客戶端發送指定的廣播,而服務端註冊該廣播的接收者,然後在這個廣播接收者的onReceive函數中處理。
  第三種:服務端輸出一個ContentProvider,併爲這個功能輸出一個uri地址,然後註冊一個ContentObserver.客戶端可通過更新數據的方式來觸發服務端ContentObserver的onChange函數,服務端在該函數中做對應處理即可。
  在Android代碼中,這三種方法都有使用。
8.3 AccountManagerService分析
  本節將分析AccountManagerService.如前所述,AccountManagerService負責管理手機中用戶的online賬戶,主要工作涉及賬戶的添加和刪除、AuthToken(全稱爲authenticationtoken.有了它,客戶端無須每次操作都向服務器發送密碼了)的獲取和更新等。關於AccountManagerService更詳細的功能,可閱讀SDK文檔中AccountManager的說明。
8.3.1 初識AccountManagerService
  AccountManagerService創建時的代碼如下:SystemServer.java::ServerThread.run
  其構造函數的代碼如下:AccountManagerService.java::AccountManagerService
  在AccountManagerService構造函數中創建了一個AccountAuthenticatorCache對象,它是什麼?來看下方。
  1、AccountAuthenticatorCache分析
  AccountAuthenticatorCache是Android平臺中賬戶驗證服務(Account Authenticator Service,AAS)的管理中心。AAS由應用程序通過在AndroidManifest.xml中輸出符合指定要求的Service信息而來。稍後讀者將看到這些要求的具體格式。
  先來看AccountAuthenticatorCache的派生關係,如圖8-3(見書409)
  # AccountAuthenticatorCache從RegisteredServicesCache<AuthenticatorDescription>派生。RegisteredServicesCache是一個模板類,專門用於管理系統中指定Service的信息收集和更新,而具體是哪些Service,則由RegisteredServicesCache構造時的參數指定。AccountAuthedticatorCache對外輸出是由RegisteredServiceCache模板參數指定的類的實例。在圖8-3中就是AuthenticatorDescription.
  # AuthenticatorDescription繼承了Parcelable接口,這代表它可以跨Binder傳遞。該類描述了AAS相關的信息。
  # AccountAuthenticatorCache實現了IAccountAuthenticatorCache接口。這個接口供外部調用都使用以獲取AAS的信息。
  下面看AccountAuthenticatorCache的創建,相關代碼如下:AccountAuthenticatorCache.java::AccountAuthenticatorCache
  AccountAuthenticatorCache調用基類RegisteredServicesCache的構造函數時,傳遞了3個字符串參數,這3個參數用於控制RegisteredServicesCache從PackageManagerService獲取哪些Service的信息。
  (1)RegisteredServicesCache分析 RegisteredServicesCache.java::RegisteredServicesCache
  由以上代碼可知:
  # 成員變量mPersistentServicesFile指向/data/system/registercd_service目錄下的一個文件,該文件保存了以前獲取的對應Service的信息。就AccountAuthenticator而言,mPersistentServicesFiel指向該目錄的android.accounts.AccountAuthenticator.xml文件。
  # 由於RegisteredServicesCache管理的是系統中指定Service的信息,當系統中有Package安裝、卸載或更新時,RegisteredServicesCache也需要對應更新自己的信息,因爲有些Service可能會隨着APK被刪除而不復存在。
  generateServiceMap函數將獲取指定的Service信息,其代碼如下:RegisteredServicesCache.java::generateServicesMap
  接下來解析Service的parseServiceInfo函數。
  (2)parseServiceInfo函數分析:代碼如下:RegisteredServicesCache.java::parseServiceInfo
  parseServiceInfo將解析Service中的MetaData信息,然後調用子類實現的parseServiceAttributes函數,以獲取特定類型Service的信息。
  下面通過實例向讀者展示最終的解析結果。
  (3)AccountAuthenticatorCache總結
  在Email應用的AndroidManifest.xml中定義一個AAS,如圖8-4所示(見書413)
  這個XML文件中的內容是有嚴格要求的,其中:
  # accountType標籤用於指定賬戶類型(賬戶類型和具體應用有關,Android並未規定賬戶的類型).
  # icon、smallIcon、label和accountPreferences等用於界面顯示。例如,當需要用戶輸入賬戶信息時,系統會彈出一個Activity,上述幾個標籤就用於界面顯示,詳細說明可閱讀SDK文檔AbstractAccountAuthenticator的說明。android.accounts.AccountAuthenticator.xml的內部
  下面來看AccountManagerService的構造函數。
  2、AccountManagerService構造函數分析 代碼如下:AccountManagerService.java::AccountManagerService
8.3.2 AccountManager.addAccount分析
  這一節將分析AccountManagerService中的一個重要的函數,即addAccount,其功能是爲某項目賬戶添加一個用戶。下面以前面提及到的Email爲例來認識AAS的處理流程。
  AccountManagerService是一個運行在SystemServer中的服務,客戶端進程必須藉助AccountManager提供的API來使用AccountManagerService服務,所以本例需要從AccountManager的addAccount函數講起。
  1、AccountManager的addAccount發起請求 代碼如下:AccountManager.java::addAcount
  在以上代碼中,AccountManager的addAcount函數將返回一個匿名類對象,該匿名類繼承自AmsTask類,那麼,AmsTask又是什麼呢?
  (1)AmsTask介紹
  先來看AmsTask的繼承關係,如圖8-7所示(見書417)
  (2)AmsTask匿名類處理分析
  AccountManager的addCount最終返回給客戶端的是一個AmsTask的子類,首先來了解它的構造函數,其代碼如下:AccountManager.java::AmsTask
  下一步調用的是這個匿名類的Start函數,代碼如下:AccountManager.java::AmsTask.start
  調用doWork函數即下面這個函數,代碼如下:AccountManager.java::addAcount返回的匿名類
  AccountManager的addAccount函數的實現比較新奇,它內部使用了java的concurrent(併發)類。
  下面轉到AccountManagerService中去分析addAccount的實現。
  2、AccountManagerService.addAccount轉發請求,代碼如下:AccountManagerService.java::addAccount
  由以上代碼可知,AccountManagerService的addAccount函數最後也創建了一個匿名類對象,該匿名類派生自Session。addAccount最後還調用了這個對象的bind函數。其中重要的內部就是Session。那麼,Session又是什麼呢?
  (1)Session介紹
  Session家族成員如圖8-8所示(見書419)
  (2)Session匿名類處理分析
  首先調用Session的構造函數,代碼爲:AccountManagerService.java::Session
  獲得匿名類對象後,addAccount將調用其bind函數,該函數由Session實現,代碼如下:AccountManagerService.java::Session::bind
  由以上代碼可知,Session的bind函數將啓動指定類型的Service,這是通過bindService函數完成的。如果服務啓動成功,Session的onServiceConnected函數將被調用,這部分代碼如下:AccountManagerService.java::Session.onServiceConnected
  匿名類實現的run函數非常簡單,代碼如下:AccountManagerService.java::addAccount返回的匿名類
  由以上代碼可知,AccountManagerService在addAccount最終將調用AAS實現的addAccount函數。
  不面來看本例中滿足“com.android.email”類型的服務是如何處理addAccount的請求的。該服務就是Email應用中的EasAuthenticatorService,下面來分析它。
  3、EasAuthenticatorService處理請求
  EasAuthenticatorService的創建是AccountManagerService調用了bindService完成的,該函數會觸發EasAuthenticatorService的onBind函數的調用,這部分代碼如下:EasAuthenticatorService.java::onBind
  下面來分析EasAuthenticator。
  (1)EasAuthenticator介紹
  EasAuthenticator是EasAuthenticatorService定義的內部類,其家族關係如圖8-9所示(見書422)
  (2)EasAuthenticator的addAccount函數
  根據上文的描述可知,Email進程中首先被觸發的是IAccountAuthenticator Bn端的addAccount函數,其代碼如下:AbstractAccountAuthenticator.java::Transprot::addAccount
  本例中AbstractAccountAuthenticator子類(即EasAuthenticator)實現的addAccount函數,代碼如下:EasAuthenticatorService.java::EasAuthenticator.addAccount
  4、返回值的處理流程
  5、AccountManager的addAccount分析總結
  AccountManger的addAccount流程分析起來會給人一種行雲流水般的感覺。該流程涉及3個模塊,分別是客戶端、AccountManagerService和AAS。
  總結起來addAccount相關流程如圖8-10所示(見書427)
8.3.3 AccountManagerService的分析總結
  本節對AccountManagerService進行分析,從技術上說,本節涉及和java concurrent類相關的知識。另外,對併發編程來說,架構設計是最重要的,因此讀者務必閱讀腳註中提到的參考書籍《Pattern.Oriented.Software.Architecture.Volume2》。
8.4 數據同步管理SyncManager分析
  本節將分析ContentService中負責數據同步管理的SyncManager。SyncManager和AccountManagerService之間的關係比較緊密。同時,由於數據同步涉及手機中重要數據(例如聯繫人信息、Email、日曆等)的傳輸,因此它的控制邏輯非常嚴謹,知識點也比較多,難度相對比AccountManagerService大。
  先來認識數據同步管理的核心SyncManager.
8.4.1 初識SyncManager
  SyncManager的構造函數的代碼較長,可分段來看。下面先來介紹第一段的代碼。
  1、SyncManager介紹 代碼如下:SyncManager.java::SyncManager
  在以上代碼中,首先見到的是SyncManager的幾位重要成員,它們之間的關係如圖8-11所示(見書429)
  下面來認識一下SyncManager家族中的幾位主要成員,首先是SyncStorageEngine.
  2、SyncStorageEngine介紹
  SyncStorageEngine負責整個同步系統中信息管理方面的工作。先看其init函數代碼:SyncStorageEngine.java::init
  3、SyncAdaptersCache介紹
  再看SyncAdaptersCache,其構造函數代碼如下:SyncAdaptersCache.java::SyncAdaptersCache
  SyncAdaptersCache的基類是RegisteredServicesCache。8.3.1節已經分析過RegisteredServicesCache了,此處不再贅述。
  再看SyncManager家族中最後一位成員SyncQueue.
  4、SyncQueue介紹
  SyncQueue用於管理同步操作對象SyncOperation。SyncQueue的構造函數代碼爲:SyncQueue.java::SyncQueue
  SyncQueue比較簡單,其中一個比較難理解的概念是backoff,後文對此作解釋。
  至此,SyncManager及相關家族成員已介紹完畢。下面將通過實例分析同步服務的工作流程。在本例中,將同步Email數據,目標同步服務爲EmailSyncAdapterService。
8.4.2 ContentResolver的requestSync分析
  ContentResolver提供了一個requestSync函數,用於發起一次數據同步請求。在本例中,該函數的調用方法如下:
  1、客戶端發起請求
  ContentResolver的requestSync的代碼如下:ContentResolver.java::requestSync
  與添加賬戶(addAccount)相比,客戶端發起一次同步請求所要做的工作就太簡單了。下面轉戰ContentService去看它的requestSync函數
  2、ContentService的requestSync函數分析,代碼如下:ContentService.java::requestSync
  ContentService將工作轉交給SyncManager來完成,其調用的函數是scheduleSync。
  (1)SyncManager的scheduleSync函數分析
  先行介紹scheduleSync函數非常重要,其調用代碼如下:SyncManager.java::scheduleSync
  scheduleSync函數較複雜,難點在於其策略控制。建議讀者反覆閱讀這部分內容。
  scheduleSync最後將構造一個SyncOperation對象,並調用scheduleSyncOperation處理它。scheduleSyncOperation內部將這個SyncOperatio對象保存到時mSyncQueue中,然後發送Message_check_alarms消息讓mSyncHandler處理。由於scheduleSyncOperation函數比較簡單,因此下面將直接去mSyncHandler的handMessage函數中分析Message_check_alarms的處理流程。
  (2)處理Message_check_alarms消息
  SyncHandler的handleMessage代碼如下:SyncManager.java::SyncHandler:handleMessage
  如上代碼所述,Message_check_alarms消息的處理就是調用maybeStartNextSyncLocked函數。這個函數比較煩瑣。它主要做了以下幾項工作。
  ActiveSyncContext是SyncManager和同步服務交互的關鍵類,其家族圖譜如圖8-16所示(見書444)
  (3)ActiviSyncContext派發請求,代碼如下:SyncManager.java::ActiveSyncContext.bindToSyncAdapter
  3、EmailSyncAdapterService處理請求
  在本例中,目標同步服務位於EmailSyncAdapterService中,先看它通過onBind函數返回給ActiveSyncContext的是什麼。
  (1)onBind分析,代碼如下:EmailSyncAdapterService.java::onBind
  (2)startSync分析
  在SyncService中,首先被調用的函數是ISyncAdapterImpl的startSync函數,其代碼爲:AbstractThreadSyncAdapter.java::ISyncAdapterImpl.startSync
  執行完onPerformSync函數後,ISyncAdapterImpl.run返回前會調用mSyncContextonFinished函數,通知位於SyncManager中的ActiveSyncContext同步操作的結果。
  4、ContetnResolver reqeustSync分析總結
  總結requestSync的工作流程,如圖8-18(見書449)
  由圖8-18可知,requestSync涉及的對象及調用流程比較煩瑣。但從技術上看,則沒有什麼需要特別注意的地方。
8.4.3 數據同步管理SyncManager分析總結
  本節對ContentService中第二個主要功能即數據同步管理SyncManager進行了研究,這部分內容主要包括3個方面:
  # SyncManager及相關成員的作用。
  # 通過requestSync展示了SyncManager各個模塊的作用及交互過程。
  # 穿插於上述兩方面之中的是數據同步的一些處理策略和規則。筆者未對這部分內容展開更細緻的討論。感興趣的讀者可自行學習。
8.5 本章學習指導
  本章對ContentService和AccountManagerService進行了研究,在學習過程中,需要注意以下幾個方面。
  # ContentService包括兩個不同的部分,一個是數據更新通知機制,另一個是同步服務管理。在這兩部分中,ContentService承擔了數據更新通知機制的工作,同步服務管理的工作則委託給SyncManager來完成。基於這種分工,本章先分析了ContentService的數據更新通知機制。這部分內容非常簡單,讀者應能輕鬆掌握。不過,作爲深入學習的入口,筆者有意留下幾個問題,旨在讓讀者仔細思考。
  # 由於同步服務管理和AccountManagerService關係密切,因此本章先分析了AccountManagerService。在這部分代碼中,讀者需要了解RegisteredServicescCache的作用。另外,感興趣的讀者也可以學習如何編寫AuthedticatorService,相關文檔在SDK關於AbstractAccountAuthenticator類的說明中。
  # SyncManager是本章理解難度最大的知識點。筆者覺得,其難度主要體現在同步策略上。另外,缺乏相關文檔資料的參與也是導致學習SyncManager難度較大的原因之一。讀者在把本部分內容搞清楚的基礎上,可自行研究本章沒有提及的內容。另外,讀者如想學習如何編寫同步服務,可參與SDK文檔中關於AbstractThreadSyncAdapter的說明。
  除上述內容處,AccountManager的addAcount函數實現中所使用的java concurrent類,也是讀者必須學習並掌握的基礎知識。
8.6 本章小結
  本章對ContentService和AccountManagerService進行了較爲深入的探討,其中:
  # 8.2節和8.4節分別分析了ContentService中數據更新通知機制的實現及同步服務管理方面的工作。
  # 8.3節分析了AccountManagerService及addAccount的實現。
  本章留出以下問題供讀者自行研究,這些問題包括:
  # 8.2節最後提到的開放性問題和Cursor query中ContentObserver的使用分析。
  # 8.4節涉及SyncManager的同步請求處理的策略和maybeStartNextSyncLocked函數分析等。另外,讀者如果有興趣,不妨研究下SyncStorageEngine中同步信息的統計等內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章