SettingsProvider顾名思义是一个提供数据共享的Provider,SettingsProvider和Android系统其它Provider有很多不一样的地方:
1.SettingsProvider只接受int float String等基本类型的数据;
2.SettingsProvider由Android系统frameowrk进行了封装
3.SettingsProvider的数据由键值对组成
SettingsProvider有点类似Android的properties系统(Android属性系统):SystemProperites。SystemProperites除具有SettingsProvider以上的三个特性,SettingsProvider和SystemProperties的不同点在于:
1、数据保存方式不同:SystemProperies的数据保存属性文件中(/system/build.prop等),开机后会被加载到system properites store;SettingsProvider的数据保存在文件/data/system/users/0/settings_***.xml和数据库settings.db中;
2、作用范围不同:SettingsProvider可以实现跨进程、跨层次调用,即底层的c/c++可调用,java层也可以调用;SettingsProvider只能在java层调用
3、公开程度不同:SettingsProvider有部分功能上层第三方APP可以使用,SystemProvider上层第三方APP不可以使用。
在Android 6.0版本时,SettingsProvider被重构,Android从性能、安全等方面考虑,把SettingsProvider中原本保存在settings.db中的数据,目前全部保存在XML文件中。
数据分类
SettingsProvider主要有三种类型数据:Global、System、Secure三种类型
Global:所有的偏好设置对系统的所有用户公开,第三方有读没有写的权限
System:包含各种各样的用户偏好系统设置
Secure:安全性的用户偏好系统设置,第三方APP有读没有写的权限
Android6.0版本之后SettingsProvider管理的用户偏好设置数据从原来的settings.db数据库文件中转移到下面的3个xml文件中:data/system/users/0/settings_global.xml
data/system/users/userid/settings_system.xml
data/system/users/userid/settings_secure.xml
主要源码
SettingsProvider的代码数量不多,主要包含如下的java文件:
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
frameworks/base/core/java/android/provider/Settings.java
AndroidManifest.xml配置
SettingsProvider的AndroidManifest.xml文件对应和ContentProvider的配置如下:
<manifest ......
android:sharedUserId="android.uid.system">
<application android:allowClearUserData="false"
android:label="@string/app_label"
android:process="system"
......
android:directBootAware="true">
<provider android:name="SettingsProvider"
android:authorities="settings"
android:multiprocess="false"
android:exported="true"
android:singleUser="true"
android:initOrder="100" />
</application>
</manifest>
上面的Manifest配置由sharedUserId可知,SettingsProvider运行在系统进程中,定义的ContentProvider实现类是SettingsProvider,Uri凭证是settings。
SettingsProvider的启动过程
系统启动SettingsProvider是在frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() {
//省略一部分代码
//...
traceBeginAndSlog("InstallSystemProviders");
mActivityManagerService.installSystemProviders();
// Now that SettingsProvider is ready, reactivate SQLiteCompatibilityWalFlags
SQLiteCompatibilityWalFlags.reset();
traceEnd();
//省略一部分代码
//...
}
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public final void installSystemProviders() {
// 1、获取系统中所有的Provider,最终通过调用包管理PackageManagerService中的
//queryContentProviders()方法来查询所有的Provider
List<ProviderInfo> providers;
synchronized (this) {
ProcessRecord app = 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的安装,包括
//SettingsProvider
if (providers != null) {
mSystemThread.installSystemProviders(providers);
}
synchronized (this) {
mSystemProvidersInstalled = true;
}
mConstants.start(mContext.getContentResolver());
mCoreSettingsObserver = new CoreSettingsObserver(this);
mFontScaleSettingObserver = new FontScaleSettingObserver();
mDevelopmentSettingsObserver = new DevelopmentSettingsObserver();
GlobalSettingsToPropertiesMapper.start(mContext.getContentResolver());
// 3、Provider启动完成
// Now that the settings provider is published we can consider sending
// in a rescue party.
RescueParty.onSettingsProviderPublished(mContext);
//mUsageStatsService.monitorPackages();
}
其中第二步中,installSystemProviders()会启动SettingsProvider。启动SettingsProvider即运行SettingsProvider,首先调用OnCreate()方法。
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@Override
public boolean onCreate() {
Settings.setInSystemServer();
// fail to boot if there're any backed up settings that don't have a non-null validator
ensureAllBackedUpSystemSettingsHaveValidators();
ensureAllBackedUpGlobalSettingsHaveValidators();
ensureAllBackedUpSecureSettingsHaveValidators();
synchronized (mLock) {
mUserManager = UserManager.get(getContext());
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mPackageManager = AppGlobals.getPackageManager();
mHandlerThread = new HandlerThread(LOG_TAG,
Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mSettingsRegistry = new SettingsRegistry();
}
mHandler.post(() -> {
//注册广播接收,关心设备用户变化以及APP卸载的广播
registerBroadcastReceivers();
startWatchingUserRestrictionChanges();
});
ServiceManager.addService("settings", new SettingsService(this));
return true;
}
重点代码分析,上述代码中首先实例化一个HandlerThread的实例mHandlerThread,优先级是Process.THREAD_PRIORITY_BACKGROUND。关键的部分是:mSettingsRegistry = new SettingsRegistry()。
SettingsRegistry是SettingsProvider.java的内部类,先分析构造方法
public SettingsRegistry() {
mHandler = new MyHandler(getContext().getMainLooper());
// 1、对xml的修改做版本管理
mGenerationRegistry = new GenerationRegistry(mLock);
// 2、创建Backup,做系统备份
mBackupManager = new BackupManager(getContext());
// 3、迁移所有的系统设置数据
migrateAllLegacySettingsIfNeeded();
syncSsaidTableOnStart();
}
migrateAllLegacySettingsIfNeeded()方法,从命名上是迁移settings数据。
private void migrateAllLegacySettingsIfNeeded() {
synchronized (mLock) {
final int key = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
File globalFile = getSettingsFile(key);
if (SettingsState.stateFileExists(globalFile)) {
return;
}
mSettingsCreationBuildId = Build.ID;
final long identity = Binder.clearCallingIdentity();
try {
//获取系统中所有的用户(多用户,一般user0)
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;
//关键代码,通过DatabaseHelper类创建数据库settings.db,并使用默认设置
//对数据库表数据初始化
DatabaseHelper dbHelper = new DatabaseHelper(getContext(), userId);
SQLiteDatabase database = dbHelper.getWritableDatabase();
migrateLegacySettingsForUserLocked(dbHelper, database, userId);
//关键代码,生成和初始化xml文件
// 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);
}
}
}
上面的代码首先是调用了makeKey()方法,所谓makeKey()就是和上文中的数据分类小章节中提到的System、Global和Secure三种key。然后调用getSettingsFile()方法获取到一个File对象的实例,如下:
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);
} else if (isSecureSettingsKey(key)) {
final int userId = getUserIdFromKey(key);
return new File(Environment.getUserSystemDirectory(userId),
SETTINGS_FILE_SECURE);
} else if (isSsaidSettingsKey(key)) {
final int userId = getUserIdFromKey(key);
return new File(Environment.getUserSystemDirectory(userId),
SETTINGS_FILE_SSAID);
} else {
throw new IllegalArgumentException("Invalid settings key:" + key);
}
}
上面的代码中对Global、System、Secure分别生成一个File对象实例,它们的File对象分别对应的文件是:
- /data/system/users/0/settings_global.xml
- /data/system/users/0/settings_system.xml
- /data/system/users/0/settings_secure.xml
- 在8.0以后,每位使用者所安装的每个 APP (package) 都会产生一组独立的 ID (SSAID),并将描述档案放置 “/data/system/users/0/settings_ssaid.xml"。
在migrateAllLegacySettingsIfNeeded()方法,实例化一股DatabaseHelper,DatabaseHelper是SQLiteOpenHelper的子类。
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "settings.db";
public DatabaseHelper(Context context, int userHandle) {
super(context, dbNameForUser(userHandle), null, DATABASE_VERSION);
mContext = context;
mUserHandle = userHandle;
}
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 finishes
if (!databaseFile.exists()) {
Log.i(TAG, "No previous database file exists - running in in-memory mode");
return null;
}
return databaseFile.getPath();
}
}
@Override
public void onCreate(SQLiteDatabase db) {
//创建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);");
// 1、创建表,createSecureTable,Secure数据表
createSecureTable(db);
// 2、创建表,createGlobalTable,Global数据表
// 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
boolean onlyCore = false;
try {
onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService(
"package")).isOnlyCoreApps();
} catch (RemoteException e) {
}
if (!onlyCore) {
loadBookmarks(db);
}
// 3、默认音量数据填充数据库
// Load initial volume levels into DB
loadVolumeLevels(db);
// 4、默认数据填充数据库
// Load inital settings values
loadSettings(db);
}
}
loadSettings()这个方法和loadVolumeLevels()方法类似,都是加载很多默认值写入到数据库中,这些默认值很大一部分被定义在文件frameworks/base/packages/SettingsProvider/res/values/defaults.xml中,也有一些来自其它地方。总之,loadVolumeLevels()和loadSettings()的作用就是在手机第一次启动时,把手机编好设置的默认值写入到数据库settings.db中。
Settings.db数据迁移到xml中
继续回到SettingsRegistry中的migrateAllLegacySettingsIfNeeded方法,在migrateAllLegacySettingsIfNeeded中接下来会执行migrateLegacySettingsForUserLocked方法
private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper, SQLiteDatabase database, int userId) {
//1、处理System数据
// Move over the system settings.
final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
//生成对应的SettingsState对象保存在mSettingsStates中,生成setting_system.xml
ensureSettingsStateLocked(systemKey);
SettingsState systemSettings = mSettingsStates.get(systemKey);
//数据迁移,将settings.db的数据迁移到settingsStates.get(systemKey);
migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);
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);
//生成对应的SettingsState对象保存在mSettingsStates中,生成settings_secure.xml
ensureSettingsStateLocked(secureKey);
SettingsState secureSettings = mSettingsStates.get(secureKey);
migrateLegacySettingsLocked(secureSettings, database, TABLE_SECURE);
//数据迁移,将settings.db的数据迁移到settings_secure.xml中
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);
//生成对应的SettingsState对象保存在mSettingsStates中,生成settings_global.xml
ensureSettingsStateLocked(globalKey);
SettingsState globalSettings = mSettingsStates.get(globalKey);
//数据迁移,将settings.db的数据迁移到settings_global.xml中
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();
}
//数据迁移完成后,是否删除settings.db,或者做一个备份
// Drop the database as now all is moved and persisted.
if (DROP_DATABASE_ON_MIGRATION) {
dbHelper.dropDatabase();
} else {
dbHelper.backupDatabase();
}
}
ensureSettingsStateLocked是一个非常重要的方法,实例化一个SettingsState对象,
这个对象指向文件data/system/users/0/xx.xml文件,然后将settingsState放置在对象mSettingsStates中。
SettnigsProvider启动时会创建settings.db数据库,然后把所有的默认设置项写入到数据库,接着会把数据库中所有的设置项从数据库转移到xml文件中,随后便会对数据库执行删除操作。为什么会有这么一个过程,这些过程是否可以移除创建数据库这一步?其实这个过程是为了兼容之前的版本而设计,在SettingsProvider被Android重构后,SettingsProvider中数据库相关的代码Android已经停止更新。
//ensureSettingsStateLocked是一个重要的方法,给每一个数据类型key,
//创建一个对应的SettingsState对象并保存在mSettingsStates
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);
}
}
之后会执行migrateLegacySettingsLocked方法
private void migrateLegacySettingsLocked(SettingsState settingsState,
SQLiteDatabase database, String table) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(table);
Cursor cursor = queryBuilder.query(database, ALL_COLUMNS,
null, null, null, null, null);
try {
......
while (!cursor.isAfterLast()) {
String name = cursor.getString(nameColumnIdx);
String value = cursor.getString(valueColumnIdx);
settingsState.insertSettingLocked(name, value,
SettingsState.SYSTEM_PACKAGE_NAME);
cursor.moveToNext();
}
} finally {
cursor.close();
}
}
上面这个方法,查询数据库中System所有的设置,然后在while循环中把每个值的信息作为insertSettingLocked()的参数。
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
public boolean insertSettingLocked(String name, String value, String packageName) {
Setting oldState = mSettings.get(name);
String oldValue = (oldState != null) ? oldState.value : null;
if (oldState != null) {
......
} else {
Setting state = new Setting(name, value, packageName);
mSettings.put(name, state);
}
......
}
上面的方法把每一个设置项封装到Setting中,接着把state放置到ArrayMap<String,Setting>的实例mSettings中。
那么,从方法ensureSettingsStateLocked()到insertSettingsLocked()方法,这个过程表明,有一个对象SettingsState,指向文件/data/system/users/0/settings_system.xml,持有变量mSettings,而mSettings持有封装了设置项的name,value,packageName的对象Setting,也就是settings_system.xml文件中的所有设置项间接被SettingsState持有。
在migrateLegacySettingsForUserLocked中,migrateLegacySettingsLocked方法执行完毕后,调用systemSettings.persistSyncLocked(),systemSettings是SettingsState的实例,代表的是settings_system.xml的所有设置项,persistSyncLocked()方法就是把systemSettings持有的所有的设置项从内存中固化到文件settings_system.xml中。
封装SettingsProvider接口
SettingsProvider是向整个Android系统提供用户偏好设置的程序,framework有一个类Settings.java对使用SettingsProvider进行了封装。
frameworks/base/core/java/android/provider/Settings.java
public final class Settings {
public static final String AUTHORITY = "settings";
public static final class Global extends NameValueTable {
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global");
......
}
public static final class Secure extends NameValueTable {
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/secure");
......
}
public static final class System extends NameValueTable {
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/system");
......
}
private static class NameValueCache {
private final Uri mUri;
private final HashMap<String, String> mValues = new HashMap<String, String>();
public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
......
}
public boolean putStringForUser(ContentResolver cr, String name, String value,
final int userHandle) {
......
}
private IContentProvider lazyGetProvider(ContentResolver cr) {
IContentProvider cp = null;
synchronized (NameValueCache.this) {
cp = mContentProvider;
if (cp == null) {
cp = mContentProvider = cr.acquireProvider(mUri.getAuthority());
}
}
return cp;
}
}
上面的代码中,分别声明了Global、Secure、System三个静态内部类,分别对应SettingsProvider中的Global、Secure、System三种数据类型。Global、Secure、System三个静态内部类会分别持有自己NameValueCache的实例变量,每个NameValueCache持有指向SettingsProvider中的SettingsProvider.java的AIDL远程调用IContentProvider,读者可以阅读《Android System Server大纲之ContentService和ContentProvider原理剖析》了解ConatentProvider的这个过程。因此,查询数据需要经过NameValueCache的getStringForUser()方法,插入数据需要经过putStringForUser()方法。同时,NameValueCache还持有一个变量mValues,用于保存查询过的设置项,以便下下次再次发起查询时,能够快速返回。
操作SettingsProvider
由于Settings.java对使用SettingsProvider进行了封装,所以,使用起来相当简单简洁。由于Global、Secure、System三种数据类型的使用是几乎相同,所以本文就只以Global为例对查询插入数据的过程进行分析。
查询数据
从SettingsProvider的Global中查询数据,查询是否是飞行模式使用方法如下:
String globalValue = Settings.Global.getString(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON);
分析一下getString()方法
frameworks/base/core/java/android/provider/Settings.java
public static String getString(ContentResolver resolver, String name) {
return getStringForUser(resolver, name, UserHandle.myUserId());
}
/** @hide */
public static String getStringForUser(ContentResolver resolver, String name,
int userHandle) {
//因为在Android系统的更新中,保存在Global、Secure、System三种类型的数据存放位置有变化
//需要添加判断兼容老版本的使用方法。
if (MOVED_TO_SECURE.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global"
+ " to android.provider.Settings.Secure, returning read-only value.");
return Secure.getStringForUser(resolver, name, userHandle);
}
return sNameValueCache.getStringForUser(resolver, name, userHandle);
}
NameValueCache.getStringForUser()方法,如下
public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
final boolean isSelf = (userHandle == UserHandle.myUserId());
if (isSelf) {
......
} else if (mValues.containsKey(name)) {
return mValues.get(name);
......
IContentProvider cp = lazyGetProvider(cr);
// Try the fast path first, not using query(). If this
// fails (alternate Settings provider that doesn't support
// this interface?) then we fall back to the query/table
// interface.
if (mCallGetCommand != null) {
try {
......
Bundle b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);
if (b != null) {
String value = b.getString(Settings.NameValueTable.VALUE);
......
mValues.put(name, value);
}
return value;
}
} catch (RemoteException e) {
// Not supported by the remote side? Fall through
// to query().
}
}
Cursor c = null;
try {
c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
new String[]{name}, null, null);
String value = c.moveToNext() ? c.getString(0) : null;
synchronized (NameValueCache.this) {
mValues.put(name, value);
}
return value;
......
if (c != null) c.close();
}
}
}
NameValueCache是Settings.java中的内部类,
首先从缓存mValues变量中去找,如果没有查询到,就调用SettingsProvider的call()接口,如果call()接口也没有查询到,再调用query()接口。这里用的是call()接口,下文就以call()接口往下分析。cp.call()会调用到SettingsProvider的call()方法,读者可以阅读《Android System Server大纲之ContentService和ContentProvider原理剖析》了解ConatentProvider的这个过程。注意参数mCallGetCommand。
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
public Bundle call(String method, String name, Bundle args) {
final int requestingUserId = getRequestingUserId(args);
switch (method) {
case Settings.CALL_METHOD_GET_GLOBAL: {
Setting setting = getGlobalSetting(name);
return packageValueForCallResult(setting, isTrackingGeneration(args));
}
case Settings.CALL_METHOD_GET_SECURE: {
Setting setting = getSecureSetting(name, requestingUserId);
return packageValueForCallResult(setting, isTrackingGeneration(args));
}
......
}
return null;
}
上层传过来的参数的command是mCallGetCommand,即Settings.CALL_METHOD_GET_GLOBAL,所以调用getGlobalSettings(),getGlobalSetting()返回的Setting setting是封装了设置项name、value等信息的,查看getGlobalSetting()方法:
private Setting getGlobalSetting(String name) {
// Get the value.
synchronized (mLock) {
return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_GLOBAL,
UserHandle.USER_SYSTEM, name);
}
}
通过mSettingsRegistry.getSettingLocked()继续寻找:
public Setting getSettingLocked(int type, int userId, String name) {
final int key = makeKey(type, userId);
SettingsState settingsState = peekSettingsStateLocked(key);
return settingsState.getSettingLocked(name);
}
通过peekSettingsStateLocked(key)寻找SettingsState:
private SettingsState peekSettingsStateLocked(int key) {
SettingsState settingsState = mSettingsStates.get(key);
if (settingsState != null) {
return settingsState;
}
ensureSettingsForUserLocked(getUserIdFromKey(key));
return mSettingsStates.get(key);
}
获取到SettingsState settingsState,回到getSettingLocked(),调用settingsState.getSettingLocked(name):
public Setting getSettingLocked(String name) {
if (TextUtils.isEmpty(name)) {
return mNullSetting;
}
Setting setting = mSettings.get(name);
if (setting != null) {
return new Setting(setting);
}
return mNullSetting;
}
到getSettingsLocked()最终获取到Setting setting。
插入数据
从SettingsProvider的Global中插入数据,插入飞行模式的使用方法如下:
boolean isSuccess = Settings.System.putInt(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
和查询代码一样,代码非常简洁,整个查询过程几乎类似
frameworks/base/core/java/android/provider/Settings.java
public static boolean putInt(ContentResolver cr, String name, int value) {
return putString(cr, name, Integer.toString(value));
}
public static boolean putString(ContentResolver resolver,
String name, String value) {
return putStringForUser(resolver, name, value, UserHandle.myUserId());
}
public static boolean putStringForUser(ContentResolver resolver,
String name, String value, int userHandle) {
......
return sNameValueCache.putStringForUser(resolver, name, value, userHandle);
}
和查询一样,继续看putStringForUser():
public boolean putStringForUser(ContentResolver cr, String name, String value,
final int userHandle) {
try {
Bundle arg = new Bundle();
arg.putString(Settings.NameValueTable.VALUE, value);
arg.putInt(CALL_METHOD_USER_KEY, userHandle);
IContentProvider cp = lazyGetProvider(cr);
cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
} catch (RemoteException e) {
Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
return false;
}
return true;
}
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
public Bundle call(String method, String name, Bundle args) {
final int requestingUserId = getRequestingUserId(args);
switch (method) {
......
case Settings.CALL_METHOD_PUT_GLOBAL: {
String value = getSettingValue(args);
insertGlobalSetting(name, value, requestingUserId, false);
break;
}
case Settings.CALL_METHOD_PUT_SECURE: {
String value = getSettingValue(args);
insertSecureSetting(name, value, requestingUserId, false);
break;
}
......
}
return null;
}
首先调用getSettingValue(args)获取对应的设置项,接着insertGlobalSetting()方法:
private boolean insertGlobalSetting(String name, String value, int requestingUserId,
boolean forceNotify) {
return mutateGlobalSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT,
forceNotify);
}
直接调用了mutateGlobalSetting()方法:
private boolean mutateGlobalSetting(String name, String value, int requestingUserId,
int operation, boolean forceNotify) {
// Make sure the caller can change the settings - treated as secure.
enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
// If this is a setting that is currently restricted for this user, do not allow
// unrestricting changes.
if (isGlobalOrSecureSettingRestrictedForUser(name, callingUserId, value)) {
return false;
}
// Perform the mutation.
synchronized (mLock) {
switch (operation) {
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry
.insertSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM,
name, value, getCallingPackage(), forceNotify);
}
......
}
}
return false;
}
首先进行权限检查,然后调用mSettingsRegistry.insertSettingLocked()方法:
public boolean insertSettingLocked(int type, int userId, String name, String value,
String packageName, boolean forceNotify) {
final int key = makeKey(type, userId);
SettingsState settingsState = peekSettingsStateLocked(key);
final boolean success = settingsState.insertSettingLocked(name, value, packageName);
if (forceNotify || success) {
notifyForSettingsChange(key, name);
}
return success;
}
新增数据保存到数据库
以新增一个"intercept_back"来获取back键是否被禁用,数据类型是 System integer类型。
定义变量:
frameworks/base/core/java/android/provider/Settings.java
public static final class System extends NameValueTable {
..
...
/**
*Enable / disable back key interface
*0 = enable
*1 = disable
*@hide
*/
public static final String INTERCEPT_BACK = "intercept_back";
public static final String[] SETTINGS_TO_BACKUP = {
STAY_ON_WHILE_PLUGGED_IN, // moved to global
WIFI_USE_STATIC_IP,
WIFI_STATIC_IP,
WIFI_STATIC_GATEWAY,
WIFI_STATIC_NETMASK,
......
SHOW_BATTERY_PERCENT,
INTERCEPT_BACK //add by wangjin in 2019/01/09
};
}
注意:
在这一步一定要加/** @hide */(一定是/** */格式),不然编译会报以下错误:
Checking API: checkpublicapi-current
out/target/common/obj/PACKAGING/public_api.txt:20: error 5: Added public field android.Manifest.permission.BACKUP
out/target/common/obj/PACKAGING/public_api.txt:82: error 5: Added public field android.Manifest.permission.INVOKE_CARRIER_SETUP
out/target/common/obj/PACKAGING/public_api.txt:106: error 5: Added public field android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
out/target/common/obj/PACKAGING/public_api.txt:116: error 5: Added public field android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST
******************************
You have tried to change the API from what has been previously approved.
To make these errors go away, you have two choices:
1) You can add "@hide" javadoc comments to the methods, etc. listed in the
errors above.
2) You can update current.txt by executing the following command:
make update-api
To submit the revised current.txt to the main Android repository,
you will need approval.
设置默认值
vendor/mediatek/proprietary/packages/apps/SettingsProvider/res/values/defaults.xml (mtk)
<integer name="intercept_back">0</integer>
加载该值
private void loadSystemSettings(SQLiteDatabase db) {
loadIntegerSetting(stmt,Settings.System.INTERCEPT_BACK, R.integer.intercept_back);
}