• SettingsProvider


    Android Settings 系列文章:

    首语

    为啥要聊到这个模块呢?因为Settings里存在大量的设置项,这些设置项的状态需要保存,它们就是通过SettingsProvider来处理的。以状态栏显示电量百分比菜单为例(Battery->Battery percentage),分析下它的状态保存。
    本文以Android 13 SettingsProvider源码进行分析。

    Settings模块调用

    这个菜单的核心实现在BatteryPercentagePreferenceController.java中,可以发现菜单的状态保存实现在Settings类中,状态读取通过getInt方法,状态保存通过putInt方法,

    public class BatteryPercentagePreferenceController extends BasePreferenceController implements
            PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
         @Override
        public void updateState(Preference preference) {
            //菜单状态保存读取
            int setting = Settings.System.getInt(mContext.getContentResolver(),
                    SHOW_BATTERY_PERCENT, 0);
    
            ((SwitchPreference) preference).setChecked(setting == 1);
        }
    
        @Override
        public boolean onPreferenceChange(Preference preference, Object newValue) {
            boolean showPercentage = (Boolean) newValue;
            //菜单状态保存
            Settings.System.putInt(mContext.getContentResolver(), SHOW_BATTERY_PERCENT,
                    showPercentage ? 1 : 0);
            FeatureFactory.getFactory(mContext).getMetricsFeatureProvider()
                    .action(mContext, SettingsEnums.OPEN_BATTERY_PERCENTAGE, showPercentage);
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    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,允许它是网页或特定的应用程序活动。

    修改数据需要权限:

    • android.permission.WRITE_SETTINGS
    • android.permission.WRITE_SECURE_SETTINGS
    public final class Settings {
      	public static final class System extends NameValueTable {
            public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/system");
     		public static int getInt(ContentResolver cr, String name, int def) {
                return getIntForUser(cr, name, def, cr.getUserId());
            }
    
            /** @hide */
            @UnsupportedAppUsage
            public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
                String v = getStringForUser(cr, name, userHandle);
                return parseIntSettingWithDefault(v, def);
            }  
            public static String getStringForUser(ContentResolver resolver, String name,
                    int userHandle) {
                return sNameValueCache.getStringForUser(resolver, name, userHandle);
            }
        }
        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);
        }
        public static final class Secure extends NameValueTable {
            public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/secure");                
        } 
        public static final class Config extends NameValueTable {
             public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/config");                       
        }    
        private static class NameValueCache {
             <T extends NameValueTable> NameValueCache(Uri uri, String getCommand,
                    String setCommand, String deleteCommand, ContentProviderHolder providerHolder,
                    Class<T> callerClass) {
                this(uri, getCommand, setCommand, deleteCommand, null, null, providerHolder,
                        callerClass);
            }
        	public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
           		 IContentProvider cp = mProviderHolder.getProvider(cr);
            	...
                if (mCallGetCommand != null) {
                    b = cp.call(cr.getAttributionSource(),
                                        mProviderHolder.mUri.getAuthority(), mCallGetCommand, name,
                                        args);
                     String value = b.getString(Settings.NameValueTable.VALUE);
                    return value;
                }
            	if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) {
                        final long token = Binder.clearCallingIdentity();
                        try {
                            c = cp.query(cr.getAttributionSource(), mUri,
                                    SELECT_VALUE_PROJECTION, queryArgs, null);
                        } finally {
                            Binder.restoreCallingIdentity(token);
                        }
                    } else {
                        c = cp.query(cr.getAttributionSource(), mUri,
                                SELECT_VALUE_PROJECTION, queryArgs, null);
                    }
            ...
        }
            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) {
                        mContentProvider = contentResolver
                                .acquireProvider(mUri.getAuthority());
                    }
                    return mContentProvider;
                }
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    SettingsProvider模块分析

    SettingsProvider模块源码为frameworks/base/packages/SettingsProvider/,模块名为SettingsProvider,包名为com.android.providers.settings,Manifest中定义了authority为settings的ContentProvider。

    <provider android:name="SettingsProvider"
                      android:authorities="settings"
                      android:multiprocess="false"
                      android:exported="true"
                      android:singleUser="true"
                      android:initOrder="100"
                      android:visibleToInstantApps="true" />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    查看下SettingsProvider的实现,首先在onCreate方法中有迁移处理,用户相关监听,添加了两个服务SettingsService,DeviceConfigService。

    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));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    SettingsService类重写onShellCommand方法来处理adb shell 命令。

    final public class SettingsService extends Binder {
        @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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    执行 adb shell settings,打印了以下command使用信息。可以使用这些命令快速进行数据操作。

    Settings provider (settings) commands:
      help
          Print this help text.
      get [--user  | current] NAMESPACE KEY
          Retrieve the current value of KEY.
      put [--user  | 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  | current] NAMESPACE KEY
          Delete the entry for KEY.
      reset [--user  | 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  | current] NAMESPACE
          Print all defined keys.
          NAMESPACE is one of {system, secure, global}, case-insensitive
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    SettingsService进行了adb shell命令的扩展,让我们操作数据更加方便。DeviceConfigService同理,通过adb shell device_config查看command信息。

    分析了SettingsProvider的onCreate方法后,再看下insert方法是如何插入数据的,它从uri取出table,对应不同uri为system/global/secure等。以插入global数据为例分析,System,Global等实现类似。operation来判断是增删改查那种操作,通过SettingsState类的insertSettingLocked方法来进行插入操作,而SettingsState是通过ensureSettingsStateLocked方法创建的,然后保存到mSettingsStates中。

    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);
                    ...
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92

    继续分析SettingsState类的insertSettingLocked方法,先将数据保存到mSettings,创建了一个Handler延时加锁进行写数据操作,核心写数据操作在doWriteState方法里。mStatePersistFile是从SettingsState传递过来的,由创建SettingsState的ensureSettingsStateLocked方法可知,通过getSettingsFile创建mStatePersistFile,文件路径为用户系统目录(/data/system/users/0/),文件名为settings_global.xml,然后在xml中进行写数据。

    final class SettingsState {
    
    	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);
        }
                
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    到这里,才知道数据是保存在xml文件中的,而并非数据库里。Global类型数据保存在settings_global.xml中,System类型数据保存在settings_system.xml中,Secure类型数据保存在settings_secure.xml中,都在用户系统目录(/data/system/users/0/)下保存,截取部分内容如下:

    <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>    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    查看时可能乱码,这是因为Android13保存的xml文件使用的是一种二进制格式,通过以下命令修改:

    adb shell setprop persist.sys.binary_xml false
    
    • 1

    xml配置文件的格式就变为ASCII 码格式文件,就不会乱码可以正常查看了。

    对于其它的query,update,delete方法,也不需赘述了,都是对mSettings进行操作,根据mSettings变化重新写入xml。核心实现都在SettingsState类中,通过锁来确保多个修改以原子方式持久保存到内存和磁盘中。

    再看下call方法,前面Settings类中getStringForUser方法就调用了call方法去获取数据。method是区分各种类型数据操作的,不同类型数据操作有不同的method定义,之后的数据操作流程就和增删改查方法中的一致。

    public class SettingsProvider extends ContentProvider {
        @Override
        public Bundle call(String method, String name, Bundle args) {
            case Settings.CALL_METHOD_GET_GLOBAL: {
                    Setting setting = getGlobalSetting(name);
                    return packageValueForCallResult(setting, isTrackingGeneration(args));
                }
            case Settings.CALL_METHOD_PUT_GLOBAL: {
                    String value = getSettingValue(args);
                    String tag = getSettingTag(args);
                    final boolean makeDefault = getSettingMakeDefault(args);
                    final boolean overrideableByRestore = getSettingOverrideableByRestore(args);
                    insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false,
                            overrideableByRestore);
                    break;
                }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    对SettingsProvider的基本方法分析以后,我们分析下数据迁移方法migrateLegacySettingsForUserIfNeededLocked,它在onCreate方法中调用。通过DatabaseHelper类获取数据库实例来操作数据库,在TABLE_GLOBAL表内查询name 、value列,然后通过SettingsState的insertSettingLocked方法将数据插入到xml,插入完成后删除数据库。

    public class SettingsProvider extends ContentProvider {
        private static final boolean DROP_DATABASE_ON_MIGRATION = true;
        public static final String TABLE_GLOBAL = "global";
        private void migrateLegacySettingsForUserIfNeededLocked(int userId) {
                // Every user has secure settings and if no file we need to migrate.
                final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
                File secureFile = getSettingsFile(secureKey);
                if (SettingsState.stateFileExists(secureFile)) {
                    return;
                }
                DatabaseHelper dbHelper = new DatabaseHelper(getContext(), userId);
                SQLiteDatabase database = dbHelper.getWritableDatabase();
    
                migrateLegacySettingsForUserLocked(dbHelper, database, userId);
            }
        private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper,
                    SQLiteDatabase database, int userId) {
            ...
            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 this was just created
                    if (mSettingsCreationBuildId != null) {
                        globalSettings.insertSettingLocked(Settings.Global.DATABASE_CREATION_BUILDID,
                                mSettingsCreationBuildId, null, true,
                                SettingsState.SYSTEM_PACKAGE_NAME);
                    }
                    globalSettings.persistSyncLocked();
                }
    
                // 已经迁移,删除数据库
                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, LEGACY_SQL_COLUMNS,null, null, null, null, null);
                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 = cursor.getString(valueColumnIdx);
                        //插入数据到xml
                        settingsState.insertSettingLocked(name, value, null, true,
                                SettingsState.SYSTEM_PACKAGE_NAME);
                        cursor.moveToNext();
                    }
                } finally {
                    cursor.close();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    那看下DatabaseHelper实现,数据库名为settings.db,onCreate方法中创建了多张表,还是以Global为例,其它同理。在global表插入数据,KEY一般都是在Settings中定义,VALUE则一般都是本地资源。给这些KEY对应的设置项添加了初始值。可以在res/values/defaults.xml文件中看到定义了大量菜单的初始值。

    class DatabaseHelper extends SQLiteOpenHelper {
        private static final String DATABASE_NAME = "settings.db";
        @Override
        public void onCreate(SQLiteDatabase db) {
            //创建表
            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);");
    		
            createSecureTable(db);
            ...
            //加载数据
            // Load initial volume levels into DB
            loadVolumeLevels(db);
    
            // Load inital settings values
            loadSettings(db);
        }
        private void loadSettings(SQLiteDatabase db) {
            loadSystemSettings(db);
            loadSecureSettings(db);
            // The global table only exists for the 'owner/system' user
            if (mUserHandle == UserHandle.USER_SYSTEM) {
                loadGlobalSettings(db);
            }
        }
        private void loadGlobalSettings(SQLiteDatabase db) {
            SQLiteStatement stmt = null;
            final Resources res = mContext.getResources();
            try {
               	//插入sql
                stmt = db.compileStatement("INSERT OR IGNORE INTO global(name,value)"
                        + " VALUES(?,?);");
                loadBooleanSetting(stmt, Settings.Global.AIRPLANE_MODE_ON,
                        R.bool.def_airplane_mode_on);
     			loadStringSetting(stmt, Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
                        R.string.airplane_mode_toggleable_radios);
                loadIntegerSetting(stmt, Settings.Global.WIFI_SLEEP_POLICY,
                        R.integer.def_wifi_sleep_policy);
                ...
        }
         private void loadBooleanSetting(SQLiteStatement stmt, String key, int resid) {
            loadSetting(stmt, key,
                    mContext.getResources().getBoolean(resid) ? "1" : "0");
        } 
         private void loadSetting(SQLiteStatement stmt, String key, Object value) {
            stmt.bindString(1, key);
            stmt.bindString(2, value.toString());
            //执行sql
            stmt.execute();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    其它源码就是关于升级和备份相关的,升级大致逻辑是由于DatabaseHelper类已经废弃,118版本之前的升级在SettingsProvider侧,118之后的升级移到SettingsProvider下,有UpgradeController类的upgradeIfNeededLocked方法。因此后续的升级步骤不要在DatabaseHelper下添加。

    整理下SettingsProvider的流程,Settings.db初始化往表里添加大量数据,然后从Settings.db将数据迁移到到不同类型(Global/System/Secure)数据的xml中,最后删除数据库。

    总结

    SettingsProvider 模块使用 ContentProvider 的方式来管理和访问设置数据。它提供了一组标准的 URI用于访问不同类型的设置信息。通过使用这些 URI,应用程序可以读取、写入和监听设置的变化。

    通过与 SettingsProvider 模块交互,Settings等应用程序和系统组件可以轻松地管理设备的各种设置,为用户提供更好的个性化和控制体验。

  • 相关阅读:
    时序预测 | MATLAB实现POA-CNN-GRU鹈鹕算法优化卷积门控循环单元时间序列预测
    时间轴、流程类时间轴绘制
    模拟epoll的饥饿场景
    内置属性-top栏切换
    【CSDN|每日一练】求最小元素
    vscode不显示横滚动条处理
    Java JVM 运行机制及基本原理
    叶子数和深度
    如何选择最适转录本序列构建过表达质粒
    【毕业季|进击的技术er】作为一名职场人,精心总结的嵌入式学习路线图
  • 原文地址:https://blog.csdn.net/yang_study_first/article/details/134001083