Android 12系统源码_系统设置(二)Setting 保存数据分析
1、Settings类分析
在Settings中可以看到,getInt最终实现是通过ContentProvider的query方法去查询数据,putInt方法同理。mProviderHolder通过NameValueCache构造函数传入,uri为"content://settings/system"。mCallGetCommand为CALL_METHOD_GET_GLOBAL,调用ContentProvider的call方法。mContentProvider是authority为settings的ContentProvider。这里其实就知道为啥跟SettingsProvider相关联了。
因为在SettingsProvider中,定义了一个SettingsProvider,authority为settings。
继续分析下Settings类,可以发现它只能保存int,float,string等基本类型的数据,同时以键值对的形式保存,Settings中定义了大量的设置项KEY。其次除了System类外还有Global,Secure,Config,Bookmarks类分别构造了不同URI操作数据。因为Settings对数据进行了分类。
- System 包含各种系统设置。
- Global 包含各种对用户公开的系统设置,第三方应用程序可以读取,不可以写入。
- Secure 包含各种安全系统设置。第三方应用程序可以读取,不可以写入。
- Config 配置系统设置。只有Android可以读取,特定的配置服务可以写入。
- Bookmarks。用户定义的书签和快捷方式。 每个书签的目标是一个 Intent URL,允许它是网页或特定的应用程序活动。
2、Settings.Global的putStringForUser方法
这里我们拿Global来做下存储链路分析
public final class Settings {
public static final class Global extends NameValueTable {
private static final HashSet<String> MOVED_TO_SECURE;
private static final HashSet<String> MOVED_TO_SYSTEM;
public static boolean putString(ContentResolver resolver,
String name, String value) {
return putStringForUser(resolver, name, value, null, false, resolver.getUserId(),
DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
public static boolean putStringForUser(@NonNull ContentResolver resolver,
@NonNull String name, @Nullable String value, @Nullable String tag,
boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore) {
if (MOVED_TO_SECURE.contains(name)) {
return Secure.putStringForUser(resolver, name, value, tag,
makeDefault, userHandle, overrideableByRestore);
}
if (MOVED_TO_SYSTEM.contains(name)) {
return System.putStringForUser(resolver, name, value, tag,
makeDefault, userHandle, overrideableByRestore);
}
//继续调用NameValueCache的putStringForUser方法
return sNameValueCache.putStringForUser(resolver, name, value, tag,
}
}
}
保存数据是,先判断设置的数据key值是否在MOVED_TO_SECURE、MOVED_TO_GLOBAL或者MOVED_TO_SECURE_THEN_GLOBAL数组中,如果在则直接返回false.如果存在则调用sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle)保存数据。
3、Settings.NameValueCache的putStringForUser方法
public final class Settings {
public static final String AUTHORITY = "settings";
public static final class Global extends NameValueTable {
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global");
private static final NameValueCache sNameValueCache = new NameValueCache(
CONTENT_URI,
CALL_METHOD_GET_GLOBAL,
CALL_METHOD_PUT_GLOBAL,
CALL_METHOD_DELETE_GLOBAL,
sProviderHolder,
Global.class);
}
private static class NameValueCache {
private final ContentProviderHolder mProviderHolder;
private <T extends NameValueTable> NameValueCache(Uri uri, String getCommand,
String setCommand, String listCommand, String setAllCommand,
ContentProviderHolder providerHolder, Class<T> callerClass) {
mUri = uri;
mCallGetCommand = getCommand;
mCallSetCommand = setCommand;
mCallListCommand = listCommand;
mCallSetAllCommand = setAllCommand;
mProviderHolder = providerHolder;
mReadableFields = new ArraySet<>();
mAllFields = new ArraySet<>();
mReadableFieldsWithMaxTargetSdk = new ArrayMap<>();
getPublicSettingsForClass(callerClass, mAllFields, mReadableFields,
mReadableFieldsWithMaxTargetSdk);
}
public boolean putStringForUser(ContentResolver cr, String name, String value,
String tag, boolean makeDefault, final int userHandle,
boolean overrideableByRestore) {
try {
Bundle arg = new Bundle();
arg.putString(Settings.NameValueTable.VALUE, value);
arg.putInt(CALL_METHOD_USER_KEY, userHandle);
if (tag != null) {
arg.putString(CALL_METHOD_TAG_KEY, tag);
}
if (makeDefault) {
arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
}
if (overrideableByRestore) {
arg.putBoolean(CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY, true);
}
//调用ContentProviderHolder的getProvider方法
IContentProvider cp = mProviderHolder.getProvider(cr);
//调用IContentProvider的call方法,这里调用的其实是
cp.call(cr.getAttributionSource(),
mProviderHolder.mUri.getAuthority(), mCallSetCommand, name, arg);
} catch (RemoteException e) {
Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
return false;
}
return true;
}
}
private static final class ContentProviderHolder {
private final Object mLock = new Object();
private final Uri mUri;
private IContentProvider mContentProvider;
public ContentProviderHolder(Uri uri) {
mUri = uri;
}
public IContentProvider getProvider(ContentResolver contentResolver) {
synchronized (mLock) {
if (mContentProvider == null) {
//这里得到的其实是SettingsProvider
mContentProvider = contentResolver.acquireProvider(mUri.getAuthority());
}
return mContentProvider;
}
}
public void clearProviderForTest() {
synchronized (mLock) {
mContentProvider = null;
}
}
}
}
4 SettingsProvider模块分析
在源码中搜索android:authorities="settings"关键字,得到以下信息:
./frameworks/base/packages/SettingsProvider/AndroidManifest.xml:18: android:authorities=“settings”
完整代码如下所示。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.providers.settings"
coreApp="true"
android:sharedUserId="android.uid.system">
<provider android:name="SettingsProvider"
android:authorities="settings"
android:multiprocess="false"
android:exported="true"
android:singleUser="true"
android:initOrder="100"
android:visibleToInstantApps="true" />
<service
android:name="WriteFallbackSettingsFilesJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/>
</application>
</manifest>
SettingsProvider模块源码为frameworks/base/packages/SettingsProvider,模块名为SettingsProvider,包名为com.android.providers.settings,Manifest中定义了authority为settings的ContentProvider,name为SettingsProvider。对应的类代码如下。
public class SettingsProvider extends ContentProvider {
@Override
public boolean onCreate() {
...
synchronized (mLock) {
//迁移处理
mSettingsRegistry.migrateAllLegacySettingsIfNeededLocked();
mSettingsRegistry.syncSsaidTableOnStartLocked();
}
mHandler.post(() -> {
//用户移除停止广播注册
registerBroadcastReceivers();
//用户限制变更监听
startWatchingUserRestrictionChanges();
});
ServiceManager.addService("settings", new SettingsService(this));
ServiceManager.addService("device_config", new DeviceConfigService(this));
return true;
}
}
SettingsProvider在onCreate方法中有迁移处理,用户相关监听,同时为ServiceManager添加了两个服务SettingsService,DeviceConfigService。
5、SettingsService服务支持adb shell命令
SettingsService类重写了onShellCommand方法,这使得它可以来处理adb shell 命令。
final public class SettingsService extends Binder {
final SettingsProvider mProvider;
public SettingsService(SettingsProvider provider) {
mProvider = provider;
}
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
(new MyShellCommand(mProvider, false)).exec(
this, in, out, err, args, callback, resultReceiver);
}
}
通过adb执行指令adb shell settings,可以得到如下内容
Settings provider (settings) commands:
help
Print this help text.
get [--user <USER_ID> | current] NAMESPACE KEY
Retrieve the current value of KEY.
put [--user <USER_ID> | current] NAMESPACE KEY VALUE [TAG] [default]
Change the contents of KEY to VALUE.
TAG to associate with the setting.
{default} to set as the default, case-insensitive only for global/secure namespace
delete [--user <USER_ID> | current] NAMESPACE KEY
Delete the entry for KEY.
reset [--user <USER_ID> | current] NAMESPACE {PACKAGE_NAME | RESET_MODE}
Reset the global/secure table for a package with mode.
RESET_MODE is one of {untrusted_defaults, untrusted_clear, trusted_defaults}, case-insensitive
list [--user <USER_ID> | current] NAMESPACE
Print all defined keys.
NAMESPACE is one of {system, secure, global}, case-insensitive
6 SettingsProvider如何插入数据
分析了SettingsProvider的onCreate方法后,再看下insert方法是如何插入数据的。
public class SettingsProvider extends ContentProvider {
public static final String TABLE_SYSTEM = "system";
@Override
public Uri insert(Uri uri, ContentValues values) {
...
String table = getValidTableOrThrow(uri);
switch (table) {
case TABLE_GLOBAL: {
if (insertGlobalSetting(name, value, null, false,
UserHandle.getCallingUserId(), false, /* overrideableByRestore */ false)) {
return Uri.withAppendedPath(Settings.Global.CONTENT_URI, name);
}
} break;
}
}
private boolean insertGlobalSetting(String name, String value, String tag,
boolean makeDefault, int requestingUserId, boolean forceNotify,
boolean overrideableByRestore) {
return mutateGlobalSetting(name, value, tag, makeDefault, requestingUserId,
MUTATION_OPERATION_INSERT, forceNotify, 0, overrideableByRestore);
}
private boolean mutateGlobalSetting(String name, String value, String tag,
boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
int mode, boolean overrideableByRestore) {
switch (operation) {
//插入操作
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL,
UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
callingPackage, forceNotify,
CRITICAL_GLOBAL_SETTINGS, overrideableByRestore);
}
}
}
final class SettingsRegistry {
private static final String SETTINGS_FILE_GLOBAL = "settings_global.xml";
public boolean insertSettingLocked(int type, int userId, String name, String value,
String tag, boolean makeDefault, String packageName, boolean forceNotify,
Set<String> criticalSettings, boolean overrideableByRestore) {
return insertSettingLocked(type, userId, name, value, tag, makeDefault, false,
packageName, forceNotify, criticalSettings, overrideableByRestore);
}
public boolean insertSettingLocked(int type, int userId, String name, String value,
String tag, boolean makeDefault, boolean forceNonSystemPackage, String packageName,
boolean forceNotify, Set<String> criticalSettings, boolean overrideableByRestore) {
...
SettingsState settingsState = peekSettingsStateLocked(key);
if (settingsState != null) {
success = settingsState.insertSettingLocked(name, value,
tag, makeDefault, forceNonSystemPackage, packageName, overrideableByRestore);
}
}
@Nullable
private SettingsState peekSettingsStateLocked(int key) {
...
if (!ensureSettingsForUserLocked(getUserIdFromKey(key))) {
return null;
}
return mSettingsStates.get(key);
}
public boolean ensureSettingsForUserLocked(int userId) {
...
if (userId == UserHandle.USER_SYSTEM) {
final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
ensureSettingsStateLocked(globalKey);
}
}
private void ensureSettingsStateLocked(int key) {
...
if (mSettingsStates.get(key) == null) {
final int maxBytesPerPackage = getMaxBytesPerPackageForType(getTypeFromKey(key));
SettingsState settingsState = new SettingsState(getContext(), mLock,
getSettingsFile(key), key, maxBytesPerPackage, mHandlerThread.getLooper());
mSettingsStates.put(key, settingsState);
}
}
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);
...
}
}
}
}
insert方法从uri取出table,对应不同uri为system/global/secure等。以插入global数据为例分析,System,Global等实现类似。operation来判断是增删改查那种操作,通过SettingsState类的insertSettingLocked方法来进行插入操作,而SettingsState是通过ensureSettingsStateLocked方法创建的,然后保存到mSettingsStates中。
7 SettingsProvider如何插入数据
final class SettingsState {
private final ArrayMap<String, Setting> mSettings = new ArrayMap<>();
public boolean insertSettingLocked(String name, String value, String tag,
boolean makeDefault, boolean forceNonSystemPackage, String packageName,
boolean overrideableByRestore) {
...
mSettings.put(name, newState);
scheduleWriteIfNeededLocked();
}
private void scheduleWriteIfNeededLocked() {
...
writeStateAsyncLocked();
}
private void writeStateAsyncLocked() {
...
Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS);
mHandler.sendMessageDelayed(message, writeDelayMillis);
}
private final class MyHandler extends Handler {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_PERSIST_SETTINGS: {
Runnable callback = (Runnable) message.obj;
doWriteState();
if (callback != null) {
callback.run();
}
}
private void doWriteState() {
synchronized (mLock) {
version = mVersion;
settings = new ArrayMap<>(mSettings);
namespaceBannedHashes = new ArrayMap<>(mNamespaceBannedHashes);
mDirty = false;
mWriteScheduled = false;
}
synchronized (mWriteLock){
AtomicFile destination = new AtomicFile(mStatePersistFile, mStatePersistTag);
FileOutputStream out = null;
try {
out = destination.startWrite();
TypedXmlSerializer serializer = Xml.resolveSerializer(out);
serializer.startDocument(null, true);
serializer.startTag(null, TAG_SETTINGS);
serializer.attributeInt(null, ATTR_VERSION, version);
final int settingCount = settings.size();
for (int i = 0; i < settingCount; i++) {
Setting setting = settings.valueAt(i);
if (setting.isTransient()) {
if (DEBUG_PERSISTENCE) {
Slog.i(LOG_TAG, "[SKIPPED PERSISTING]" + setting.getName());
}
continue;
}
if (writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
setting.getValue(), setting.getDefaultValue(), setting.getPackageName(),
setting.getTag(), setting.isDefaultFromSystem(),
setting.isValuePreservedInRestore())) {
}
}
}
serializer.endTag(null, TAG_SETTINGS);
serializer.startTag(null, TAG_NAMESPACE_HASHES);
for (int i = 0; i < namespaceBannedHashes.size(); i++) {
String namespace = namespaceBannedHashes.keyAt(i);
String bannedHash = namespaceBannedHashes.get(namespace);
if (writeSingleNamespaceHash(serializer, namespace, bannedHash)) {
}
}
}
serializer.endTag(null, TAG_NAMESPACE_HASHES);
serializer.endDocument();
destination.finishWrite(out);
}
}
SettingsState类的insertSettingLocked方法,先将数据保存到mSettings,创建了一个Handler延时加锁进行写数据操作,核心写数据操作在doWriteState方法里。mStatePersistFile是从SettingsState传递过来的,由创建SettingsState的ensureSettingsStateLocked方法可知,通过getSettingsFile创建mStatePersistFile,文件路径为用户系统目录(/data/system/users/0/),文件名为settings_global.xml,然后在xml中进行写数据。
<settings version="213">
<setting id="127" name="adb_wifi_enabled" value="0" package="android" defaultValue="0" defaultSysSet="true" />
<setting id="44" name="low_battery_sound_timeout" value="0" package="android" defaultValue="0" defaultSysSet="true" />
<setting id="95" name="wear_os_version_string" value="" package="android" defaultValue="" defaultSysSet="true" />
...
</settings>
查看时可能乱码,这是因为Android保存的xml文件使用的是一种二进制格式,通过以下命令修改:
adb shell setprop persist.sys.binary_xml false
xml配置文件的格式就变为ASCII 码格式文件,就不会乱码可以正常查看了。
对于其它的query,update,delete方法,也不需赘述了,都是对mSettings进行操作,根据mSettings变化重新写入xml。核心实现都在SettingsState类中,通过锁来确保多个修改以原子方式持久保存到内存和磁盘中。
再看下call方法,前面Settings类中getStringForUser方法就调用了call方法去获取数据。method是区分各种类型数据操作的,不同类型数据操作有不同的method定义,之后的数据操作流程就和增删改查方法中的一致。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)