一、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";
//保存设备的uuid
Settings.Secure.putString(getContentResolver(), LinspirerToolConstant.KEY_DEVICE_UUID, uuid)
//读取设备的uuid
String uuid = Settings.Secure.getString(getContentResolver(), LinspirerToolConstant.KEY_DEVICE_UUID );
获取UUID 的ContentResolver对象
//获取ContentResolver对象
ContentResolver contentResolver = getContentResolver();
//注册监听对应的数据库字段Key
contentResolver.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
<bool name="def_wifi_on">true</bool> <!-- 默认开启 Wi-Fi -->
<integer name="def_screen_off_timeout">60000</integer> <!-- 屏幕超时 60 秒 -->
<string name="def_time_12_24">24</string> 设置时间格式 是12还是24
- 代码入口:DatabaseHelper.java 的 loadSettings() 方法
SettingsProvider的启动过程
frameworks\base\packages\settingsprovider\src\com\android\providers\settings\SettingsProvider.java
运行SettingsProvider,和Activity类似,会回调ContentProvider的生命周期方法,首先的,会调用OnCreate()方法,如下:
@Override
public 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<UserInfo> 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中
@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);
// 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:
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="display_settings_screen"
android:title="@string/display_settings"
settings:keywords="@string/keywords_display">
<PreferenceCategory
android:title="@string/category_name_brightness">
<com.android.settingslib.RestrictedPreference
android:key="brightness"
android:title="@string/brightness"
settings:keywords="@string/keywords_display_brightness_level"
settings:useAdminDisabledSummary="true"
settings:userRestriction="no_config_brightness"/>
<com.android.settingslib.PrimarySwitchPreference
android:key="auto_brightness_entry"
android:title="@string/auto_brightness_title"
android:fragment="com.android.settings.display.AutoBrightnessSettings"
settings:controller="com.android.settings.display.AutoBrightnessPreferenceController"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/category_name_lock_display">
<Preference
android:key="lockscreen_from_display_settings"
android:title="@string/lockscreen_settings_title"
android:fragment="com.android.settings.security.LockscreenDashboardFragment"
settings:keywords="@string/keywords_ambient_display_screen"
settings:controller="com.android.settings.security.screenlock.LockScreenPreferenceController"/>
<com.android.settingslib.RestrictedPreference
android:key="screen_timeout"
android:title="@string/screen_timeout"
android:summary="@string/summary_placeholder"
android:fragment="com.android.settings.display.ScreenTimeoutSettings"
settings:controller="com.android.settings.display.ScreenTimeoutPreferenceController"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/category_name_appearance">
<com.android.settings.display.darkmode.DarkModePreference
android:key="dark_ui_mode"
android:title="@string/dark_ui_mode"
android:fragment="com.android.settings.display.darkmode.DarkModeSettingsFragment"
android:widgetLayout="@null"
settings:widgetLayout="@null"
settings:controller="com.android.settings.display.DarkUIPreferenceController"
settings:keywords="@string/keywords_dark_ui_mode"/>
<Preference
android:fragment="com.android.settings.accessibility.TextReadingPreferenceFragment"
android:key="text_reading_options"
android:persistent="false"
android:title="@string/accessibility_text_reading_options_title"
settings:controller="com.android.settings.accessibility.TextReadingFragmentForDisplaySettingsController"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/category_name_color">
<com.android.settingslib.PrimarySwitchPreference
android:key="night_display"
android:title="@string/night_display_title"
android:fragment="com.android.settings.display.NightDisplaySettings"
settings:controller="com.android.settings.display.NightDisplayPreferenceController"
settings:keywords="@string/keywords_display_night_display"/>
<Preference
android:key="color_mode"
android:title="@string/color_mode_title"
android:fragment="com.android.settings.display.ColorModePreferenceFragment"
settings:controller="com.android.settings.display.ColorModePreferenceController"
settings:keywords="@string/keywords_color_mode"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/category_name_display_controls">
<!--
Standard auto-rotation preference that will be shown when device state based
auto-rotation settings are NOT available.
-->
<SwitchPreference
android:key="auto_rotate"
android:title="@string/accelerometer_title"
settings:keywords="@string/keywords_auto_rotate"
settings:controller="com.android.settings.display.AutoRotatePreferenceController"/>
<!--
Auto-rotation preference that will be shown when device state based auto-rotation
settings are available.
-->
<Preference
android:key="device_state_auto_rotate"
android:title="@string/accelerometer_title"
android:fragment="com.android.settings.display.DeviceStateAutoRotateDetailsFragment"
settings:keywords="@string/keywords_auto_rotate"
settings:controller="com.android.settings.display.DeviceStateAutoRotateOverviewController"/>
<Preference
android:key="screen_resolution"
android:title="@string/screen_resolution_title"
android:fragment="com.android.settings.display.ScreenResolutionFragment"
settings:keywords="@string/keywords_screen_resolution"
settings:controller="com.android.settings.display.ScreenResolutionController"/>
<SwitchPreference
android:key="display_white_balance"
android:title="@string/display_white_balance_title"
android:summary="@string/display_white_balance_summary"
settings:controller="com.android.settings.display.DisplayWhiteBalancePreferenceController"/>
<SwitchPreference
android:key="peak_refresh_rate"
android:title="@string/peak_refresh_rate_title"
android:summary="@string/peak_refresh_rate_summary"
settings:controller="com.android.settings.display.PeakRefreshRatePreferenceController"/>
<SwitchPreference
android:key="show_operator_name"
android:title="@string/show_operator_name_title"
android:summary="@string/show_operator_name_summary"/>
<Preference
android:key="screensaver"
android:title="@string/screensaver_settings_title"
android:fragment="com.android.settings.dream.DreamSettings"
settings:keywords="@string/keywords_screensaver"
settings:controller="com.android.settings.display.ScreenSaverPreferenceController"/>
<SwitchPreference
android:key="camera_gesture"
android:title="@string/camera_gesture_title"
android:summary="@string/camera_gesture_desc"/>
<SwitchPreference
android:key="lift_to_wake"
android:title="@string/lift_to_wake_title"/>
<SwitchPreference
android:key="tap_to_wake"
android:title="@string/tap_to_wake"
android:summary="@string/tap_to_wake_summary"/>
<ListPreference
android:key="theme"
android:title="@string/device_theme"
android:summary="@string/summary_placeholder"/>
<Preference
android:key="vr_display_pref"
android:title="@string/display_vr_pref_title"
android:fragment="com.android.settings.display.VrDisplayPreferencePicker"/>
</PreferenceCategory>
</PreferenceScreen>
所有的菜单项都在这里;
例如:则对应的是自动亮度(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方法。