SettingsProvider

本文基于Android 13 SettingsProvider源码,分析Settings模块调用、Settings类和SettingsProvider模块。Settings通过SettingsProvider处理设置项状态保存,SettingsProvider以ContentProvider方式管理和访问设置数据,数据保存在xml文件中,还涉及数据迁移、升级等操作,方便应用和系统组件管理设备设置。
该文章已生成可运行项目,

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;
    }
}

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;
            }
        }
}

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" />

查看下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));
    }
}

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);
    }
}

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

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

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);
                ...
            }
        }
    }
}

继续分析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);
    }
            
}

到这里,才知道数据是保存在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>    

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

adb shell setprop persist.sys.binary_xml false

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;
            }
    }
}

对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();
            }
        }
    }
}

那看下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();
    }
}

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

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

总结

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

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

本文章已经生成可运行项目
### Property Get Boolean Function Usage In programming contexts, especially within environments such as Android development or certain C/C++ libraries, `property_get_bool` serves a specific utility related to retrieving boolean values from system properties or configurations. Although direct details about this exact function are not provided in the given context[^1], understanding can be extrapolated based on common practices. The typical signature of a `property_get_bool` method would involve taking at least one argument representing the key of the property intended to fetch and possibly an optional default value should the specified property not exist. The outcome is generally a boolean (`true`/`false`) indicating whether that particular configuration flag or setting has been enabled or disabled. For instance, when working with systems where dynamic adjustments through runtime settings play crucial roles—such as adjusting debugging levels, enabling/disabling features—the use of such functions streamlines access without hardcoding these parameters into applications directly. #### Example Implementation Context Given how similar utilities operate across various platforms: ```c++ bool property_get_bool(const char *key, bool default_value) { // Assume internal logic here interacts with underlying storage mechanism. } ``` This snippet demonstrates what might occur under-the-hood but does not represent actual implementation code found verbatim in any standard library. Instead, it illustrates expected behavior patterns associated with querying boolean-typed data points stored externally relative to application binaries.
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八归少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值