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定义,之后的数据操作流程就和增删改查方法中的一致。

SettingsProvider

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐