Android Settings 数据库生成、监听与默认值调整
一、Settings 数据库生成机制
- 传统数据库生成(Android 6.0 前)
- 路径:/data/data/com.android.providers.settings/databases/settings.db
- 创建流程:
- SQL 脚本初始化:通过 sqlite 工具创建数据库文件并执行 SQL 脚本定义表结构(如 secure、system、global)
- ADB 推送:生成 .db 文件后,通过 adb push 推送至设备 /data/system/ 目录
- 重启服务:执行 adb shell stop && adb shell start 使配置生效
- 新版 XML 存储(Android 6.0 及以后)
- 数据迁移:首次启动或恢复出厂设置时,若检测到旧数据库,系统会将其迁移至 XML 文件(如 /data/system/users/0/settings_global.xml)
- 文件结构:
- Global:settings_global.xml(全局设置):所有的偏好设置对系统的所有用户公开,第三方APP有读没有写的权限;
- System:settings_system.xml(用户偏好):包含各种各样的用户偏好系统设置,第三方APP有读没有写的权限;
- Secure:settings_secure.xml(安全设置):安全性的用户偏好系统设置,第三方APP有读没有写的权限;
二、数据监听实现
- ContentObserver 机制
- 监听原理:通过注册 ContentObserver 监听特定 URI 的变动(如 Settings.Global.CONTENT_URI)
// 自定义观察者类private class SettingsObserver extends ContentObserver { public SettingsObserver(Handler handler) { super(handler); } @Override public void onChange(boolean selfChange, Uri uri) { // 处理设置变更逻辑(如读取新值并更新 UI) int value = Settings.Global.getInt(getContentResolver(), "key_name", 0); }}// 注册监听Uri uri = Settings.Global.getUriFor("key_name");getContentResolver().registerContentObserver(uri, false, new SettingsObserver(mHandler));
例如 以UUID为例
//首先我们先定义一个数据库的键值Key//比如我们定义一个保存设备uuid的键值为"device_uuid"private static final String KEY_DEVICE_UUID = "device_uuid"; //保存设备的uuidSettings.Secure.putString(getContentResolver(), LinspirerToolConstant.KEY_DEVICE_UUID, uuid) //读取设备的uuidString uuid = Settings.Secure.getString(getContentResolver(), LinspirerToolConstant.KEY_DEVICE_UUID );
获取UUID 的ContentResolver对象
//获取ContentResolver对象ContentResolver contentResolver = getContentResolver();//注册监听对应的数据库字段KeycontentResolver.registerContentObserver(Settings.Secure.getUriFor(LinspirerToolConstant.KEY_SHEEPMIE_UUID),true,new SettingsObserver(null));
自定义ContentObserver类,实现数据变化的回调方法
private final class SettingsObserver extends ContentObserver { /** * Creates a content observer. * @param handler The handler to run {@link #onChange} on, or null if none. */ public SettingsObserver(Handler handler) { super(handler); } @Override public void onChange(boolean selfChange) { try { String uuid = Settings.Secure.getString(getContentResolver(), KEY_DEVICE_UUID); Log.w(" -- uuid被改变啦!!! == " + uuid); } catch (Throwable e) { e.printStackTrace(); } }}
默认值配置详解
- 路径:frameworks/base/packages/SettingsProvider/res/values/defaults.xml
true 60000 24 设置时间格式 是12还是24
- 代码入口:DatabaseHelper.java 的 loadSettings() 方法
SettingsProvider的启动过程
frameworks\base\packages\settingsprovider\src\com\android\providers\settings\SettingsProvider.java
运行SettingsProvider,和Activity类似,会回调ContentProvider的生命周期方法,首先的,会调用OnCreate()方法,如下:
@Overridepublic boolean onCreate() { // 标记当前运行在系统服务进程中,确保设置项的正确处理 Settings.setInSystemServer(); // 同步初始化关键组件,保证线程安全 synchronized (mLock) { // 获取用户管理服务(处理多用户场景) mUserManager = UserManager.get(getContext()); // 获取全局包管理服务(应用安装/权限管理) mPackageManager = AppGlobals.getPackageManager(); // 获取系统配置管理服务(读取/system/etc配置) mSysConfigManager = getContext().getSystemService(SystemConfigManager.class); // 创建后台处理线程(优先级为BACKGROUND) mHandlerThread = new HandlerThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND); mHandlerThread.start(); // 启动线程 // 创建Handler绑定线程的Looper mHandler = new Handler(mHandlerThread.getLooper()); // 初始化设置注册中心(核心数据结构) mSettingsRegistry = new SettingsRegistry(); } // 缓存系统包名和签名(优化后续鉴权性能) SettingsState.cacheSystemPackageNamesAndSystemSignature(getContext()); // 同步处理数据迁移 synchronized (mLock) { // 迁移旧版本设置数据(兼容性处理) mSettingsRegistry.migrateAllLegacySettingsIfNeededLocked(); // 同步SSAID表(AndroidID关联处理) mSettingsRegistry.syncSsaidTableOnStartLocked(); } // 通过Handler异步执行(避免阻塞主线程) mHandler.post(() -> { // 注册广播接收器(监听用户切换/包变更等) registerBroadcastReceivers(); // 监听用户限制变化(如企业策略更新) startWatchingUserRestrictionChanges(); }); // 注册系统服务(核心服务暴露) ServiceManager.addService("settings", new SettingsService(this)); // 设置服务入口 ServiceManager.addService("device_config", new DeviceConfigService(this)); // 设备配置服务 return true; // 初始化成功}
在onCreate方法中,
1.先实例化了HandlerThread的对象mHandlerThread,优先级是THREAD_PRIORITY_BACKGROUND
2.然后实例化SettingsRegistry的实例mSettingsRegistry
3.接下来注册了一个广播接收器
SettingsRegistry的实例化过程
再回到onCreate()中
migrateAllLegacySettingsIfNeededLocked() 中
/** * 迁移所有遗留设置数据(如果需要) * * 该方法负责将旧版SQLite数据库中的设置项迁移到新版XML存储格式。 * 仅在首次启动或检测到未初始化时执行迁移。 * * 同步要求:调用时必须持有mLock锁 */private void migrateAllLegacySettingsIfNeededLocked() { // 检查全局设置文件是否已存在(避免重复迁移) final int key = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM); File globalFile = getSettingsFile(key); if (SettingsState.stateFileExists(globalFile)) { return; // 已迁移则直接返回 } // 记录当前构建ID(用于追踪迁移版本) mSettingsCreationBuildId = Build.ID; // 清除调用者身份(以系统权限执行迁移) final long identity = Binder.clearCallingIdentity(); try { // 获取所有活跃用户(包括多用户场景) List users = mUserManager.getAliveUsers(); // 遍历每个用户执行迁移 final int userCount = users.size(); for (int i = 0; i < userCount; i++) { final int userId = users.get(i).id; // 初始化用户数据库帮助类(访问旧版SQLite) DatabaseHelper dbHelper = new DatabaseHelper(getContext(), userId); SQLiteDatabase database = dbHelper.getWritableDatabase(); // 执行核心迁移逻辑 migrateLegacySettingsForUserLocked(dbHelper, database, userId); // 执行版本升级检查(处理非迁移的增量更新) UpgradeController upgrader = new UpgradeController(userId); upgrader.upgradeIfNeededLocked(); // 清理非活跃用户的内存状态(优化资源使用) if (!mUserManager.isUserRunning(new UserHandle(userId))) { removeUserStateLocked(userId, false); } } } finally { // 恢复原始调用者身份 Binder.restoreCallingIdentity(identity); }}
1.首先通过makeKey获得key,这个key就是之前说过的Global、System、Secure三种类型,
2.获得之后通过getSettingsFile方法创建三种类型的File类型实例:
通过getSettingsFile生成的三种文件分别为:
/data/system/users/{id}/settings_global.xml 存放global
/data/system/users/{id}/settings_system.xml 存放 system
/data/system/users/{id}/settings_secure.xml 存放secure
然后 方法中又实例化了一个dbHelper
dbHelper中
@Overridepublic 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); // Only create the global table for the singleton 'owner/system' user if (mUserHandle == UserHandle.USER_SYSTEM) { createGlobalTable(db); } db.execSQL("CREATE TABLE bluetooth_devices (" + "_id INTEGER PRIMARY KEY," + "name TEXT," + "addr TEXT," + "channel INTEGER," + "type INTEGER" + ");"); db.execSQL("CREATE TABLE bookmarks (" + "_id INTEGER PRIMARY KEY," + "title TEXT," + "folder TEXT," + "intent TEXT," + "shortcut INTEGER," + "ordering INTEGER" + ");"); db.execSQL("CREATE INDEX bookmarksIndex1 ON bookmarks (folder);"); db.execSQL("CREATE INDEX bookmarksIndex2 ON bookmarks (shortcut);"); // Populate bookmarks table with initial bookmarks loadBookmarks(db); // Load initial volume levels into DB loadVolumeLevels(db); // Load inital settings values loadSettings(db);}
其中 通过 db.execSQL分别创建了 system,Secure,Global三个数据库表
然后通过loadVolumeLevels(db);方法 将系统默认的音量设置写到数据库的system的数据表中
LoadVolumeLevels(db):
/** * 加载音频音量默认值到系统设置数据库 * * 该方法用于初始化或重置音频相关的系统设置值,包括各音频流的默认音量级别 * 以及铃声模式影响的音频流配置。这些设置会被存储在系统的SQLite数据库中。 * * @param db 可写的系统设置数据库实例 */private void loadVolumeLevels(SQLiteDatabase db) { // 使用预编译SQL语句提高批量插入效率 SQLiteStatement stmt = null; try { // 创建INSERT OR IGNORE语句(避免重复插入) stmt = db.compileStatement("INSERT OR IGNORE INTO system(name,value) VALUES(?,?);"); // 加载各音频流的默认音量设置(单位:音量等级) loadSetting(stmt, Settings.System.VOLUME_MUSIC, AudioSystem.getDefaultStreamVolume(AudioManager.STREAM_MUSIC)); // 媒体音量 loadSetting(stmt, Settings.System.VOLUME_RING, AudioSystem.getDefaultStreamVolume(AudioManager.STREAM_RING)); // 铃声音量 loadSetting(stmt, Settings.System.VOLUME_SYSTEM, AudioSystem.getDefaultStreamVolume(AudioManager.STREAM_SYSTEM)); // 系统音量 loadSetting(stmt, Settings.System.VOLUME_VOICE, AudioSystem.getDefaultStreamVolume(AudioManager.STREAM_VOICE_CALL)); // 通话音量 loadSetting(stmt, Settings.System.VOLUME_ALARM, AudioSystem.getDefaultStreamVolume(AudioManager.STREAM_ALARM)); // 闹钟音量 loadSetting(stmt, Settings.System.VOLUME_NOTIFICATION, AudioSystem.getDefaultStreamVolume(AudioManager.STREAM_NOTIFICATION));// 通知音量 loadSetting(stmt, Settings.System.VOLUME_BLUETOOTH_SCO, AudioSystem.getDefaultStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO)); // 蓝牙SCO音量 /* * 配置铃声模式影响的音频流(位掩码方式): * - 默认情况下,铃声、通知和系统音量的音频流会受铃声模式影响 * - 在非语音设备(如平板)上,音乐音量也会受铃声模式影响 * - 在语音设备(如手机)上,仅铃声/通知/系统音量受影响 */ int ringerModeAffectedStreams = (1 << AudioManager.STREAM_RING) | (1 << AudioManager.STREAM_NOTIFICATION) | (1 << AudioManager.STREAM_SYSTEM) | (1 << AudioManager.STREAM_SYSTEM_ENFORCED); // 检查设备是否支持语音功能(config_voice_capable) if (!mContext.getResources().getBoolean( com.android.internal.R.bool.config_voice_capable)) { ringerModeAffectedStreams |= (1 << AudioManager.STREAM_MUSIC); // 平板设备增加音乐流 } // 将配置写入数据库 loadSetting(stmt, Settings.System.MODE_RINGER_STREAMS_AFFECTED, ringerModeAffectedStreams); // 加载默认的静音影响流配置(来自AudioSystem常量) loadSetting(stmt, Settings.System.MUTE_STREAMS_AFFECTED, AudioSystem.DEFAULT_MUTE_STREAMS_AFFECTED); } finally { // 确保关闭数据库语句,避免资源泄漏 if (stmt != null) stmt.close(); }}
最后调用loadSettings
到现在 以及加载许多默认值写入数据库中,这些默认值很大一部分定义在defaults.xml文件中,loadVolumlevels和loadSettings()的作用就是在手机第一次启动时,把手机编好设置的默认值写入到数据库settings.db中。
再回到com.android.providers.settings.SettingsProvider.SettingsRegistry.java migrateAllLegacySettingsIfNeededLocked()中
在DatabaseHelper和SQLiteDatabase创建完毕后,调用migrateLegacySettingsForUserLocked方法:
/** * 迁移指定用户的遗留设置数据(从SQLite数据库到XML存储) * * 该方法将系统设置、安全设置和全局设置(仅对主用户)从旧版SQLite数据库迁移到新版XML存储格式。 * 迁移完成后可选择删除或备份旧数据库。 * * @param dbHelper 数据库帮助类实例,提供数据库访问能力 * @param database 可写的SQLite数据库实例 * @param userId 要迁移的用户ID * * 同步要求:调用时必须持有mLock锁 */private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper, SQLiteDatabase database, int userId) { // ==================== 系统设置迁移 ==================== final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId); ensureSettingsStateLocked(systemKey); // 确保系统设置状态已初始化 SettingsState systemSettings = mSettingsStates.get(systemKey); migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM); // 执行实际迁移 systemSettings.persistSyncLocked(); // 立即持久化到XML文件 // ==================== 安全设置迁移 ==================== final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId); ensureSettingsStateLocked(secureKey); // 确保安全设置状态已初始化 SettingsState secureSettings = mSettingsStates.get(secureKey); migrateLegacySettingsLocked(secureSettings, database, TABLE_SECURE); // 执行实际迁移 ensureSecureSettingAndroidIdSetLocked(secureSettings); // 确保Android ID已正确设置 secureSettings.persistSyncLocked(); // 立即持久化到XML文件 // ==================== 全局设置迁移(仅主用户) ==================== 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); // 执行实际迁移 // 如果是新创建的设置文件,记录构建ID(用于追踪迁移版本) if (mSettingsCreationBuildId != null) { globalSettings.insertSettingLocked( Settings.Global.DATABASE_CREATION_BUILDID, mSettingsCreationBuildId, null, true, SettingsState.SYSTEM_PACKAGE_NAME); } globalSettings.persistSyncLocked(); // 立即持久化到XML文件 } // ==================== 旧数据库处理 ==================== if (DROP_DATABASE_ON_MIGRATION) { dbHelper.dropDatabase(); // 配置为true时直接删除旧数据库 } else { dbHelper.backupDatabase(); // 默认行为:备份旧数据库(保留回滚能力) }}
1.首先调用了 ensureSettingsStateLocked(systemKey);
实例化了settingsState对象指向了sysytem的数据文件,然后将settingsState对象放到mSettingsStates中,然后回到migrateLegacySettingsForUserLocked继续调用migrateLegacySettingsLocked方法
2.再调用了 migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);
/** * 执行实际的遗留设置数据迁移(从SQLite表到SettingsState) * * 该方法从指定的SQLite表中读取所有设置项,并将其插入到新的SettingsState存储中。 * 这是设置数据从SQLite迁移到XML格式的核心逻辑。 * * @param settingsState 目标SettingsState实例(系统/安全/全局) * @param database 包含旧设置的SQLite数据库 * @param table 要迁移的源表名(system/secure/global) * * 同步要求: * - 调用时必须持有外部mLock锁 * - SettingsState内部会维护自己的同步机制 */private void migrateLegacySettingsLocked(SettingsState settingsState, SQLiteDatabase database, String table) { // 构建SQL查询(全表扫描) SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); queryBuilder.setTables(table); // 设置查询表名 // 执行查询获取所有设置项(只查询name和value列) Cursor cursor = queryBuilder.query(database, LEGACY_SQL_COLUMNS, null, null, null, null, null); // 空检查 if (cursor == null) { return; // 表不存在或查询失败时提前返回 } try { // 检查是否有数据 if (!cursor.moveToFirst()) { return; // 空表直接返回 } // 获取列索引(优化后续访问性能) final int nameColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.NAME); final int valueColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.VALUE); // 记录数据库版本(用于兼容性管理) settingsState.setVersionLocked(database.getVersion()); // 遍历游标逐条迁移数据 while (!cursor.isAfterLast()) { // 读取设置项键值对 String name = cursor.getString(nameColumnIdx); String value = cursor.getString(valueColumnIdx); // 插入到新的SettingsState存储 settingsState.insertSettingLocked( name, // 设置项名称(如"volume_music") value, // 设置项值(如"11") null, // 默认tag为null true, // makeDefault设为true SettingsState.SYSTEM_PACKAGE_NAME); // 标记为系统设置 cursor.moveToNext(); // 移动到下一条记录 } } finally { // 确保关闭游标释放资源 cursor.close(); }}
先查询出所有System的信息存入游标,然后在循环中作为insertSettingLocked方法的参数
再跳转到insertSettingLocked(String name, String value, String tag,
boolean makeDefault, boolean forceNonSystemPackage, String packageName,
boolean overrideableByRestore)方法中
/** * 插入或更新设置项(线程安全) * * 该方法用于修改设置项的当前值和/或默认值,包含内存管理、冲突检测和日志记录等完整逻辑。 * 是SettingsProvider核心的写操作方法。 * * @param name 设置项名称(如"screen_brightness") * @param value 要设置的新值(如"120") * @param tag 可选标签,用于标记设置来源 * @param makeDefault 是否同时设为默认值 * @param forceNonSystemPackage 强制允许非系统应用修改 * @param packageName 发起修改的包名(用于权限控制) * @param overrideableByRestore 是否允许被系统恢复操作覆盖 * @return 是否成功修改 * * 同步要求:调用时必须持有mLock锁 */@GuardedBy("mLock")public boolean insertSettingLocked(String name, String value, String tag, boolean makeDefault, boolean forceNonSystemPackage, String packageName, boolean overrideableByRestore) { // 1. 参数校验 if (TextUtils.isEmpty(name)) { return false; // 拒绝空key } // 2. 获取旧值状态 Setting oldState = mSettings.get(name); String oldValue = (oldState != null) ? oldState.value : null; String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null; String newDefaultValue = makeDefault ? value : oldDefaultValue; // 3. 内存使用检查(防止恶意应用耗尽内存) int newSize = getNewMemoryUsagePerPackageLocked(packageName, oldValue == null ? name.length() : 0 /* deltaKeySize */, oldValue, value, oldDefaultValue, newDefaultValue); checkNewMemoryUsagePerPackageLocked(packageName, newSize); // 4. 创建/更新设置项 Setting newState; if (oldState != null) { // 4.1 更新现有项(包含权限校验) if (!oldState.update(value, makeDefault, packageName, tag, forceNonSystemPackage, overrideableByRestore)) { return false; // 权限校验失败 } newState = oldState; } else { // 4.2 创建新项 newState = new Setting(name, value, makeDefault, packageName, tag, forceNonSystemPackage); mSettings.put(name, newState); } // 5. 记录统计日志(用于分析设置项变更) FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, name, // 设置项名称 value, // 新值 newState.value, // 实际设置值(可能被修正) oldValue, // 旧值 tag, // 操作标签 makeDefault, // 是否设为默认 getUserIdFromKey(mKey), // 用户ID FrameworkStatsLog.SETTING_CHANGED__REASON__UPDATED); // 操作类型 // 6. 记录历史操作(用于调试和恢复) addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState); // 7. 更新内存计数器 updateMemoryUsagePerPackageLocked(packageName, newSize); // 8. 触发持久化(异步写入磁盘) scheduleWriteIfNeededLocked(); return true;}
将每个设置项放mSetting中。
所以 ensureSettingsStateLocked(systemKey);到 migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);方法
首先创建了指向system的XML数据文件的对象SettingsState(com.android.providers.settings.SettingsState), 该对象中存在mSettings(MAP类型),
这个mSetting中封装了所有设置项name、value、packageName的对象setting。
所以settingState拥有了system的XML数据文件的所有设置项
最后再执行 systemSettings.persistSyncLocked();
1.移除旧的消息:从消息队列中移除 MSG_PERSIST_SETTINGS 消息,防止重复写入。
2.立即写入状态:调用 doWriteState() 立即执行设置的持久化操作。
其中doWriteState() 会将当前settingsState拥有的设置项从内存中序列化写入 XML文件中
然后至此结束 后面的方法 都是secure和global的XML文件的处理 最后 XML数据文件都由 SettingsState通过mSetting这个map持有
总结
在SetttngsProvider的启动过程中,会创建数据库,把默认设置的数据值写入数据库,然后将数据库中的数据全部迁移到xml文件中
然后为了通过Settings.java对使用SettingsProvider进行了封装
settings.java代码创建了三个静态内部类,System,Secure,Global,三个类都继承了NameValueCache,每个NameValueCache都有指向SettingsProvider中的SettingsProvider.java的AIDL远程调用IContentProvider(IContentProvider 是 Android 框架内部定义的 AIDL 接口,用于实现 ContentProvider 的跨进程通信。),因此,查询数据需要经过NameValueCache的getStringForUser()方法,插入数据需要经过putStringForUser()方法。同时,NameValueCache还持有一个变量mValues,用于保存查询过的设置项,以便下下次再次发起查询时,能够快速返回。
自动调节亮度为例子:
其对应的Fragment是:DisplaySettings.java;
加载布局:display_settings.xml:
所有的菜单项都在这里;
例如:则对应的是自动亮度(auto_brightness)选项:
com.android.settings.DisplaySettings中
获取该项对应的controller对象并将它添加到controllers中并返回;
该方法返回buildPreferenceControllers():
com.android.settings.display.AutoBrightnessPreferenceController中
状态读取(isChecked):从系统设置读取当前亮度模式,若与默认值不同(即自动模式已启用),返回 true。若没有存储该值,就返回 DEFAULT_VALUE(手动模式)。
状态写入(setChecked):根据开关状态,向系统设置写入自动 (1) 或手动 (0) 模式。
获取和修改的实质是对SettingsProvider的操作
设备的亮度模式SCREEN_BRIGHTNESS_MODE_AUTOMATIC(自动)和SCREEN_BRIGHTNESS_MODE_MANUAL(手动)都是通过SettingsProvider来操作的,可以看到自动亮度设置属于System系统属性
由于Settings.java对使用SettingsProvider进行了封装,所以,使用起来相当简单简洁
public static int getInt(ContentResolver cr, String name, int def) { return getIntForUser(cr, name, def, UserHandle.myUserId());}
在getIntForUser方法中又调用了getStringForUser方法。