SettingProvider

SettingsProvider 顾名思义是一个提供设置数据共享的 Provider,SettingsProvider 和Android 系统其它 Provider 有很多不一样的地方,如:

SettingsProvider 只接受 int、float、string等基本类型的数据;

SettingsProvider 由 Android 系统 framework 进行了封装,使用更加快捷方便

SettingsProvider 的数据由键值对组成

SettingsProvider 有点类似 Android 的 properties 系统(Android 属性系统):SystemProperties。SystemProperties 除具有 SettingsProvider 以上的三个特性,SettingsProvider 和 SystemProperties 的不同点在于:

数据保存方式不同:SystemProperties 的数据保存属性文件中(/system/build.prop等),开机后会被加载到 system properties store;SettingsProvider 的数据保存在文件/data/system/users/0/settings_***.xml 和数据库 settings.db 中;

作用范围不同:SystemProperties可以实现跨进程、跨层次调用,即底层的 c/c++ 可以调用,java 层也可以调用;SettingProvider 只能能在 java 层(APP)使用;

公开程度不同:SettingProvider 有部分功能上层第三方 APP 可以使用,SystemProperties 上层第三方 APP 不可以使用。

用一句话概括 SettingsProvider 的作用,SettingsProvider 包含全局性、系统级别的用户编好设置。在手机中有一个 Settings 应用,用户可以在 Settings 里面做很多设备的设置,这些用户偏好的设置很多就保存在 SettingsProvider 中。例如,飞行模式。

在 Android 6.0 版本时,SettingsProvider 被重构,Android 从性能、安全等方面考虑,把SettingsProvider 中原本保存在 settings.db 中的数据,目前全部保存在 XML 文件中。

SettingsProvider概览

主要源码

SettingsProvider 的代码数量不多,主要包含如下的 java 文件:

数据分类

SettingsProvider 对数据进行了分类,分别是 Global、System、Secure 三种类型,它们的区别如下:

Global:所有的偏好设置对系统的所有用户公开,第三方APP有读没有写的权限;

System:包含各种各样的用户偏好系统设置;

Secure:安全性的用户偏好系统设置,第三方APP有读没有写的权限。

AndroidManifest.xml配置

SettingsProvider 的 AndroidManifest.xml 文件对应用和 ContentProvider的配置如下:

这些代码定义在文件 frameworks/base/packages/SettingsProvider/AndroidManifest.xml中。

上面的 Manifest 配置由 sharedUserId 可知,SettingsProvider 运行在系统进程中,定义的 ContentProvider 实现类是 SettingsProvider,Uri 凭证是 settings。

SettingsProvider的启动过程

启动 SettingsProvider 即运行 SettingsProvider,和打开一个 Activity 类似,会回调 ContentProvider 的生命周期方法,首先的,会调用 OnCreate() 方法,如下:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

上面的代码首先是实例化一个 HandlerThread 的实例 mHandlerThread,优先级为 Process.THREAD_PRIORITY_BACKGROUND,下文会用到。然后实例化 SettingsRegistry 的实例 mSettingsRegistry,这一步很重要。接着会注册广播接收器,所关心的广播包括设备用户变化以及 APP 卸载的广播,设备用户的变化对大多数地方使用 SettingProvider的影响不是很大,本文就不再阐述和用户变化相关的内容了。但是APP卸载这里需要关注一下,当一个APP有数据保存在 SettingsProvider 时,APP 被卸载后,被卸载的 APP 设置的所有数据都会被清除。回到 SettingsRegistry 的实例化过程,构造方法如下:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

migrateAllLegacySettingsIfNeeded() 方法,从命名上是迁移 settings 数据,迁移什么数据呢?从哪里迁移到哪里呢?继续往下看:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

上面的代码首先是调用了 makeKey()方法,所谓 makeKey() 就是和上文中的数据分类小章节中提到的 System、Global 和 Secure 三种 key。然后调用 getSettingsFile() 方法获取到一个 File 对象的实例,如下:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

上面的代码中对Global、System、Secure 分别生成一个 File 对象实例,它们的 File 对象分别对应的文件是:

/data/system/users/0/settings_global.xml

/data/system/users/0/settings_system.xml

/data/system/users/0/settings_secure.xml

那么也就是说,Global 类型的数据保存在文件 settings_global.xml 中,System 类型的数据保存在文件 settings_system.xml 中,Secure 类型的数据保存在文件 settings_secure.xml中。

回到上文中的 migrateAllLegacySettingsIfNeeded() 方法,实例化一个 DatabaseHelper,DatabaseHelper 是 SQLiteOpenHelper 的子类,然后调用 getWritableDatabase() 获取到指向数据库文件的 SQLiteDatabase 实例 database。从 Android SQLite 的架构可知,这个过程会调用 SQLiteOpenHelper 的 onCreate() 方法,如果读者对这个过程迷惑的,可以阅读 Android 的 API 指南:

https://developer.android.google.cn/guide/topics/data/data-storage.html#db

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java 中。

这个方法调用 db.execSQL(“CREATE TABLE system …、createSecureTable()、createGlobalTable() 分别创建 System、Secure、Global 三个数据库表,这个和上文中数据分类章节中的内容一致。接着调用 loadVolumeLevels(db) 方法,把默认的铃声音量、音乐音量、通知音量以及震动设置等等写入到数据库的 System 表格中。处理完后调用方法 loadSettings(db),如下:

这个方法定义在文件frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java中。

loadSettings() 这个方法和 loadVolumeLevels() 方法类似,都是加载很多默认值写入到数据库中,这些默认值很大一部分被定义在文件 frameworks/base/packages/SettingsProvider/res/values/defaults.xml 中,也有一些来自其它地方。总之,loadVolumeLevels() 和 loadSettings() 的作用就是在手机第一次启动时,把手机编好设置的默认值写入到数据库 settings.db中。

DatabaseHelper 的 onCreate() 方法执行完毕后,这里又回到 migrateAllLegacySettingsIfNeeded() 方法中,DatabaseHelper 创建完毕后,继续调用 migrateLegacySettingsForUserLocked() 方法,如下:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

上面的代码中的每个方法都是那么重要,首先是 ensureSettingsStateLocked(systemKey),如下:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

上面代码实例化一个 SettingsState 对象,这个对象指向文件 /data/system/users/0/settings_system.xml,然后把 settingsState 放置在对象 mSettingsStates 中。回到 migrateLegacySettingsForUserLocked() 方法,ensureSettingsStateLocked() 执行完毕后,调用migrateLegacySettingsLocked() 方法,如下:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

上面这个方法,查询数据库中 System 所有的设置,然后在 while 循环中把每个值的信息作为insertSettingLocked() 的参数,insertSettingLocked() 方法如下:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java 中。

上面的方法把每个设置项封装到对象 Setting 中,接着有把 state 放置到 ArrayMapString, Setting 的实例 mSettings 中。

那么,从方法 ensureSettingsStateLocked() 到 insertSettingLocked() 方法,这个过程表明,有一个对象 SettingsState,指向文件 /data/system/users/0/settings_system.xml,持有变量 mSettings,而 mSettings 持有封装了设置项的 name, value, packageName 的对象Setting,换句话说,settings_system.xml 文件中的所有的设置项间接被 SettingsState 持有。

又回到 migrateLegacySettingsForUserLocked() 方法,migrateLegacySettingsLocked() 方法执行完毕后,调用 systemSettings.persistSyncLocked(),systemSettings 是 SettingsState 的实例,代表的是 settings_system.xml 所有的设置项,persistSyncLocked() 方法就是把 systemSettings 持有的所有的设置项从内存中固化到文件 settings_system.xml中,这个过程的代码就不贴出来了。migrateLegacySettingsForUserLocked() 方法中省略的代码,就是和上文的这几个方法一样,把 settings_global.xml、settings_secure.xml 两个文件中的所有设置项封装到 Setting 中,被 SettingsState 持有。

也就是说,settings_global.xml、settings_secure.xml、settings_system.xml 三个文件的所有设置项间接被 SettingsState 持有,而 SettingsState 又被封装到类 SettingsProvider.java 的变量 mSettingsStates 中,mSettingsStates 是 SparseArray”SettingsState” 的实例。它们的层次关系如下图:

再次回到方法 migrateLegacySettingsForUserLocked(),在把数据中的数据转移到 xml 文件后,执行下面这段代码:

如果是工程版本的系统,把数据库 settings.db 重命名为 settings.db-backup,如果是非工程版本的系统,把数据库文件删除,也会删除日志settings.db-journal。

SettnigsProvider 启动时会创建 settings.db 数据库,然后把所有的默认设置项写入到数据库,接着会把数据库中所有的设置项从数据库转移到 xml 文件中,随后便会对数据库执行删除操作。为什么会有这么一个过程,这些过程是否可以移除创建数据库这一步?其实这个过程是为了兼容之前的版本而设计,在 SettingsProvider 被 Android 重构后,SettingsProvider 中数据库相关的代码 Android 已经停止更新。

封装 SettingsProvider 接口

由章节前言中的描述,SettingsProvider 是向整个 Android 系统提供用户编好设置的提供程序,所保存的数据类型和方式上也有一定约束和规定,且要求使用 SettingsProvider 是方便的,代码量少的。因此,需要对 ContentProvider 的一些接口进行封装,以保证在整个Android 的 java 层任何一个地方都能方便、快捷的使用 SettingsProvider 进行数据查询,数据更新和数据插入。所以,理所当然地,framework 有一个类 Settings.java 对使用 SettingsProvider 进行了封装。如下:

这个类定义在文件 frameworks/base/core/java/android/provider/Settings.java 中。

上面的代码中,分别声明了 Global、Secure、System 三个静态内部类,分别对应 SettingsProvider 中的 Global、Secure、System 三种数据类型。Global、Secure、System 三个静态内部类会分别持有自己 NameValueCache 的实例变量,每个 NameValueCache 持有指向 SettingsProvider 中的 SettingsProvider.java 的 AIDL 远程调用 IContentProvider,读者可以阅读《Android System Server 大纲之 ContentService 和 ContentProvider 原理剖析》:

http://blog.csdn.net/myfriend0/article/details/58587065

了解ConatentProvider的这个过程。因此,查询数据需要经过NameValueCache 的 getStringForUser() 方法,插入数据需要经过 putStringForUser() 方法。同时,NameValueCache 还持有一个变量 mValues,用于保存查询过的设置项,以便下下次再次发起查询时,能够快速返回。

操作SettingsProvider

由于 Settings.java 对使用 SettingsProvider 进行了封装,所以,使用起来相当简单简洁。由于 Global、Secure、System 三种数据类型的使用是几乎相同,所以本文就只以 Global为例对查询插入数据的过程进行分析。

查询数据

从 SettingsProvider 的 Global 中查询数据,查询是否是飞行模式使用方法如下:

上面的代码,用起来代码量很少,只需要一行代码即可查询到所需要的值。深入 Settings.java 看 getString() 方法:

这些方法定义在文件 frameworks/base/core/java/android/provider/Settings.java中。

getString() 直接调用了getStringForUser(),getStringForUser() 首先有做一个判断MOVED_TO_SECURE.contains(name),做这个判断是因为在 Android 系统的更新中,保存在 Global、Secure、System 三种类型的数据的存放位置有变化,所以需要加这个判断兼容老版本的使用方法。在章节“封装 SettingsProvider 接口”中提到,查询必须经过 NameValueCache.getStringForUser()方法,如下:

这个方法定义在文件frameworks/base/core/java/android/provider/Settings.java中。

首先从缓存 mValues 变量中去找,如果没有查询到,就调用 SettingsProvider 的 call() 接口,如果 call() 接口也没有查询到,再调用 query() 接口。这里用的是 call() 接口,下文就以 call() 接口往下分析。cp.call() 会调用到 SettingsProvider 的 call()方法,读者可以阅读《Android System Server 大纲之 ContentService 和 ContentProvider 原理剖析》了解 ConatentProvider 的这个过程。注意参数 mCallGetCommand。

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

上层传过来的参数的 command 是 mCallGetCommand,即Settings.CALL_METHOD_GET_GLOBAL,所以调用 getGlobalSetting(),方法,在章节“SettingsProvider 的启动过程”中可知,getGlobalSetting() 返回的 Setting setting 是封装了设置项 name、value 等信息的,查看 getGlobalSetting() 方法:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

通过 mSettingsRegistry.getSettingLocked() 继续寻找:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

通过 peekSettingsStateLocked(key) 寻找 SettingsState:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

获取到 SettingsState settingsState,回到 getSettingLocked(),调用 settingsState.getSettingLocked(name):

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java 中。

到 getSettingLocked() 最终获取到 Setting setting,其实这个过程就是章节 “SettingsProvider 的启动过程” 中的层次关系图的反映,读者可以查看这个图更好理解这个过程。

插入数据

从SettingsProvider的Global中插入数据,插入飞行模式的使用方法如下:

和查询一样,代码非常简洁,这个过程和查询几乎类似,将快速游览这个过程。往下跟踪:

这个方法定义在文件 frameworks/base/core/java/android/provider/Settings.java 中。

和查询一样,继续看 putStringForUser():

这个方法定义在文件 frameworks/base/core/java/android/provider/Settings.java 中。

直接调用 SettingsProvider 的 call() 接口:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

首先调用 getSettingValue(args) 获取对应的设置项,接着 insertGlobalSetting() 方法:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

直接调用了 mutateGlobalSetting() 方法:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

首先对使用的进行权限检查,然后调用 mSettingsRegistry.insertSettingLocked() 方法:

这个方法定义在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。

到这里,就不往下分析了,其实这个即是查询的反过程,结合章节 “SettingsProvider 的启动过程”中的层次关系图,能够很好理解这个过程。

SettingsProvider使用

查阅 SettingsProvider 的设置项不需要声明任何权限。

第三方 APP 可以通过 framework 的 Settings.java 查询 SettingsProvider 中的设置项,使用方法查阅章节“查询数据”。第三 APP 是否可以修改 SettingsProvider 的设置项 Android 系统不允许第三方 APP 修改 SettingsProvider 中的设置项。

权限问题

查阅 SettingsProvider 的设置项不需要声明任何权限。

修改 SettingsProvider 需要权限:

android.permission.WRITE_SETTINGS,Protection level: signature

Secure数据:android.permission.WRITE_SECURE_SETTINGS,Not for use by third-party applications.

对已Global和Secure模块,还需要关心上文中的 isGlobalOrSecureSettingRestrictedForUser() 方法设置到的限制。

总结

本文从 SettingsProvider 的启动过程到使用 SettingsProvider 查询插入数据进行了详细的过程描述,在 SettingsProvider 的启动过程中,需要创建数据库,把默认设置项值写入到数据库,把数据中的所有数据,迁移到 xml 文件中。SettingsProvider 的查询和插入结合章节 “SettingsProvider 的启动过程” 中的层次关系图,一目了然。对于第三方 APP 只有读没有写的能力。由于 SettingsProvider 的特性,虽然 SettingsProvider 是跨进程通信,但是由于从多个层次都做了缓存,且 SettingsProvider 中的同步协作机制,只要时间都是花费在 Binder 通信上面,但是 Binder 通信是一种快速的跨进程通信的过程,所以在主线程(UI线程)中可以直接使用 SettingsProvider 查阅插入数据而不会导致UI阻塞导致 ANR(应用程序无响应)。另外,由于 SettingsProvider 的特性和限制,SettingsProvider 不予写入过多的数据,最好只是系统设置相关的设置项才保存到 SettingsProvider 中,同时也不适合写入过大的数据,否则将会严重影响 SettingsProvider 的性能。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值