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中。
如果是工程版本的系統,吧數據庫settings.db重命名爲settings.db-backup,如果是非工程版本的系統,把數據庫文件刪除,也會刪除日誌settings.db-journal.
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);
}