文章目录
- 一、SettingsProvider 的概述
- 二、SettingsProvider 的启动流程
- 三、对 SettingsProvider 进行操作方法
- 四、客制化示例
一、SettingsProvider 的概述
SettingsProvider 是一个为 Android 系统设置提供数据共享的 Provider,它包含全局、安全和系统级别的用户偏好设置。并且,它是跟随 framework 一起编译,以apk的形式内置到系统的应用程序,所以源码的位置在:
frameworks\base\packages\SettingsProvider
数据分类:
SettingsProvider 对数据进行了分类,主要分为 Global、System 和 Secure 三种类型:
- Global:全局偏好设置 ,对系统中所有用户公开;
- System:系统偏好设置
- Secure:安全偏好设置。
此外,为了方便对数据的操作,系统对 SettingsProvider 的一些接口进行封装处理。在 Settings 类中,分别声明了 Global、Secure、System 三个静态内部类,分别对应上述的三种数据类型。通过调用 这三个内部类中定义好的方法及属性,就可以对系统设置的数据进行相应的操作。 Settings 类的路径在:
frameworks\base\core\java\android\provider\Settings.java
下面是它的部分代码:
/*** The Settings provider contains global system-level device preferences.*/
public final class Settings {…… //内部类 Systempublic static final class System extends NameValueTable {private static final float DEFAULT_FONT_SCALE = 1.0f;public static String getString(ContentResolver resolver, String name) {return getStringForUser(resolver, name, resolver.getUserId());}public static String getStringForUser(ContentResolver resolver, String name,int userHandle) {if (MOVED_TO_SECURE.contains(name)) {Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"+ " to android.provider.Settings.Secure, returning read-only value.");return Secure.getStringForUser(resolver, name, userHandle);}if (MOVED_TO_GLOBAL.contains(name) || MOVED_TO_SECURE_THEN_GLOBAL.contains(name)) {Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"+ " to android.provider.Settings.Global, returning read-only value.");return Global.getStringForUser(resolver, name, userHandle);}return sNameValueCache.getStringForUser(resolver, name, userHandle);}public static boolean putString(ContentResolver resolver, String name, String value) {return putStringForUser(resolver, name, value, resolver.getUserId());}public static boolean putStringForUser(ContentResolver resolver, String name, String value,int userHandle) {if (MOVED_TO_SECURE.contains(name)) {Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"+ " to android.provider.Settings.Secure, value is unchanged.");return false;}if (MOVED_TO_GLOBAL.contains(name) || MOVED_TO_SECURE_THEN_GLOBAL.contains(name)) {Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"+ " to android.provider.Settings.Global, value is unchanged.");return false;}return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle);}……}……//内部类 Securepublic static final class Secure extends NameValueTable {public static final Uri CONTENT_URI =Uri.parse("content://" + AUTHORITY + "/secure");@UnsupportedAppUsageprivate static final ContentProviderHolder sProviderHolder =new ContentProviderHolder(CONTENT_URI);…… } ……//内部类 Globalpublic static final class Global extends NameValueTable {public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global");public static final String NOTIFICATION_BUBBLES = "notification_bubbles";private static final Validator NOTIFICATION_BUBBLES_VALIDATOR = BOOLEAN_VALIDATOR;……}
数据储存位置及方式:
Android 6.0(M) 以前:
/data/data/com.android.providers.settings/databases/settings.db
Android 6.0(M) 及其以后:
/data/system/users/ <user_id>/settings_system.xml
/data/system/users/ <user_id>/settings_global.xml
/data/system/users/<user_id>/settings_secure.xml
数据存储方式:SettingsProvider只接受 int、float 和 String 等类型的数据,且这些数据最终都会被转换为 String 类型,然后以键-值对的形式存放在对应的 xml 文件中。
改变存储方式的目的主要有以下三点:
- 提高系统性能(400ms降低到10ms);
- 为每一个用户独立储存偏好设置;
- 限制了第三方应用对偏好设置的写入,增加了安全性。
此外,除了上述三个数据文件以外,在8.0以后,用户所安装的每个 APP (package) 都会产生一组独立的 ID (SSAID)和对应的描述,这些数据被存放在 “/data/system/users/<user_id>/settings_ssaid.xml"。下面是系统用户 0 存放上述 xml 文件的示例图 :
二、SettingsProvider 的启动流程
程序启动流程图:
1、SettingsProvider 是一个系统 Provider,和其他系统 Provider 的启动流程一样,它会在 SystemServer 启动系统服务的过程中被安装进系统。以下是实现代码:
FilePath: frameworks/base/services/java/com/android/server/SystemServer.java
/*** Starts a miscellaneous grab bag of stuff that has yet to be refactored and organized.*/private void startOtherServices() {……traceBeginAndSlog("InstallSystemProviders");mActivityManagerService.installSystemProviders();// Now that SettingsProvider is ready, reactivate SQLiteCompatibilityWalFlagsSQLiteCompatibilityWalFlags.reset();traceEnd();……}
上述代码中,在 SystemServer 启动系统服务的 startOtherServices() 方法中,通过调用 ActivityManagerService 的 installSystemProviders() 方法完成 SettingsProvider 的安装和创建。
2、ActivityManagerService 的 installSystemProviders() 方法。
FilePath: frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public class ActivityManagerService extends IActivityManager.Stubimplements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {……public final void installSystemProviders() {// 1、获取系统中所有的 Provider。这个过程最终会通过调用包管理模块 PackageManagerService 中的// queryContentProviders() 方法来获取所有的 ProviderList<ProviderInfo> providers;synchronized (this) {ProcessRecord app = mProcessList.mProcessNames.get("system", SYSTEM_UID);providers = generateApplicationProvidersLocked(app);if (providers != null) {for (int i=providers.size()-1; i>=0; i--) {ProviderInfo pi = (ProviderInfo)providers.get(i);if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {Slog.w(TAG, "Not installing system proc provider " + pi.name+ ": not system .apk");providers.remove(i);}}}}//2、调用 ActivityThread 的 installSystemProviders 方法完成系统 Provider 的安装启动,//其中就包括 SettingsProviderif (providers != null) {mSystemThread.installSystemProviders(providers);}synchronized (this) {mSystemProvidersInstalled = true;}mConstants.start(mContext.getContentResolver());mCoreSettingsObserver = new CoreSettingsObserver(this);mActivityTaskManager.installSystemProviders();mDevelopmentSettingsObserver = new DevelopmentSettingsObserver();SettingsToPropertiesMapper.start(mContext.getContentResolver());mOomAdjuster.initSettings();// Now that the settings provider is published we can consider sending// in a rescue party.//3、处理 SettingsProvider 安装之前某些依赖程序RescueParty.onSettingsProviderPublished(mContext);//mUsageStatsService.monitorPackages();}……
其中,上述代码第二步调用到 ActivityThread 的 installSystemProviders() 方法涉及到比较复杂的处理逻辑,和 SettingsProvider 的启动流程关系不大,这里就不做分析了,但它最终是会调用到 SettingsProvider 中的 onCreate() 方法。
3、SettingsProvider 中的 onCreate() 方法。
FilePath: frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@SuppressWarnings("deprecation")
public class SettingsProvider extends ContentProvider {
……@Overridepublic boolean onCreate() {Settings.setInSystemServer();// fail to boot if there're any backed up settings that don't have a non-null validatorensureAllBackedUpSystemSettingsHaveValidators();ensureAllBackedUpGlobalSettingsHaveValidators();ensureAllBackedUpSecureSettingsHaveValidators();synchronized (mLock) {mUserManager = UserManager.get(getContext());mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);mPackageManager = AppGlobals.getPackageManager();//1、创建 HandlerThread 对象,用来执行异步操作mHandlerThread = new HandlerThread(LOG_TAG,Process.THREAD_PRIORITY_BACKGROUND);mHandlerThread.start();mHandler = new Handler(mHandlerThread.getLooper());//2、创建 SettingsRegistry 对象mSettingsRegistry = new SettingsRegistry();}mHandler.post(() -> {//3、注册广播接收器registerBroadcastReceivers();startWatchingUserRestrictionChanges();});//4、向系统添加SettingsService、DeviceConfigService 服务ServiceManager.addService("settings", new SettingsService(this));ServiceManager.addService("device_config", new DeviceConfigService(this));return true;}
……
上述中的关键部分是创建 SettingsRegistry 对象,而 SettingsRegistry 是SettingsProvider 的内部类。
4、内部类 SettingsRegistry
FilePath: frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
final class SettingsRegistry {private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid";private static final String SETTINGS_FILE_GLOBAL = "settings_global.xml";private static final String SETTINGS_FILE_SYSTEM = "settings_system.xml";private static final String SETTINGS_FILE_SECURE = "settings_secure.xml";private static final String SETTINGS_FILE_SSAID = "settings_ssaid.xml";private static final String SETTINGS_FILE_CONFIG = "settings_config.xml";private static final String SSAID_USER_KEY = "userkey";private final SparseArray<SettingsState> mSettingsStates = new SparseArray<>();private GenerationRegistry mGenerationRegistry;private final Handler mHandler;private final BackupManager mBackupManager;private String mSettingsCreationBuildId;public SettingsRegistry() {mHandler = new MyHandler(getContext().getMainLooper());//1、创建 GenerationRegistry 对象,对xml文件的更改做版本管理mGenerationRegistry = new GenerationRegistry(mLock);//2、创建 BackupManager 对象,系统备份mBackupManager = new BackupManager(getContext());//3、迁移所有的系统设置数据migrateAllLegacySettingsIfNeeded();syncSsaidTableOnStart();}
……
}
在 SettingsRegistry 的构造方法中,注释1处创建了一个GenerationRegistry对象,GenerationRegistry对象的核心作用类似于对xml文件的更改做版本管理。
注释2处创建BackupManager对象,和系统备份有关。
注释3是迁移所有的系统设置数据,是重点方法。
5、migrateAllLegacySettingsIfNeeded()方法的分析:
FilePath: frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
private void migrateAllLegacySettingsIfNeeded() {synchronized (mLock) {final int key = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);File globalFile = getSettingsFile(key);//1、判断"settings_global.xml"文件是否存在//存在,则不做数据迁移//不存在,则要做数据迁移//文件不存在的情况有:1.机器首次启动;2.恢复出厂设置后的首次开机;3.系统版本低于6.0if (SettingsState.stateFileExists(globalFile)) {return;}mSettingsCreationBuildId = Build.ID;final long identity = Binder.clearCallingIdentity();try {//2、获取系统中所有的用户(多用户,一般用户0)List<UserInfo> users = mUserManager.getUsers(true);final int userCount = users.size();for (int i = 0; i < userCount; i++) {final int userId = users.get(i).id;//3、创建数据库 settings.db 和数据表,然后将 系统默认设置 加载到数据表中(临时数据库)DatabaseHelper dbHelper = new DatabaseHelper(getContext(), userId);SQLiteDatabase database = dbHelper.getWritableDatabase();//4、生成xml文件,并迁移数据库数据到文件中migrateLegacySettingsForUserLocked(dbHelper, database, userId);// Upgrade to the latest version.UpgradeController upgrader = new UpgradeController(userId);upgrader.upgradeIfNeededLocked();// Drop from memory if not a running user.if (!mUserManager.isUserRunning(new UserHandle(userId))) {removeUserStateLocked(userId, false);}}} finally {Binder.restoreCallingIdentity(identity);}}}
注释1处先判断"/data/system/users/0/settings_global.xml"文件是否存在,如果不存在,migrateAllLegacySettingsIfNeed()方法会直接返回,即不做后续的数据迁移操作,而文件不存在的情况一般发生机器首次启动或恢复出厂设置后的第一次开机。如果存在,则对系统设置做数据迁移。
注释2处是获取系统中所有用户,并通过for循环遍历对每个用户执行数据迁移的过程。
注释3处通过 DatabaseHelper 类创建了数据库和数据表,并使用默认设置对数据库表数据初始化。此外,这里数据库是一个临时数据库。之所以创建这个临时数据库,是为了兼容 Android 6.0 以前的版本而设计。
注释4处的代码是为用户生成 xml 文件,并将数据库数据迁移到 xml 文件的核心代码。
6、数据库创建和初始化的核心:DatabaseHelper
构造方法 DatabaseHelper() 中执行了dbNameForUser() 方法,SQLiteOpenHelper 创建了数据库 settings.db 。
在 settings.db 数据库创建后,会回调onCreate() 方法,并在方法中创建了数据表。
FilePath: frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
class DatabaseHelper extends SQLiteOpenHelper {private static final String TAG = "SettingsProvider";private static final String DATABASE_NAME = "settings.db";// Please, please please. If you update the database version, check to make sure the// database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'// is properly propagated through your change. Not doing so will result in a loss of user// settings.//此处省略……//获取数据库名称/路径static String dbNameForUser(final int userHandle) {// The owner gets the unadorned db name;if (userHandle == UserHandle.USER_SYSTEM) {return DATABASE_NAME;} else {// Place the database in the user-specific data tree so that it's// cleaned up automatically when the user is deleted.File databaseFile = new File(Environment.getUserSystemDirectory(userHandle), DATABASE_NAME);// If databaseFile doesn't exist, database can be kept in memory. It's safe because the// database will be migrated and disposed of immediately after onCreate finishesif (!databaseFile.exists()) {Log.i(TAG, "No previous database file exists - running in in-memory mode");return null;}return databaseFile.getPath();}}public DatabaseHelper(Context context, int userHandle) {//创建数据库super(context, dbNameForUser(userHandle), null, DATABASE_VERSION);mContext = context;mUserHandle = userHandle;}//此处省略isValidTable()、 isInMemory()、dropDatabase()、backupDatabase()……private void createSecureTable(SQLiteDatabase db) {db.execSQL("CREATE TABLE secure (" +"_id INTEGER PRIMARY KEY AUTOINCREMENT," +"name TEXT UNIQUE ON CONFLICT REPLACE," +"value TEXT" +");");db.execSQL("CREATE INDEX secureIndex1 ON secure (name);");}private void createGlobalTable(SQLiteDatabase db) {db.execSQL("CREATE TABLE global (" +"_id INTEGER PRIMARY KEY AUTOINCREMENT," +"name TEXT UNIQUE ON CONFLICT REPLACE," +"value TEXT" +");");db.execSQL("CREATE INDEX globalIndex1 ON global (name);");}@Overridepublic void onCreate(SQLiteDatabase db) {//1、创建 system 数据表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);");//2、创建 secure 数据表createSecureTable(db);//3、为用户0创建 global 数据表// Only create the global table for the singleton 'owner/system' userif (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 bookmarksboolean onlyCore = false;try {onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService("package")).isOnlyCoreApps();} catch (RemoteException e) {}if (!onlyCore) {loadBookmarks(db);}//4、将初始音量加载到DB中// Load initial volume levels into DBloadVolumeLevels(db);//5、将默认设置数据加载到数据表中// Load inital settings valuesloadSettings(db);}……
上述代码注释1、2、3处分别为用户创建system、secure和global三张数据表。此外,注释3处多了一个判断,这是因为 “settings_global.xml” 文件是所有用户共享的,所以只存储在0用户下。
注释4处的 loadVolumeLevels() 和 loadSettings() 方法是将默认数据填充数据表中。
以下是 loadSettings( ) 方法的内容:
FilePath: frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
private void loadSettings(SQLiteDatabase db) {//将三种数据类型的设置数据加载到数据表中loadSystemSettings(db);loadSecureSettings(db);// The global table only exists for the 'owner/system' userif (mUserHandle == UserHandle.USER_SYSTEM) {loadGlobalSettings(db);}}private void loadSystemSettings(SQLiteDatabase db) {SQLiteStatement stmt = null;try {stmt = db.compileStatement("INSERT OR IGNORE INTO system(name,value)"+ " VALUES(?,?);");loadBooleanSetting(stmt, Settings.System.DIM_SCREEN,R.bool.def_dim_screen);loadIntegerSetting(stmt, Settings.System.SCREEN_OFF_TIMEOUT,R.integer.def_screen_off_timeout);// Set default cdma DTMF typeloadSetting(stmt, Settings.System.DTMF_TONE_TYPE_WHEN_DIALING, 0);// Set default hearing aidloadSetting(stmt, Settings.System.HEARING_AID, 0);// Set default tty modeloadSetting(stmt, Settings.System.TTY_MODE, 0);loadIntegerSetting(stmt, Settings.System.SCREEN_BRIGHTNESS,R.integer.def_screen_brightness);loadIntegerSetting(stmt, Settings.System.SCREEN_BRIGHTNESS_FOR_VR,com.android.internal.R.integer.config_screenBrightnessForVrSettingDefault);loadBooleanSetting(stmt, Settings.System.SCREEN_BRIGHTNESS_MODE,R.bool.def_screen_brightness_automatic_mode);loadBooleanSetting(stmt, Settings.System.ACCELEROMETER_ROTATION,R.bool.def_accelerometer_rotation);loadDefaultHapticSettings(stmt);loadBooleanSetting(stmt, Settings.System.NOTIFICATION_LIGHT_PULSE,R.bool.def_notification_pulse);loadUISoundEffectsSettings(stmt);loadIntegerSetting(stmt, Settings.System.POINTER_SPEED,R.integer.def_pointer_speed);loadIntegerSetting(stmt, Settings.System.SCREENSHOT_BUTTON_SHOW,R.integer.def_screenshot_button_show);/** IMPORTANT: Do not add any more upgrade steps here as the global,* secure, and system settings are no longer stored in a database* but are kept in memory and persisted to XML.** See: SettingsProvider.UpgradeController#onUpgradeLocked*/} finally {if (stmt != null) stmt.close();}}
在上述 loadSystemSettings() 方法中,加载了很多 defaults.xml 文件中定义的与系统设置相关的默认值。在此期间,这些默认值会被加载到相应数据表中。以下是 defaults.xml 文件的部分内容:
FilePath: frameworks/base/packages/SettingsProvider/res/values/defaults.xml
<?xml version="1.0" encoding="utf-8"?>
……
<resources><bool name="def_dim_screen">true</bool><integer name="def_screen_off_timeout">60000</integer><integer name="def_sleep_timeout">-1</integer><bool name="def_airplane_mode_on">false</bool><bool name="def_theater_mode_on">false</bool><!-- Comma-separated list of bluetooth, wifi, and cell. --><string name="def_airplane_mode_radios" translatable="false">cell,bluetooth,wifi,nfc,wimax</string><string name="airplane_mode_toggleable_radios" translatable="false">bluetooth,wifi,nfc</string><string name="def_bluetooth_disabled_profiles" translatable="false">0</string><bool name="def_auto_time">true</bool><bool name="def_auto_time_zone">true</bool><bool name="def_accelerometer_rotation">false</bool><!-- Default screen brightness, from 0 to 255. 102 is 40%. --><integer name="def_screen_brightness">102</integer><bool name="def_screen_brightness_automatic_mode">false</bool><fraction name="def_window_animation_scale">100%</fraction><fraction name="def_window_transition_scale">100%</fraction><bool name="def_haptic_feedback">true</bool><bool name="def_bluetooth_on">true</bool><bool name="def_wifi_display_on">false</bool><bool name="def_install_non_market_apps">false</bool><bool name="def_package_verifier_enable">true</bool><!-- 0 == off, 3 == on --><integer name="def_location_mode">3</integer><bool name="assisted_gps_enabled">true</bool><bool name="def_netstats_enabled">true</bool><bool name="def_usb_mass_storage_enabled">true</bool><bool name="def_wifi_on">false</bool><!-- 0 == never, 1 == only when plugged in, 2 == always --><integer name="def_wifi_sleep_policy">2</integer><bool name="def_wifi_wakeup_enabled">true</bool><bool name="def_networks_available_notification_on">true</bool><bool name="def_backup_enabled">false</bool><string name="def_backup_transport" translatable="false">com.android.localtransport/.LocalTransport</string><!-- Default value for whether or not to pulse the notification LED when there is apending notification --><bool name="def_notification_pulse">true</bool><bool name="def_mount_play_notification_snd">true</bool><bool name="def_mount_ums_autostart">false</bool><bool name="def_mount_ums_prompt">true</bool><bool name="def_mount_ums_notify_enabled">true</bool>……
<resources>
7、将 settings.db 数据库的数据迁移到 xml 文件中
FilePath: frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper,SQLiteDatabase database, int userId) {//1、迁移与 system 设置相关的数据// Move over the system settings.//1.1、生成与xml文件唯一对应的keyfinal int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);ensureSettingsStateLocked(systemKey);//1.2、将临时数据库中设置数据存放到 SettingsState 对象中SettingsState systemSettings = mSettingsStates.get(systemKey);migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);//1.3、把数据写入到xml文件中systemSettings.persistSyncLocked();//2、迁移与 secure 设置相关的数据// Move over the secure settings.// Do this after System settings, since this is the first thing we check when deciding// to skip over migration from db to xml for a secondary user.final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);ensureSettingsStateLocked(secureKey);SettingsState secureSettings = mSettingsStates.get(secureKey);migrateLegacySettingsLocked(secureSettings, database, TABLE_SECURE);ensureSecureSettingAndroidIdSetLocked(secureSettings);secureSettings.persistSyncLocked();//3、迁移与 global 设置相关的数据// Move over the global settings if owner.// Do this last, since this is the first thing we check when deciding// to skip over migration from db to xml for owner user.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 createdif (mSettingsCreationBuildId != null) {globalSettings.insertSettingLocked(Settings.Global.DATABASE_CREATION_BUILDID,mSettingsCreationBuildId, null, true,SettingsState.SYSTEM_PACKAGE_NAME);}globalSettings.persistSyncLocked();}//4、删除数据库或备份数据库数据// Drop the database as now all is moved and persisted.if (DROP_DATABASE_ON_MIGRATION) {dbHelper.dropDatabase();} else {dbHelper.backupDatabase();}}
三、对 SettingsProvider 进行操作方法
1、操作方法
由于 framework下的 Settings.java 文件中对 SettingsProvider 进行了封装,而且Global、Secure、System 三种数据类型的使用方式几乎一样,所以对系统设置的操作也是相当简便的。以下是对系统设置的读写操作的示例:
//查询数据
String globalValue = Settings.Global.getString(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON);//写入数据
boolean isSuccess = Settings.System.putInt(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
2、权限限制
因为 SettingsProvider 是系统应用,所以对它的操作可能会有一些权限限制。首先查询 SettingsProvider 中的设置项数据是不需要声明任何权限,也就是说第三方应用也可以通过 Settings 类查询到 SettingsProvider 中的设置项数据,但是 Android 系统一般不允许第三方应用直接修改系统的设置项,需要用户授权才能修改,即修改设置项数据需要声明下权限:
- android.permission.WRITE_SETTINGS
- android.permission.WRITE_SECURE_SETTINGS
所以,一般可以直接修改系统设置的都是系统签名过的应用,也就是系统应用,如下图所示:
四、客制化示例
1、默认搜狗输入法
默认输入法属于安全设置, 设置项的数据类型是 Secure 类型。首先,在 defaults.xml 文件中并没有定义默认输入法设置项数据,所以只能在 DatabaseHelper 类里面的 loadSecureSettings() 添加相应方法可以进行修改,这个方法会在 loadSettings(SQLiteDatabase db) 方法被调用,然后将设置项数据加载到数据表中去。
@Deprecated
class DatabaseHelper extends SQLiteOpenHelper {private void loadSettings(SQLiteDatabase db) {loadSystemSettings(db);loadSecureSettings(db);// The global table only exists for the 'owner/system' userif (mUserHandle == UserHandle.USER_SYSTEM) {loadGlobalSettings(db);}}……private void loadSecureSettings(SQLiteDatabase db) {SQLiteStatement stmt = null;try {……
+ //启用搜狗输入法
+ loadSetting(stmt, Settings.Secure.ENABLED_INPUT_METHODS, "com.sohu.inputmethod.sogou/.SogouIME");
+ //将搜狗输入法设置为默认输入法
+ loadSetting(stmt, Settings.Secure.DEFAULT_INPUT_METHOD, "com.sohu.inputmethod.sogou/.SogouIME");/** IMPORTANT: Do not add any more upgrade steps here as the global,* secure, and system settings are no longer stored in a database* but are kept in memory and persisted to XML.** See: SettingsProvider.UpgradeController#onUpgradeLocked*/} finally {if (stmt != null) stmt.close();}}
获取输入法MID的命令:
adb shell ime list
2、自定义系统设置项开关
(1)在 Settings 应用相应的 xml 文件中定义一个开关控件 SwitchPreference,例如:
<SwitchPreferenceandroid:key="deep_sleep"android:title="@string/deep_sleep_title"settings:controller="com.android.settings.development.DeepSleepPreferenceController"/>
(2)定义开关的控制类,这个类需要继承一个抽象类TogglePreferenceController,然后重写 isChecked() 、setChecked()和 getAvailabilityStatus()方法,例如:
public class DeepSleepPreferenceController extends TogglePreferenceController{static final String DEEPSLEEP_ON = "1";static final String DEEPSLEEP_OFF = "0";private String mPreferenceKey = null;public DeepSleepPreferenceController(Context context, String preferenceKey) {super(context, preferenceKey);mPreferenceKey = preferenceKey;}//Synchronization switch status: set the previously saved switch status// when the user enters the interface where the switch is located again.@Overridepublic boolean isChecked() {boolean deepSleepFlag = false;if (DEEPSLEEP_ON.equals(Settings.Global.getString(mContext.getContentResolver(), mPreferenceKey))){deepSleepFlag = true;}return deepSleepFlag;}//When the user clicks the switch, the switch state is saved.@Overridepublic boolean setChecked(boolean isChecked) {final String newValue = isChecked ? DEEPSLEEP_ON : DEEPSLEEP_OFF;Settings.Global.putString(mContext.getContentResolver(), mPreferenceKey, newValue);return true;}@Overridepublic int getAvailabilityStatus() {//启用开关return AVAILABLE;}}