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类中:
|
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启动系统服务的过程中安装进系统,源码:
折叠源码
|
上面的代码显示系统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
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
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
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的各个系统框架都很复杂、代码量较大、而且系统模块之间相互耦合较深,想面面俱到的对代码做出分析,往往会把你带入泥潭,建议像我一样的初学者对各个模块的分析采取把握设计主干、工作中具体问题具体分析,再去扣细节源码,避免迷失深林之中。
更多相关文章
- Android使用Bundle进行数据传递分析
- Android里监视数据库的变化 registerContentObserver 接口
- Android SharePreferences源码解析
- 如何在Android系统源码中添加一个C项目?
- android 之json对象解析并展示(含json解析源码)
- Android下的数据储存方式(三)
- Activity 的启动流程源码分析(Android 9.0)
- android中将数据写入手机内存和sdcard中的文件
- Android IPC 通讯机制源码分析 一