簡介
SettingsProvider由Android系統框架提供,包含全局、系統級別的用戶偏好設置,系統中的setting應用和它存在十分緊密的關係。SettingsProvider作爲一個系統apk,隨框架一起編譯,在目錄樹種的位置:"frameworks\base\packages\SettingsProvider"。爲了方便使用,系統對SettingsProvider做了封裝處理,封裝的代碼“frameworks\base\core\java\android\provider\Settings.java”,所以用戶調用Settings中的方法就能很輕易的訪問SettinsProvider。SettinsProvider和其他系統Provider一樣,在SystemServer啓動Services時,調用ActivityManagerService#installSystemProviders創建啓動。
關鍵設計和結構
1. 數據分類和存儲
SettingsProvider對數據進行了分類:Global、System、Secure,其中:
- Global:全局的偏好設置,對系統中所有用戶公開,第三方App沒有寫權限;
- System:用戶偏好系統設置;
- Secure:安全相關的用戶偏好設置,第三方App沒有寫權限。
Android6.0版本之後SettingsProvider管理的用戶偏好設置數據從原來的settings.db數據庫文件中轉移到下面的3個xml文件中:
- data/system/users/0/settings_global.xml
- data/system/users/userid/settings_system.xml
- data/system/users/userid/settings_secure.xml
備註:
1、在Android多用戶環境下,Global分類數據是面向所有用戶的,所以settings_global.xml只在0用戶下存在;
2、SettingsProvider還管理着一些數據存儲在文件“data/system/users/userid/settings_ssaid.xml”中,本文中暫時不對相關的數據和代碼做分析。
2. 關鍵設計
1. 兼容性設計
爲了兼容之前版本的設計(網上很多大牛都這樣分析),Android 9.0代碼中依然保留了數據庫相關的邏輯設計。SettingsProvider在啓動時,如果檢測到settings_global.xml不存在,會創建settings.db數據庫,並將SettingsProvider管理的偏好設置的默認設置寫入到settings.db中,然後將settings.db中的數據保存到相應的xml文件下,最後刪除settings.db數據庫。數據庫操作邏封裝在DatabaseHelper類中。
備註:
1、個人數據庫在xml文件生成的過程中最大的作用就是作爲一個數據中轉載體,這樣的兼容性設計別不是十分必要;
2、在系統調試過程中如果懷疑數據庫中轉過程出了問題,可以講SettingsProvider.DROP_DATABASE_ON_MIGRATION常量設置爲false,這樣settings.db文件不會在使用完畢之後從磁盤上刪除,而是會備份爲settings-backup.db,可以用作對比分析。
2. 關鍵數據組織
SettingsProvider關鍵數據組織參見上圖,在SettingsProvider中持有一個內部類SettingsRegistry的引用m_SettingsRegistry, m_SettingsRegistry通過一個稀疏數組間接持有了系統中所有用戶偏好設置數據。稀疏數組m_SettingsStates的value類型是SettingsState類,Key是由偏好類型[Global|System|Secure]和Userid通過計算得出,計算規則在後面給出,SettingsState類的一個實例和上文介紹的某個xml文件關聯(內存中的xml文件數據表示)。在SettingsState類中通過ArrayMap持有n個內部類setting的實例,n的值取決於xml文件中item的數量,通過用戶偏好設置name可以從mSettings中取出某項具體的用戶設置數據。setting類關聯到某項具體的用戶設置數據。
在閱讀源碼的過程中要對這個數據組織模型有清晰的認識,另外Key值的清楚認識可以幫助我們更好的理解源碼中蘊藏的邏輯,因爲源碼中有很多關鍵的地方都有它的存在。下面對Key的構成規則做一個分析,源碼位於SettingsState.java類中:
|
type=[0|1|2]、SETTINGS_TYPE_SHIFT=28、SETTINGS_TYPE_MASK=0xF0000000,因爲在Android多用戶定義中,userId有效位爲低16位,所以上面代碼給出的計算是可逆的,能夠從Key逆運算得到數據類型和用戶id。這樣做的目的是通過對type和userId的組合得到"data/system/users/userid/*.xml"文件的唯一標識。
3. 緩存設計
因爲SettingsProvider被系統中很多模塊訪問,爲了方便使用系統提供了Settings類對SettingsProvider進行封裝,同時提供了2級緩存機制以提高SettingsProvider的使用性能,SettingsProvider的2級緩存結構如下圖所示。
1. 第一級緩存,SettingsProvider通過內部類SettingsRegistry間接維護了所有用戶的所有偏好設置數據,這些數據以xml文件爲單位採用“用時加載”的策略保持於xml文件數據同步;
2. 第二級緩存,Settings對SettingsProvider數據提供了封裝,Settings根據SettingsProvider的數據分類實現了3個靜態內部類訪問SetingsProvider中的提供的數據。在三個靜態內部類中通過NameValueCache維護了當前用戶設置數據的緩存,Global類型數據除外,它是所有用戶共享的。NameValueCache以設置數據條目(鍵值對)爲單位與SettingsProvider.SettingsRegistry間接維護的緩存中的設置數據條目<String,Setting>保持“用時同步”。
3. 兩級緩存之間通過"Generation"-int數值維護數據版本,當數據版本發生改變時,NameValueCache數據清空,當某鍵值對數據發生第一次訪問之後,直到SettingsProvider緩存的版本和Settings緩存維護的版本不一致之前,NameValueCache中的數據可用。
3. 關鍵源碼解讀
1. 相關源碼
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
frameworks/base/packages/SettingsProvider/AndroidManifest.xml
frameworks/base/core/java/android/provider/Settings.java
frameworks/base/services/java/com/android/server/SystemServer.java
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
frameworks/base/core/java/android/app/ActivityThread.java
2. SettingsProvider AndroidManifest.xml文件
AndroidManifest.xml
<manifest ...
android:sharedUserId="android.uid.system">
<application android:allowClearUserData="false"
android:label="@string/app_label"
android:process="system"
android:backupAgent="SettingsBackupAgent"
...
android:directBootAware="true">
<provider android:name="SettingsProvider"
android:authorities="settings"
android:multiprocess="false"
android:exported="true"
android:singleUser="true"
android:initOrder="100"
android:visibleToInstantApps="true" />
</application>
</manifest>
分析Manifest文件,從shareUserId知道SettingsProvider運行在系統進程中,從backupAgent知道SettingsProvider使用系統的備份框架對關鍵數據進行了備份操作,從authorities知道SettingsProvider Uri的Authority是settings。
3. SettingsProvider啓動過程
SettingsProvider是一個系統Provider,啓動流程見上圖,和其他系統Provider的啓動流程一樣,在SystemServer啓動系統服務的過程中安裝進系統,源碼:
摺疊源碼
|
上面的代碼顯示系統Provider在系統啓動的時候在startOtherServices()中調用ActivityManagerService的installSystemProviders()完成安裝創建,SettingsProvider也包括在其中,ActivityManagerService#installSystemProviders()源碼:
|
上面的代碼在註釋1處獲取所有的系統Provider,這個過程最終會調用到包管理模塊的queryContentProviders()函數,關於獲取系統Provider的過程細節我們這裏不做分析,感興趣的從這裏向下繼續跟源碼;在註釋2處,調用ActivityThread的installSystemProviders方法完成系統Provider的安裝啓動,包括SettingsProvider;在註釋3處理SettingsProvider安裝之前某些依賴救援程序(Android 8.0之後引入)相關邏輯,這裏不做詳細分析。ActivityThread#installSystemProviders涉及到比較複雜的處理邏輯,和我們分析SettingsProvider的啓動流程關係不大,本文這裏不做分析。最終,通過調用ActivityThread#installSystemProviders會調用到SettingsProvider的onCreate函數,SettingsProvider#onCreate的調用流程如下:
SettingsProvider的關鍵啓時序見上圖,onCreate源碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
上面代碼註釋1處創建一個HandlerThread,用來執行一些異步操作;註釋3處會註冊一些關心的系統廣播,比如用戶變化、App卸載等,本文不具體分析所有廣播的回調邏輯;註釋4處會向系統添加SettingsService服務,關於SettingsService服務提供的功能再以後的文章中單獨分析。註釋2處會創建SettingsRegistry對象,是上面是時序圖核心時序邏輯的開始,下面就具體分析SettingsRegistry類創建過程中都做了哪些事情,SettingsRegistry構造函數源碼:
1 2 3 4 5 6 7 8 9 10 |
|
上面代碼註釋1處創建了一個GenerationRegistry對象,GenerationRegistry對象的核心作用類似於對xml文件的更改做版本管理;註釋2處創建BackupManager對象,和系統備份有關,有興趣的話可以看一看android的備份機制;代碼3是本文要分析的核心邏輯,這個函數十分重要,源碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
上面代碼註釋1處,很重要,判斷"/data/system/users/0/settings_global.xml"文件是否存在,如果不存在migrateAllLegacySettingsIfNeed()函數直接返回,也就是說在這種情況下不需要遷移數據(字面意思),而正常情況下settings_global.xml文件只有在系統首次啓動的時候不存在,也就是說migrateAllLegacySettingsIfNeeded()數據遷移只會發生在系統首次啓動。爲了便於理解,我們先提前總結一下migrateAllLegacySettingsIfNeed()函數數據遷移的核心動作:遍歷系統中的所有用戶(一般情況只有0用戶),循環爲每個用戶創建一個臨時數據庫,並將系統各個模塊的默認設置寫入數據庫,接着調用migrateLegacySettingsForUserLocked()將數據庫中的內容寫入到“/data/system/users/userid/*.xml”文件中,也就是xml創建和初始化的過程。註釋2處就是獲取系統中所有用戶,並通過for循環遍歷對每個用戶執行數據遷移的過程。註釋3處的邏輯很重要,它通過DatabaseHelper類創建了數據庫和表,並使用默認設置對數據庫表數據初始化。註釋4處的代碼是爲每個用戶初始化xml表的核心代碼。下面首先分析數據庫創建初始化的核心過程,接着再分析數據從數據庫表遷移到xml文件的邏輯。DatabaseHelperd的onCreate源碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
上面代碼註釋1、2、3處爲用戶創建3中類型數據的數據表,註釋3處多了一個判斷,因爲settings_global.xml文件是所有用戶共享的,而只存儲在0用戶下;註釋4處調用loadVolumeLevels()和loadSettings()以默認設置數據填充數據庫表,具體填充了哪些數據,本文不做分析,填充邏輯僅僅是向相關的表中插入數據項。下面接着分析最終要得一個函數調用,migrateLegacySettingsForUserLocked(),源碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
上面的代碼註釋1處,爲用戶處理System類型數據,1.首先生成與xml文件唯一對應的key,key的生成規則在2.2章節有講;2. 調用ensureSettingsStateLocked()方法確保 :SparseArray<SettingsState>容器中有xml文件對應的:SettingsState對象;3.調用 migrateLegacySettingsLocked方法將臨時數據庫中設置數據更新到:SettingsState對象;4.調用:SettingsState.persistSyncLocked()方法把數據寫入到xml文件中。
4. 數據獲取流程
設置數據獲取大體時序邏輯見上圖,通過Settings類封裝之後SettingsProvider的數據獲取變得相當簡單(數據更新同樣),在代碼中只需要向下面這樣調用Settings.System類的getString方法就能輕鬆獲得某個屬性數據:
|
Settings.System.getString()方法調用了getStringForUser()方法,方法源碼:
1 2 3 4 5 |
|
Settings.System.getStringForUser()方法調用了NameValueCache緩存類的同名方法,方法源碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
上面的代碼註釋1處檢查NameValueCache緩存是否命中,檢查條件是用戶id和Generation,命中直接返回緩存中的值;註釋2處的代碼通過binder調用SettingsProvider的call方法獲取數據;註釋3處的代碼主要是檢查更新Generation,保持緩存值是最新的。下面接着分析SettingsProvider.call()方法,方法源碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
SettingsProvider提供call()方法來向外提供數據,這裏優先並沒使用ContentProvider的CURD方法,call()方法中通過通過傳遞過來的method確定客戶端請求的操作,Settings.System.getString方法最終傳遞過來的method就是Settings.CALL_GET_SYSTEM。源碼中註釋1處調用getSystemSetting從SystemsRegistry中維護的緩存中提取數據,後面的分析中會看到如果緩存中數據尚未加載會從xml,會在這時候加載,這是爲什麼上面我們使用“用時加載”這個名詞的原因;註釋2處代碼打包返回結果,另外還會維護Generation,這個過程本文不會詳細分析。接下來我們分析一下getSystemSetting()方法,方法源碼:
1 2 3 4 5 6 7 8 9 10 |
|
源碼註釋1處代碼完成多用戶和權限相關的處理;註釋2處調用SettingsRegistry的getSettingLocked()方法獲取數據,SettingsRegistry.getSettingsLocked()方法源碼:
1 2 3 4 5 6 7 8 9 10 |
|
源碼註釋1處的代碼很重要,方法SettingsRegistry.peekSettingsStateLocked()根據傳入的key值會找到xml文件對應的SettingsState對象,這個過程中如果SettingsState對象不存在,會創建並加載xml數據;註釋2處的代碼就是從SettingsState對象中維護的設置數據中找到name對應的value並返回。下面着重分析一下peekSettingsStateLocked(),源碼:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
註釋1處的代碼先從緩存SparseArray<SettingsState>對象mSettingsStates中查找到key對應的:SettingsState,如果在mSettingsStates中找到了key值關聯的對象,直接將:SettingsState返回,如果key值關聯的對象並未在緩存中,也即時xml文件並未加載,這時接着執行下面的邏輯;註釋2處執行xml爲加載的情況下的邏輯,SettingsRegistry.ensureSettingsForUserLocked()源碼:
1 2 3 4 5 6 7 8 9 10 11 |
|
註釋1處調用SettingsRegistry.migrateLegacySettingsForUserIfNeededLocked()遷移數據,遷移數據的邏輯和前文介紹系統首次啓動時遷移數據的邏輯一樣,不用的是內部只會調用migrateLegacySettingsForUserLocked()一次,爲當前指定的用戶執行數據遷移流程,這是應對新創建的用戶xml文件不存在的情形;註釋2處調用SettingsRegistry.ensureSettingsStateLocked加載xml文件中的數據到緩存,ensureSettingsStateLocked()源碼:
1 2 3 4 5 6 7 8 9 |
|
註釋1處,爲給定的key值創建一個新的SettingsState對象,並將對象加入到SettingsRegistry持有的SparseArray<SettingsState>中,xml數據加載的過程在SettingsState的構造函數中完成,源碼:
1 2 3 4 5 6 7 8 |
|
註釋1處,調用SettingsState.readStateSyncLocked()方法,處理xml文件,源碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
註釋1處,打開xml文件操作;註釋2處調用SettingsState.parseStateLocked()方法正在完成xml文件解析,並將數據存入到SettingsState緩存中,源碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
parseStateLocked在While循環中爲每個TAG_SETTINGS=settings標籤調用註釋1處的SettingsState.parseSettingsLocked()完成xml文件解析,源碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
代碼註釋1處從xml文件中提取版本信息,和上文說的Generation相關;代碼註釋2處提取每條設置的屬性值;註釋3處以提取到的完整的設置條目數據構建Setting對象加入到SettingsState維護的Map中。
到這裏,以System類型介紹數據獲取的整個流程就基本介紹完了,總結一下,1. 首先客戶端程序調用Settings.System.getString()或者Settings.System.getStringForUser()方法發起數據獲取流程;2. 優先區NameValueCahce緩存中查找客戶端請求的數據是否存在,存在就從緩存總返回結果,不存在或者Generation更新了,需要重新維護緩存,從下級緩存中提取數據;3. 從SettingsRegistry維護的緩存中區查找數據,如果SettingsRegistry緩存中依然找不到請求的數據(這裏是以xml文件爲單位,2中以xml文件的setting條目爲單位),加載xml文件,加載過程中如果xml文件不存在還需要先創建和初始化xml文件,最終將客戶端請求的數據返回。
備註:本章以System類型的數據分析獲取流程,對於Global和Sercure類型的數據獲取流程基本一樣,只是在某些處理細節上存在差異。
5. 數據設置流程
數據設置流程和數據上文講的數據設置流程調用過程基本類似,客戶端調用時使用調用Settings.System.putString()或Settings.System.GetString()方法設置System類型的數據,本文就不重複分析這部分的源碼了。
總結
本文只是從自己的角度對SettingsProvider的底層原理和實現邏輯做了一個簡單的分析,文中很多觀點出於自己的理解,有錯誤的地方還歡迎指正探討。文中還有很多邏輯並未覆蓋到,比如對系統廣播的處理、備份的實現邏輯等等,從經過幾個月接觸Android的認知來看,Android的各個系統框架都很複雜、代碼量較大、而且系統模塊之間相互耦合較深,想面面俱到的對代碼做出分析,往往會把你帶入泥潭,建議像我一樣的初學者對各個模塊的分析採取把握設計主幹、工作中具體問題具體分析,再去扣細節源碼,避免迷失深林之中。