简介

        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源码分析(Android 9.0)_第1张图片

       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间接维护的缓存中的设置数据条目保持“用时同步”。

       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文件,从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 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 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容器中有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对象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中,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的各个系统框架都很复杂、代码量较大、而且系统模块之间相互耦合较深,想面面俱到的对代码做出分析,往往会把你带入泥潭,建议像我一样的初学者对各个模块的分析采取把握设计主干、工作中具体问题具体分析,再去扣细节源码,避免迷失深林之中。

更多相关文章

  1. Android使用Bundle进行数据传递分析
  2. Android里监视数据库的变化 registerContentObserver 接口
  3. Android SharePreferences源码解析
  4. 如何在Android系统源码中添加一个C项目?
  5. android 之json对象解析并展示(含json解析源码)
  6. Android下的数据储存方式(三)
  7. Activity 的启动流程源码分析(Android 9.0)
  8. android中将数据写入手机内存和sdcard中的文件
  9. Android IPC 通讯机制源码分析 一

随机推荐

  1. MySQL基础知识——安装
  2. 这该死的游标,怎么这么难呢?
  3. LVS NAT原理、配置
  4. VmwareWorkstation 16无法安装VMware Too
  5. Mac系统上查看端口占用和释放端口教程
  6. 职场里,对数据库要有敬畏之心!
  7. 使用jquery实现全选、全不选等购物车操作
  8. Too many open files) when using LOCK T
  9. 【小思考】如何在实际工作中提升数据价值
  10. javascript常用代码汇总