一、SettingsProvider简介
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创建启动。
二、SettingsProvider 的功能和用途
SettingsProvider 为获取和管理系统的应用设置信息提供了一个标准化的方式,通常包括了功能和用途细分如下:
2.1. 系统级设置管理
系统级设置包括各种基础的系统设置, 比如Wi-Fi设置、蓝牙设置、语言与输入设置、日期与时间等,所有应用程序和用户都能够使用这些设置。这些设置还可以在应用程序中使用 Preference API 进行存储和读取。以下是获取并编辑 Wi-Fi 设置的示例代码:
Uri wifiUri = Settings.System.getUriFor(Settings.System.WIFI_SLEEP_POLICY); ContentResolver cr = getContentResolver(); cr.registerContentObserver(wifiUri, false, wifiObserver); Settings.System.putInt(cr,Settings.System.WIFI_SLEEP_POLICY,Settings.System.WIFI_SLEEP_POLICY_NEVER); |
---|
2.2. 应用级设置管理
应用级设置允许应用程序将其特定设置保存在数据库中,然后在应用程序中进行读取和管理。这些设置通常是针对单个应用程序,其他应用程序和系统无法访问。以下是所述存储在数据库中的内容的示例:
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/accounts"); public static final String ACCOUNT_NAME = "name"; public static final String ACCOUNT_TYPE = "type"; public static final String ACCOUNT_PASSWORD = "password"; |
---|
2.3. 设备管理员设置
设备管理员设置为企业应用程序提供了额外的安全性,并可以控制一些特定的功能。设备管理员通常是管理员、公司或组织,而用户必须通过设备管理员策略来使资产得到管理。以下是示例代码,用于设置设备策略:
DevicePolicyManager devicePolicyManager =(DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE); ComponentName adminComponentName = new ComponentName(this, DeviceAdminReceiver.class); Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN); intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, adminComponentName); startActivityForResult(intent, ADD_DEVICE_ADMIN_REQUEST_CODE); devicePolicyManager.setPasswordQuality(adminComponentName, DevicePolicyManager.PASSWORD_QUALITY_COMPLEX); |
---|
三、关键设计和结构
3.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”中,本文中暂时不对相关的数据和代码做分析。
3.2. 关键设计
3.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,可以用作对比分析。
3.2.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.2.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.3. 关键源码解读
3.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
3.3.2 SettingsProvider AndroidManifest.xml文件
AndroidManifest.xml
<manifest ... |
---|
分析Manifest文件,从shareUserId知道SettingsProvider运行在系统进程中,从backupAgent知道SettingsProvider使用系统的备份框架对关键数据进行了备份操作,从authorities知道SettingsProvider Uri的Authority是settings。
3.3.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源码:
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构造函数源码:
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是本文要分析的核心逻辑,这个函数十分重要,源码:
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源码:
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(),源码:
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, null, true, SettingsState.SYSTEM_PACKAGE_NAME); } globalSettings.persistSyncLocked(); } //4 if (DROP_DATABASE_ON_MIGRATION) { dbHelper.dropDatabase(); } else { dbHelper.backupDatabase(); } } |
---|
上面的代码注释1处,为用户处理System类型数据,1.首先生成与xml文件唯一对应的key;2. 调用ensureSettingsStateLocked()方法确保 :SparseArray<SettingsState>容器中有xml文件对应的:SettingsState对象;3.调用 migrateLegacySettingsLocked方法将临时数据库中设置数据更新到:SettingsState对象;4.调用:SettingsState.persistSyncLocked()方法把数据写入到xml文件中。
3.3.4 数据获取流程
设置数据获取大体时序逻辑见上图,通过Settings类封装之后SettingsProvider的数据获取变得相当简单(数据更新同样),在代码中只需要向下面这样调用Settings.System类的getString方法就能轻松获得某个属性数据:
|
---|
Settings.System.getString()方法调用了getStringForUser()方法,方法源码:
public static String getStringForUser(ContentResolver resolver, String name,int userHandle) { ... ... return sNameValueCache.getStringForUser(resolver, name, userHandle); } |
---|
Settings.System.getStringForUser()方法调用了NameValueCache缓存类的同名方法,方法源码:
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()方法,方法源码:
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()方法,方法源码:
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()方法源码:
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(),源码:
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()源码:
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()源码:
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的构造函数中完成,源码:
public SettingsState(Context context, Object lock, File file, int key,int maxBytesPerAppPackage, Looper looper) { ... ... synchronized (mLock) { //1 readStateSyncLocked(); } } |
---|
注释1处,调用SettingsState.readStateSyncLocked()方法,处理xml文件,源码:
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缓存中,源码:
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文件解析,源码:
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类型的数据获取流程基本一样,只是在某些处理细节上存在差异。
3.3.5 数据设置流程
数据设置流程和数据上文讲的数据设置流程调用过程基本类似,客户端调用时使用调用Settings.System.putString()或Settings.System.GetString()方法设置System类型的数据,本文就不重复分析这部分的源码了。
四、在应用程序中使用 SettingsProvider
任何具有“android.permission.WRITE_SETTINGS”权限的应用程序均可以访问 SettingsProvider 数据。以下是一些在应用程序中使用 SettingsProvider 的代码示例:
Uri soundUri = Settings.System.DEFAULT_RINGTONE_URI; Ringtone ringtone = RingtoneManager.getRingtone(this, soundUri); ringtone.play() |
---|