SettingsProvider源码流程解析

简介: 前阵子遇到个问题,OTA升级之后设置中的默认设置项发生了改变,后来查找发现设置中该设置项是通过读取系统数据库中的数据来决定选项是否开启,所以顺带查看了一下SettingsProvider源码,后来发现问题跟SettingsProvider没关系,但还是抽时间将阅读内容整理记录下来。
模块总览: 【SettingsProvider源码流程解析】该模块主要用于记录一些重要的系统数据,模块代码位于:
\frameworks\base\packages\SettingsProvider
代码不多,就几个java文件,围绕一个SettingsProvider展开,类关系图如下:
SettingsProvider源码流程解析
文章图片

所以manifest文件也十分简洁:


在framework中封装了一个Settings类,提供对SettingsProvider的读写接口。代码路径位于:
frameworks\base\core\java\android\provider\Settings.java

这里主要从以下两点来分析:
  1. 应用的初始化
  2. 数据的读写
1.应用的初始化 SettingsProvider的主要功能大多都在子类SettingsRegistry中实现,以前数据都记录在数据库中,6.0之后就不再使用数据库作为主要存储了,数据主要存储在xml文件中,这些文件路径定义在SettingsRegistry中:
private static final String SETTINGS_FILE_GLOBAL = "settings_global.xml"; private static final String SETTINGS_FILE_SYSTEM = "settings_system.xml"; private static final String SETTINGS_FILE_SECURE = "settings_secure.xml";

文件路径定义为:
private File getSettingsFile(int key) { if (isGlobalSettingsKey(key)) { final int userId = getUserIdFromKey(key); return new File(Environment.getUserSystemDirectory(userId), SETTINGS_FILE_GLOBAL); } else if (isSystemSettingsKey(key)) { final int userId = getUserIdFromKey(key); return new File(Environment.getUserSystemDirectory(userId), SETTINGS_FILE_SYSTEM); } else if (isSecureSettingsKey(key)) { final int userId = getUserIdFromKey(key); return new File(Environment.getUserSystemDirectory(userId), SETTINGS_FILE_SECURE); } else { throw new IllegalArgumentException("Invalid settings key:" + key); } }

所以就是如下三个文件:
data/system/users/0/settings_global.xml data/system/users/userid/settings_system.xml data/system/users/userid/settings_secure.xml

首先来分析SettingsProvider的onCreate方法:
@Override public boolean onCreate() { synchronized (mLock) { mUserManager = UserManager.get(getContext()); mPackageManager = AppGlobals.getPackageManager(); mHandlerThread = new HandlerThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND); mHandlerThread.start(); //初始化SettingsRegistry,会在这里做一次数据迁移 mSettingsRegistry = new SettingsRegistry(); } //注册广播监听 registerBroadcastReceivers(); //这里会监听用户限制相关的一些改变,比如分享位置,安装位置来源应用,使能adb等,然后对相关xml文件做修改 startWatchingUserRestrictionChanges(); return true; }

SettingsRegistry的初始化如下:
public SettingsRegistry() { mHandler = new MyHandler(getContext().getMainLooper()); mGenerationRegistry = new GenerationRegistry(mLock); mBackupManager = new BackupManager(getContext()); //如果需要的话就做数据迁移 migrateAllLegacySettingsIfNeeded(); }

数据迁移方法如下,这个方法猜测该是google为了兼容设备跨android版本升级而做的特殊处理,该处理只有在设备第一次开机xml文件不存在时候才会做迁移,将数据库总的数据都迁移到xml文件中来。
private void migrateAllLegacySettingsIfNeeded() { synchronized (mLock) { final int key = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM); File globalFile = getSettingsFile(key); //这里检测settings_global.xml如果已经存在了的话,那就没有必要去做迁移了,不存在才考虑是否需要做数据迁移。 if (globalFile.exists()) { return; }final long identity = Binder.clearCallingIdentity(); try { List users = mUserManager.getUsers(true); final int userCount = users.size(); //轮询一遍用户id,将数据库数据全部迁移,迁移后检测版本和用户是否正在运行 for (int i = 0; i < userCount; i++) { final int userId = users.get(i).id; DatabaseHelper dbHelper = new DatabaseHelper(getContext(), userId); SQLiteDatabase database = dbHelper.getWritableDatabase(); //将数据库中数据迁移到xml,这个也是我们重点分析的方法 migrateLegacySettingsForUserLocked(dbHelper, database, userId); // Upgrade to the latest version. UpgradeController upgrader = new UpgradeController(userId); upgrader.upgradeIfNeededLocked(); // Drop from memory if not a running user. if (!mUserManager.isUserRunning(new UserHandle(userId))) { removeUserStateLocked(userId, false); } } } finally { Binder.restoreCallingIdentity(identity); } } }

数据迁移方法如下,这个方法会将每个用户id下的数据库内容都迁移到用户id目录下的xml文件中:
private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper, SQLiteDatabase database, int userId) { //这里仅仅分析迁移system数据,secure和global原理一样 //首先获取key final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId); //其次生成对应的SettingsState同时加入到mSettingsStates列表中 ensureSettingsStateLocked(systemKey); //将上面生成且加入到列表中的SettingsState获取出来 SettingsState systemSettings = mSettingsStates.get(systemKey); //读取数据库内容写加入到SettingsState的settings列表中 migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM); //将SettingsState的settings列表中数据写入到xml文件中 systemSettings.persistSyncLocked(); // Move over the secure settings. // Do this after System settings, since this is the first thing we check when deciding // to skip over migration from db to xml for a secondary user. final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId); ensureSettingsStateLocked(secureKey); SettingsState secureSettings = mSettingsStates.get(secureKey); migrateLegacySettingsLocked(secureSettings, database, TABLE_SECURE); ensureSecureSettingAndroidIdSetLocked(secureSettings); secureSettings.persistSyncLocked(); // Move over the global settings if owner. // Do this last, since this is the first thing we check when deciding // to skip over migration from db to xml for owner user. 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); globalSettings.persistSyncLocked(); }//这里的DROP_DATABASE_ON_MIGRATION为true,所以迁移完成数据库就删除了 if (DROP_DATABASE_ON_MIGRATION) { dbHelper.dropDatabase(); } else { dbHelper.backupDatabase(); } }

private void migrateLegacySettingsLocked(SettingsState settingsState, SQLiteDatabase database, String table) { SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); queryBuilder.setTables(table); Cursor cursor = queryBuilder.query(database, ALL_COLUMNS, null, null, null, null, null); if (cursor == null) { return; }try { if (!cursor.moveToFirst()) { return; }final int nameColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.NAME); final int valueColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.VALUE); settingsState.setVersionLocked(database.getVersion()); while (!cursor.isAfterLast()) { String name = cursor.getString(nameColumnIdx); String value = https://www.it610.com/article/cursor.getString(valueColumnIdx); //这里将数据会先写入到mSettings列表中,但是不一定会马上写入到xml文件中 settingsState.insertSettingLocked(name, value, SettingsState.SYSTEM_PACKAGE_NAME); cursor.moveToNext(); } } finally { cursor.close(); } }

上面的写入操作可能会暂时还没有写入到xml文件中,只是存储到了SettingsState的mSettings列表中。下面的方法就会将mSettings中的数据同步到xml文件中
public void persistSyncLocked() { mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS); doWriteState(); }

至此,初始化的主要流程就分析完成了,总结下如下:
初始化流程主要包括如下三点:
1.在初始化中,如果发现xml文件不存在,那就需要将数据从数据库中迁移到xml,如果xml已经存在了,那就不用做迁移了。。
2.设置广播监听用户id的停止和移除,应用的删除,以便及时更新xml中的数据。。
3.监听部分用户相关设置的变更。
通过初始化过程的分析,也可知晓,三份数据global/system/secrue是以SettingsState为数据载体,保存在一个列表中mSettingsStates:
private final SparseArray mSettingsStates = new SparseArray<>();

而SettingsState中的数据又是以内部类Setting为载体,保存在列表mSettings中
private final ArrayMap mSettings = new ArrayMap<>();

同步数据就是将mSettings中的数据覆写到xml文件中一遍。
2.数据的读写过程 首先查看数据的读取过程,数据的读取与写入过程都写在了封装类Settings.java中。settings中我们重点关注三个子类:Global/System/Secure,分别对应所有用户设置/系统设置/安全设置。我们以读取Global中数据为例:
public static int getInt(ContentResolver cr, String name, int def) { String v = getString(cr, name); try { return v != null ? Integer.parseInt(v) : def; } catch (NumberFormatException e) { return def; } }public static int getInt(ContentResolver cr, String name) throws SettingNotFoundException { String v = getString(cr, name); try { return Integer.parseInt(v); } catch (NumberFormatException e) { throw new SettingNotFoundException(name); } }

都是调用了getString:
public static String getString(ContentResolver resolver, String name) { return getStringForUser(resolver, name, UserHandle.myUserId()); }/** @hide */ public static String getStringForUser(ContentResolver resolver, String name, int userHandle) { if (MOVED_TO_SECURE.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global" + " to android.provider.Settings.Secure, returning read-only value."); return Secure.getStringForUser(resolver, name, userHandle); } return sNameValueCache.getStringForUser(resolver, name, userHandle); }

使用到了NameValueCache的方法,Global/System/Secure三个类都各自持有一个NameValueCache的实例,只是传入的get/set命令不同而已。在NameValueCache中持有一个mVlues的哈希表,将获取过的数据存入其中,每次读取也会优先从列表中查找,如果列找中没有,就去数据库中查找。
数据的获取过程主要分为如下几点:
1.首先检测用户id是否正确,如果不正确是不能够获取数据的
2.查看mValues中是否已经存在了,存在了就直接将数据返回
3.获取contentprovider
4.如果mCallGetCommand命令不为空,则调用call命令来获取数据,获取到之后将其加入到mValues中,然后返回数据
5.获取命令为空就调用query方法获取数据,将其加入到mValues中,然后返回数据
private final HashMap mValues = new HashMap(); public NameValueCache(Uri uri, String getCommand, String setCommand) { mUri = uri; mCallGetCommand = getCommand; mCallSetCommand = setCommand; }public String getStringForUser(ContentResolver cr, String name, final int userHandle) { final boolean isSelf = (userHandle == UserHandle.myUserId()); //检测用户是否正确 if (isSelf) { synchronized (NameValueCache.this) { if (mGenerationTracker != null) {...... //能从列表获取到就直接返回 } else if (mValues.containsKey(name)) { return mValues.get(name); } } } } else { if (LOCAL_LOGV) Log.v(TAG, "get setting for user " + userHandle + " by user " + UserHandle.myUserId() + " so skipping cache"); }IContentProvider cp = lazyGetProvider(cr); if (mCallGetCommand != null) { try { ...... //使用call获取数据 Bundle b = cp.call(cr.getPackageName(), mCallGetCommand, name, args); if (b != null) { String value = https://www.it610.com/article/b.getString(Settings.NameValueTable.VALUE); // Don't update our cache for reads of other users' data if (isSelf) { synchronized (NameValueCache.this) { ...... //将数据加入列表 mValues.put(name, value); } } else { if (LOCAL_LOGV) Log.i(TAG, "call-query of user " + userHandle + " by " + UserHandle.myUserId() + " so not updating cache"); } //返回数据 return value; }} catch (RemoteException e) { // Not supported by the remote side?Fall through // to query(). } }Cursor c = null; try { //获取命令为空,就调用query查找数据 c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER, new String[]{name}, null, null); if (c == null) { Log.w(TAG, "Can't get key " + name + " from " + mUri); return null; }String value = https://www.it610.com/article/c.moveToNext() ? c.getString(0) : null; synchronized (NameValueCache.this) { //数据加入列表后返回 mValues.put(name, value); } ...... return value; } catch (RemoteException e) { Log.w(TAG,"Can't get key " + name + " from " + mUri, e); return null; // Return null, but don't cache it. } finally { if (c != null) c.close(); } } }

我们来看下contentprovider中的call方法,以上代码请求的content_uri为:content://settings/global,SettingsProvider方法如下:
@Override public Bundle call(String method, String name, Bundle args) { final int requestingUserId = getRequestingUserId(args); switch (method) { //以上分析中传入的是该值,暂时只分析这里 case Settings.CALL_METHOD_GET_GLOBAL: { Setting setting = getGlobalSetting(name); return packageValueForCallResult(setting, isTrackingGeneration(args)); }case Settings.CALL_METHOD_GET_SECURE: { Setting setting = getSecureSetting(name, requestingUserId); return packageValueForCallResult(setting, isTrackingGeneration(args)); }case Settings.CALL_METHOD_GET_SYSTEM: { Setting setting = getSystemSetting(name, requestingUserId); return packageValueForCallResult(setting, isTrackingGeneration(args)); }case Settings.CALL_METHOD_PUT_GLOBAL: { String value = https://www.it610.com/article/getSettingValue(args); insertGlobalSetting(name, value, requestingUserId, false); break; }case Settings.CALL_METHOD_PUT_SECURE: { String value = getSettingValue(args); insertSecureSetting(name, value, requestingUserId, false); break; }case Settings.CALL_METHOD_PUT_SYSTEM: { String value = getSettingValue(args); insertSystemSetting(name, value, requestingUserId); break; }default: { Slog.w(LOG_TAG,"call() with invalid method: " + method); } break; }return null; }

之前讲过这三种数据都是以SettingsState为载体保存在列表mSettingsStates中,而SettingsState中又是以Setting为数据载体,将数据保存在列表mSettings中,所以数据的查找过程最终就是到不同的SettingsState中查找mSettings列表的过程。
private Setting getGlobalSetting(String name) { if (DEBUG) { Slog.v(LOG_TAG, "getGlobalSetting(" + name + ")"); }// Get the value. synchronized (mLock) { //从SettingsState数据数据 return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM, name); } }

public Setting getSettingLocked(int type, int userId, String name) { //获取key final int key = makeKey(type, userId); //从mSettingsStates表中获取SettingsState SettingsState settingsState = peekSettingsStateLocked(key); //从mSettings表中获取setting return settingsState.getSettingLocked(name); }

private SettingsState peekSettingsStateLocked(int key) { SettingsState settingsState = mSettingsStates.get(key); if (settingsState != null) { return settingsState; }ensureSettingsForUserLocked(getUserIdFromKey(key)); return mSettingsStates.get(key); }

public Setting getSettingLocked(String name) { if (TextUtils.isEmpty(name)) { return mNullSetting; } Setting setting = mSettings.get(name); if (setting != null) { return new Setting(setting); } return mNullSetting; }

这里我们就得到了一个Setting数据:
class Setting { private String name; private String value; private String packageName; private String id; public Setting(Setting other) { name = other.name; value = https://www.it610.com/article/other.value; packageName = other.packageName; id = other.id; } ...... }

这个数据是写在文件data/system/users/0/settings_global.xml中,可能是系统默认就有的,也可能是之前写入的。回到call中,这里将获取到的数据返回:
private Bundle packageValueForCallResult(Setting setting, boolean trackingGeneration) { if (!trackingGeneration) { if (setting.isNull()) { return NULL_SETTING_BUNDLE; } return Bundle.forPair(Settings.NameValueTable.VALUE, setting.getValue()); } Bundle result = new Bundle(); result.putString(Settings.NameValueTable.VALUE, !setting.isNull() ? setting.getValue() : null); mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getkey()); return result; }

数据的读取过程分析结束,总结如下:
1.如果要获取的数据已经存在mValues列表中,就直接返回缓存数据
2.如果Settings缓存中不存在,那就通过contentprovider获取远端数据,这个可能是call也可能是使用query,获取到数据后返回,且加入到缓存中。
数据的写入方法逻辑大体相似,这里大胆的猜测流程应该是如下:
1.更新mValues中的缓存数据
2.调用contentprodiver更新远端的缓存数据
3.更新数据文件
查证代码后实际流程如下:
1.并没有更新settings中的缓存mValues,而是直接调用call方法传入命令和数据
2.修改对应SettingsState中的缓存数据mSettings列表,然后跟新xml文件
3.数据修改成功后contentResolver的notifyChange方法发出数据已被修改通知。
这里感觉少了一步修改Settings中的缓存的步骤,这样的话可能就会导致获取出来的数据跟不是最新的数据,但是其实这点谷歌的工程师不可能没考虑到,GenerationTracker就是专门用来确定数据是否有发生过修改的,如果有发生过修改,则在获取数据的最初阶段,就会将本地缓存列表清空,避免获取到的数据不是最新的数据。

    推荐阅读