SettingsProvider源碼分析(Android 9.0)

 

簡介

        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類中:

public static int makeKey(int type, int userId) {

        return (type << SETTINGS_TYPE_SHIFT) | userId;

    }

public static int getTypeFromKey(int key) {

        return key >>> SETTINGS_TYPE_SHIFT;

    }

public static int getUserIdFromKey(int key) {

        return key & ~SETTINGS_TYPE_MASK;

    }

       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啓動系統服務的過程中安裝進系統,源碼:

 摺疊源碼

private void startOtherServices() {

       ... ...

       try {

           ... ...

           traceBeginAndSlog("InstallSystemProviders");

           mActivityManagerService.installSystemProviders(); //安裝系統Providers

           // Now that SettingsProvider is ready, reactivate SQLiteCompatibilityWalFlags

           SQLiteCompatibilityWalFlags.reset();

           traceEnd();

          ... ...

       catch (RuntimeException e) {

           Slog.e("System""******************************************");

           Slog.e("System""************ Failure starting core service", e);

       }

       ... ...

   }

        上面的代碼顯示系統Provider在系統啓動的時候在startOtherServices()中調用ActivityManagerService的installSystemProviders()完成安裝創建,SettingsProvider也包括在其中,ActivityManagerService#installSystemProviders()源碼:

public final void installSystemProviders() {

        ... ...

        //1

        List<ProviderInfo> providers;

        synchronized (this) {

            ProcessRecord app = mProcessNames.get("system", SYSTEM_UID);

            providers = generateApplicationProvidersLocked(app);

            if (providers != null) {

                for (int i=providers.size()-1; i>=0; i--) {

                    ProviderInfo pi = (ProviderInfo)providers.get(i);

                    if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {

                        Slog.w(TAG, "Not installing system proc provider " + pi.name

                                ": not system .apk");

                        providers.remove(i);

                    }

                }

            }

        }

        //2

        if (providers != null) {

            mSystemThread.installSystemProviders(providers);

        }

        ... ...

        synchronized (this) {

            mSystemProvidersInstalled = true;

        }

        ... ...

        // Now that the settings provider is published we can consider sending

        // in a rescue party.

        // 3

        RescueParty.onSettingsProviderPublished(mContext);

        ... ...

    }

        上面的代碼在註釋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

public boolean onCreate() {

        ... ...

        synchronized (mLock) {

            ... ...

            //1

            mHandlerThread = new HandlerThread(LOG_TAG,

                    Process.THREAD_PRIORITY_BACKGROUND);

            mHandlerThread.start();

            mHandler = new Handler(mHandlerThread.getLooper());

            //2

            mSettingsRegistry = new SettingsRegistry();

        }

        //3

        mHandler.post(() -> {

            registerBroadcastReceivers();

            startWatchingUserRestrictionChanges();

        });

        //4

        ServiceManager.addService("settings"new SettingsService(this));

        return true;

    }

        上面代碼註釋1處創建一個HandlerThread,用來執行一些異步操作;註釋3處會註冊一些關心的系統廣播,比如用戶變化、App卸載等,本文不具體分析所有廣播的回調邏輯;註釋4處會向系統添加SettingsService服務,關於SettingsService服務提供的功能再以後的文章中單獨分析。註釋2處會創建SettingsRegistry對象,是上面是時序圖核心時序邏輯的開始,下面就具體分析SettingsRegistry類創建過程中都做了哪些事情,SettingsRegistry構造函數源碼:

1

2

3

4

5

6

7

8

9

10

public SettingsRegistry() {

        mHandler = new MyHandler(getContext().getMainLooper());

        //1

        mGenerationRegistry = new GenerationRegistry(mLock);

        //2 

        mBackupManager = new BackupManager(getContext());

        //3

        migrateAllLegacySettingsIfNeeded();

        syncSsaidTableOnStart();

}

        上面代碼註釋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

private void migrateAllLegacySettingsIfNeeded() {

        synchronized (mLock) {

            //1

            final int key = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);

            File globalFile = getSettingsFile(key);

            if (SettingsState.stateFileExists(globalFile)) {

                return;

            }

            ... ...

            try {

                //2

                List<UserInfo> users = mUserManager.getUsers(true);

                final int userCount = users.size();

                for (int i = 0; i < userCount; i++) {

                    final int userId = users.get(i).id;

                    //3

                    DatabaseHelper dbHelper = new DatabaseHelper(getContext(), userId);

                    SQLiteDatabase database = dbHelper.getWritableDatabase();

                    //4

                    migrateLegacySettingsForUserLocked(dbHelper, database, userId);

                    ... ...

                     

            finally {

                ... ...

            }

        }

    }

        上面代碼註釋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

public void onCreate(SQLiteDatabase db) {

        //1

        db.execSQL("CREATE TABLE system (" +

                    "_id INTEGER PRIMARY KEY AUTOINCREMENT," +

                    "name TEXT UNIQUE ON CONFLICT REPLACE," +

                    "value TEXT" +

                    ");");

        db.execSQL("CREATE INDEX systemIndex1 ON system (name);");

        //2

        createSecureTable(db);

        //3

        // Only create the global table for the singleton 'owner/system' user

        if (mUserHandle == UserHandle.USER_SYSTEM) {

            createGlobalTable(db);

        }

        ... ...

        //4

        loadVolumeLevels(db);

        loadSettings(db);

    }

        上面代碼註釋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

private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper,

                SQLiteDatabase database, int userId) {

        //1

        final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);

        ensureSettingsStateLocked(systemKey);

        SettingsState systemSettings = mSettingsStates.get(systemKey);

        migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);

        systemSettings.persistSyncLocked();

        //2

        final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);

        ensureSettingsStateLocked(secureKey);

        SettingsState secureSettings = mSettingsStates.get(secureKey);

        migrateLegacySettingsLocked(secureSettings, database, TABLE_SECURE);

        ensureSecureSettingAndroidIdSetLocked(secureSettings);

        secureSettings.persistSyncLocked();

        //3

        if (userId == UserHandle.USER_SYSTEM) {

            final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, userId);

            ensureSettingsStateLocked(globalKey);

            SettingsState globalSettings = mSettingsStates.get(globalKey);

            migrateLegacySettingsLocked(globalSettings, database, TABLE_GLOBAL);

            if (mSettingsCreationBuildId != null) {

                globalSettings.insertSettingLocked(Settings.Global.DATABASE_CREATION_BUILDID,

                mSettingsCreationBuildId, nulltrue,

                SettingsState.SYSTEM_PACKAGE_NAME);

            }

            globalSettings.persistSyncLocked();

        }

        //4

        if (DROP_DATABASE_ON_MIGRATION) {

            dbHelper.dropDatabase();

        else {

            dbHelper.backupDatabase();

        }

    }

        上面的代碼註釋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方法就能輕鬆獲得某個屬性數據:

String SystemValue = Settings.System.getString(getContentResolver(), Settings.System.ACTION_SETTINGS);

        Settings.System.getString()方法調用了getStringForUser()方法,方法源碼:

1

2

3

4

5

public static String getStringForUser(ContentResolver resolver, String name,

            int userHandle) {

        ... ...

        return sNameValueCache.getStringForUser(resolver, name, userHandle);

    }

        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

public String getStringForUser(ContentResolver cr, String name, final int userHandle) {

            //1

            final boolean isSelf = (userHandle == UserHandle.myUserId());

            int currentGeneration = -1;

            if (isSelf) {

                synchronized (NameValueCache.this) {

                    if (mGenerationTracker != null) {

                        if (mGenerationTracker.isGenerationChanged()) {

                            ... ...

                            mValues.clear();

                        else if (mValues.containsKey(name)) {

                            return mValues.get(name);

                        }

                        if (mGenerationTracker != null) {

                            currentGeneration = mGenerationTracker.getCurrentGeneration();

                        }

                    }

                }

            }

            IContentProvider cp = mProviderHolder.getProvider(cr);

            if (mCallGetCommand != null) {

                try {

                    Bundle args = null;

                    ... ...

                    Bundle b;

                    if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) {

                        final long token = Binder.clearCallingIdentity();

                        try {

                            //2

                            b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);

                        finally {

                            Binder.restoreCallingIdentity(token);

                        }

                    else {

                        b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);

                    }

                    if (b != null) {

                        String value = b.getString(Settings.NameValueTable.VALUE);

                        //3

                        ... ...

                        return value;

                    }

                catch (RemoteException e) {

                }

            }

           ... ...

        }

        上面的代碼註釋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

public Bundle call(String method, String name, Bundle args) {

        final int requestingUserId = getRequestingUserId(args);

        switch (method) {

            ... ...

            case Settings.CALL_METHOD_GET_SYSTEM: {

                //1

                Setting setting = getSystemSetting(name, requestingUserId);

                //2

                return packageValueForCallResult(setting, isTrackingGeneration(args));

            }

           ... ...

        }

        return null;

    }

        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

private Setting getSystemSetting(String name, int requestingUserId) {

        //1

        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);

        enforceSettingReadable(name, SETTINGS_TYPE_SYSTEM, UserHandle.getCallingUserId());

        final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name);

        //2

        synchronized (mLock) {

            return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SYSTEM, owningUserId, name);

        }

    }

        源碼註釋1處代碼完成多用戶和權限相關的處理;註釋2處調用SettingsRegistry的getSettingLocked()方法獲取數據,SettingsRegistry.getSettingsLocked()方法源碼:

1

2

3

4

5

6

7

8

9

10

public Setting getSettingLocked(int type, int userId, String name) {

           final int key = makeKey(type, userId);

           //1

           SettingsState settingsState = peekSettingsStateLocked(key);

           if (settingsState == null) {

               return null;

           }

           //2

           return settingsState.getSettingLocked(name);

       }

        源碼註釋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

private SettingsState peekSettingsStateLocked(int key) {

           //1

           SettingsState settingsState = mSettingsStates.get(key);

           if (settingsState != null) {

               return settingsState;

           }

           //2

           if (!ensureSettingsForUserLocked(getUserIdFromKey(key))) {

               return null;

           }

           return mSettingsStates.get(key);

       }

        註釋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

public boolean ensureSettingsForUserLocked(int userId) {

            ... ...

            // 1

            migrateLegacySettingsForUserIfNeededLocked(userId);

            ... ...

            // 2

            final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);

            ensureSettingsStateLocked(systemKey);

            ... ...

            return true;

        }

        註釋1處調用SettingsRegistry.migrateLegacySettingsForUserIfNeededLocked()遷移數據,遷移數據的邏輯和前文介紹系統首次啓動時遷移數據的邏輯一樣,不用的是內部只會調用migrateLegacySettingsForUserLocked()一次,爲當前指定的用戶執行數據遷移流程,這是應對新創建的用戶xml文件不存在的情形;註釋2處調用SettingsRegistry.ensureSettingsStateLocked加載xml文件中的數據到緩存,ensureSettingsStateLocked()源碼:

1

2

3

4

5

6

7

8

9

private void ensureSettingsStateLocked(int key) {

            if (mSettingsStates.get(key) == null) {

                final int maxBytesPerPackage = getMaxBytesPerPackageForType(getTypeFromKey(key));

                //1

                SettingsState settingsState = new SettingsState(getContext(), mLock,

                        getSettingsFile(key), key, maxBytesPerPackage, mHandlerThread.getLooper());

                mSettingsStates.put(key, settingsState);

            }

        }

        註釋1處,爲給定的key值創建一個新的SettingsState對象,並將對象加入到SettingsRegistry持有的SparseArray<SettingsState>中,xml數據加載的過程在SettingsState的構造函數中完成,源碼:

1

2

3

4

5

6

7

8

public SettingsState(Context context, Object lock, File file, int key,

            int maxBytesPerAppPackage, Looper looper) {

        ... ...

        synchronized (mLock) {

            //1

            readStateSyncLocked();

        }

    }

        註釋1處,調用SettingsState.readStateSyncLocked()方法,處理xml文件,源碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

private void readStateSyncLocked() {

        //1

        FileInputStream in;

        try {

            in = new AtomicFile(mStatePersistFile).openRead();

        catch (FileNotFoundException fnfe) {

            ... ...

        }

        try {

            XmlPullParser parser = Xml.newPullParser();

            parser.setInput(in, StandardCharsets.UTF_8.name());

            //2

            parseStateLocked(parser);

        catch (XmlPullParserException | IOException e) {

            ... ...

        finally {

            ... ...

        }

    }

        註釋1處,打開xml文件操作;註釋2處調用SettingsState.parseStateLocked()方法正在完成xml文件解析,並將數據存入到SettingsState緩存中,源碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

private void parseStateLocked(XmlPullParser parser)

            throws IOException, XmlPullParserException {

        ... ...

        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT

                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {

            ... ...

            String tagName = parser.getName();

            if (tagName.equals(TAG_SETTINGS)) {

                //1

                parseSettingsLocked(parser);

            }

        }

    }

        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

private void parseSettingsLocked(XmlPullParser parser)

            throws IOException, XmlPullParserException {

        //1

        mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));

        ... ...

        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT

                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {

            ... ...

            String tagName = parser.getName();

            if (tagName.equals(TAG_SETTING)) {

                //2

                String id = parser.getAttributeValue(null, ATTR_ID);

                String name = parser.getAttributeValue(null, ATTR_NAME);

                String value = getValueAttribute(parser, ATTR_VALUE, ATTR_VALUE_BASE64);

                String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);

                String defaultValue = getValueAttribute(parser, ATTR_DEFAULT_VALUE,

                        ATTR_DEFAULT_VALUE_BASE64);

                String tag = null;

                DebugUtil.Log("loadsetting- name: "+name);

                boolean fromSystem = false;

                if (defaultValue != null) {

                    fromSystem = Boolean.parseBoolean(parser.getAttributeValue(

                            null, ATTR_DEFAULT_SYS_SET));

                    tag = getValueAttribute(parser, ATTR_TAG, ATTR_TAG_BASE64);

                }

                //3

                mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag,

                        fromSystem, id));

            }

        }

    }

        代碼註釋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的各個系統框架都很複雜、代碼量較大、而且系統模塊之間相互耦合較深,想面面俱到的對代碼做出分析,往往會把你帶入泥潭,建議像我一樣的初學者對各個模塊的分析採取把握設計主幹、工作中具體問題具體分析,再去扣細節源碼,避免迷失深林之中。

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