2月1日晚間,阿里巴巴與螞蟻金服聯合宣布,根據2014年雙方籤署的戰略協議,並經阿里巴巴董事會批准,阿里巴巴將通過一家中國子公司入股並獲得螞蟻金服33%的股權。
又快到了周末,好好調整,為今年最後一段工作劃上一個完美的句號!
本篇來自 FamilyYuan 的投稿,分享了 Android 系統 APP 之 SettingsProvider,一起來看看!希望大家喜歡。
FamilyYuan 的博客地址:
http://blog.csdn.net/myfriend0
SettingsProvider 顧名思義是一個提供設置數據共享的 Provider,SettingsProvider 和Android 系統其它 Provider 有很多不一樣的地方,如:
SettingsProvider 只接受 int、float、string等基本類型的數據;
SettingsProvider 由 Android 系統 framework 進行了封裝,使用更加快捷方便
SettingsProvider 的數據由鍵值對組成
SettingsProvider 有點類似 Android 的 properties 系統(Android 屬性系統):SystemProperties。SystemProperties 除具有 SettingsProvider 以上的三個特性,SettingsProvider 和 SystemProperties 的不同點在於:
數據保存方式不同:SystemProperties 的數據保存屬性文件中(/system/build.prop等),開機後會被加載到 system properties store;SettingsProvider 的數據保存在文件/data/system/users/0/settings_***.xml 和資料庫 settings.db 中;
作用範圍不同:SystemProperties可以實現跨進程、跨層次調用,即底層的 c/c++ 可以調用,java 層也可以調用;SettingProvider 只能能在 java 層(APP)使用;
公開程度不同:SettingProvider 有部分功能上層第三方 APP 可以使用,SystemProperties 上層第三方 APP 不可以使用。
用一句話概括 SettingsProvider 的作用,SettingsProvider 包含全局性、系統級別的用戶編好設置。在手機中有一個 Settings 應用,用戶可以在 Settings 裡面做很多設備的設置,這些用戶偏好的設置很多就保存在 SettingsProvider 中。例如,飛行模式。
在 Android 6.0 版本時,SettingsProvider 被重構,Android 從性能、安全等方面考慮,把SettingsProvider 中原本保存在 settings.db 中的數據,目前全部保存在 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
SettingsProvider 對數據進行了分類,分別是 Global、System、Secure 三種類型,它們的區別如下:
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>
這些代碼定義在文件 frameworks/base/packages/SettingsProvider/AndroidManifest.xml中。
上面的 Manifest 配置由 sharedUserId 可知,SettingsProvider 運行在系統進程中,定義的 ContentProvider 實現類是 SettingsProvider,Uri 憑證是 settings。
啟動 SettingsProvider 即運行 SettingsProvider,和打開一個 Activity 類似,會回調 ContentProvider 的生命周期方法,首先的,會調用 OnCreate() 方法,如下:
public boolean onCreate() {
synchronized (mLock) {
.
mHandlerThread = new HandlerThread(LOG_TAG,
Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mSettingsRegistry = new SettingsRegistry();
}
registerBroadcastReceivers();
startWatchingUserRestrictionChanges();
return true;
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
上面的代碼首先是實例化一個 HandlerThread 的實例 mHandlerThread,優先級為 Process.THREAD_PRIORITY_BACKGROUND,下文會用到。然後實例化 SettingsRegistry 的實例 mSettingsRegistry,這一步很重要。接著會註冊廣播接收器,所關心的廣播包括設備用戶變化以及 APP 卸載的廣播,設備用戶的變化對大多數地方使用 SettingProvider的影響不是很大,本文就不再闡述和用戶變化相關的內容了。但是APP卸載這裡需要關注一下,當一個APP有數據保存在 SettingsProvider 時,APP 被卸載後,被卸載的 APP 設置的所有數據都會被清除。回到 SettingsRegistry 的實例化過程,構造方法如下:
public SettingsRegistry() {
.
migrateAllLegacySettingsIfNeeded();
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
migrateAllLegacySettingsIfNeeded() 方法,從命名上是遷移 settings 數據,遷移什麼數據呢?從哪裡遷移到哪裡呢?繼續往下看:
private void migrateAllLegacySettingsIfNeeded() {
synchronized (mLock) {
final int key = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
File globalFile = getSettingsFile(key);
if (globalFile.exists()) {
return;
}
.
DatabaseHelper dbHelper = new DatabaseHelper(getContext(), userId);
SQLiteDatabase database = dbHelper.getWritableDatabase();
migrateLegacySettingsForUserLocked(dbHelper, database, userId);
.
}
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
上面的代碼首先是調用了 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 {
throw new IllegalArgumentException("Invalid settings key:" + key);
}
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
上面的代碼中對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
那麼也就是說,Global 類型的數據保存在文件 settings_global.xml 中,System 類型的數據保存在文件 settings_system.xml 中,Secure 類型的數據保存在文件 settings_secure.xml中。
回到上文中的 migrateAllLegacySettingsIfNeeded() 方法,實例化一個 DatabaseHelper,DatabaseHelper 是 SQLiteOpenHelper 的子類,然後調用 getWritableDatabase() 獲取到指向資料庫文件的 SQLiteDatabase 實例 database。從 Android SQLite 的架構可知,這個過程會調用 SQLiteOpenHelper 的 onCreate() 方法,如果讀者對這個過程迷惑的,可以閱讀 Android 的 API 指南:
https://developer.android.google.cn/guide/topics/data/data-storage.html#db
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);
if (mUserHandle == UserHandle.USER_SYSTEM) {
createGlobalTable(db);
}
.
loadVolumeLevels(db);
loadSettings(db);
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java 中。
這個方法調用 db.execSQL(「CREATE TABLE system …、createSecureTable()、createGlobalTable() 分別創建 System、Secure、Global 三個資料庫表,這個和上文中數據分類章節中的內容一致。接著調用 loadVolumeLevels(db) 方法,把默認的鈴聲音量、音樂音量、通知音量以及震動設置等等寫入到資料庫的 System 表格中。處理完後調用方法 loadSettings(db),如下:
private void loadSettings(SQLiteDatabase db) {
loadSystemSettings(db);
loadSecureSettings(db);
if (mUserHandle == UserHandle.USER_SYSTEM) {
loadGlobalSettings(db);
}
這個方法定義在文件frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java中。
loadSettings() 這個方法和 loadVolumeLevels() 方法類似,都是加載很多默認值寫入到資料庫中,這些默認值很大一部分被定義在文件 frameworks/base/packages/SettingsProvider/res/values/defaults.xml 中,也有一些來自其它地方。總之,loadVolumeLevels() 和 loadSettings() 的作用就是在手機第一次啟動時,把手機編好設置的默認值寫入到資料庫 settings.db中。
DatabaseHelper 的 onCreate() 方法執行完畢後,這裡又回到 migrateAllLegacySettingsIfNeeded() 方法中,DatabaseHelper 創建完畢後,繼續調用 migrateLegacySettingsForUserLocked() 方法,如下:
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();
.
if (DROP_DATABASE_ON_MIGRATION) {
dbHelper.dropDatabase();
} else {
dbHelper.backupDatabase();
}
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
上面的代碼中的每個方法都是那麼重要,首先是 ensureSettingsStateLocked(systemKey),如下:
private void ensureSettingsStateLocked(int key) {
if (mSettingsStates.get(key) == null) {
.
SettingsState settingsState = new SettingsState(mLock, getSettingsFile(key), key,
maxBytesPerPackage, mHandlerThread.getLooper());
mSettingsStates.put(key, settingsState);
}
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
上面代碼實例化一個 SettingsState 對象,這個對象指向文件 /data/system/users/0/settings_system.xml,然後把 settingsState 放置在對象 mSettingsStates 中。回到 migrateLegacySettingsForUserLocked() 方法,ensureSettingsStateLocked() 執行完畢後,調用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();
}
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
上面這個方法,查詢資料庫中 System 所有的設置,然後在 while 循環中把每個值的信息作為insertSettingLocked() 的參數,insertSettingLocked() 方法如下:
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);
}
.
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java 中。
上面的方法把每個設置項封裝到對象 Setting 中,接著有把 state 放置到 ArrayMapString, Setting 的實例 mSettings 中。
那麼,從方法 ensureSettingsStateLocked() 到 insertSettingLocked() 方法,這個過程表明,有一個對象 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中,這個過程的代碼就不貼出來了。migrateLegacySettingsForUserLocked() 方法中省略的代碼,就是和上文的這幾個方法一樣,把 settings_global.xml、settings_secure.xml 兩個文件中的所有設置項封裝到 Setting 中,被 SettingsState 持有。
也就是說,settings_global.xml、settings_secure.xml、settings_system.xml 三個文件的所有設置項間接被 SettingsState 持有,而 SettingsState 又被封裝到類 SettingsProvider.java 的變量 mSettingsStates 中,mSettingsStates 是 SparseArray」SettingsState」 的實例。它們的層次關係如下圖:
再次回到方法 migrateLegacySettingsForUserLocked(),在把數據中的數據轉移到 xml 文件後,執行下面這段代碼:
if (DROP_DATABASE_ON_MIGRATION) {
dbHelper.dropDatabase();
} else {
dbHelper.backupDatabase();
}
如果是工程版本的系統,把資料庫 settings.db 重命名為 settings.db-backup,如果是非工程版本的系統,把資料庫文件刪除,也會刪除日誌settings.db-journal。
SettnigsProvider 啟動時會創建 settings.db 資料庫,然後把所有的默認設置項寫入到資料庫,接著會把資料庫中所有的設置項從資料庫轉移到 xml 文件中,隨後便會對資料庫執行刪除操作。為什麼會有這麼一個過程,這些過程是否可以移除創建資料庫這一步?其實這個過程是為了兼容之前的版本而設計,在 SettingsProvider 被 Android 重構後,SettingsProvider 中資料庫相關的代碼 Android 已經停止更新。
由章節前言中的描述,SettingsProvider 是向整個 Android 系統提供用戶編好設置的提供程序,所保存的數據類型和方式上也有一定約束和規定,且要求使用 SettingsProvider 是方便的,代碼量少的。因此,需要對 ContentProvider 的一些接口進行封裝,以保證在整個Android 的 java 層任何一個地方都能方便、快捷的使用 SettingsProvider 進行數據查詢,數據更新和數據插入。所以,理所當然地,framework 有一個類 Settings.java 對使用 SettingsProvider 進行了封裝。如下:
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;
}
}
這個類定義在文件 frameworks/base/core/java/android/provider/Settings.java 中。
上面的代碼中,分別聲明了 Global、Secure、System 三個靜態內部類,分別對應 SettingsProvider 中的 Global、Secure、System 三種數據類型。Global、Secure、System 三個靜態內部類會分別持有自己 NameValueCache 的實例變量,每個 NameValueCache 持有指向 SettingsProvider 中的 SettingsProvider.java 的 AIDL 遠程調用 IContentProvider,讀者可以閱讀《Android System Server 大綱之 ContentService 和 ContentProvider 原理剖析》:
http://blog.csdn.net/myfriend0/article/details/58587065
了解ConatentProvider的這個過程。因此,查詢數據需要經過NameValueCache 的 getStringForUser() 方法,插入數據需要經過 putStringForUser() 方法。同時,NameValueCache 還持有一個變量 mValues,用於保存查詢過的設置項,以便下下次再次發起查詢時,能夠快速返回。
由於 Settings.java 對使用 SettingsProvider 進行了封裝,所以,使用起來相當簡單簡潔。由於 Global、Secure、System 三種數據類型的使用是幾乎相同,所以本文就只以 Global為例對查詢插入數據的過程進行分析。
查詢數據
從 SettingsProvider 的 Global 中查詢數據,查詢是否是飛行模式使用方法如下:
String globalValue = Settings.Global.getString(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON);
上面的代碼,用起來代碼量很少,只需要一行代碼即可查詢到所需要的值。深入 Settings.java 看 getString() 方法:
public static String getString(ContentResolver resolver, String name) {
return getStringForUser(resolver, name, UserHandle.myUserId());
}
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.Global"
+ " to android.provider.Settings.Secure, returning read-only value.");
return Secure.getStringForUser(resolver, name, userHandle);
}
return sNameValueCache.getStringForUser(resolver, name, userHandle);
}
這些方法定義在文件 frameworks/base/core/java/android/provider/Settings.java中。
getString() 直接調用了getStringForUser(),getStringForUser() 首先有做一個判斷MOVED_TO_SECURE.contains(name),做這個判斷是因為在 Android 系統的更新中,保存在 Global、Secure、System 三種類型的數據的存放位置有變化,所以需要加這個判斷兼容老版本的使用方法。在章節「封裝 SettingsProvider 接口」中提到,查詢必須經過 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);
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) {
}
}
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();
}
}
}
這個方法定義在文件frameworks/base/core/java/android/provider/Settings.java中。
首先從緩存 mValues 變量中去找,如果沒有查詢到,就調用 SettingsProvider 的 call() 接口,如果 call() 接口也沒有查詢到,再調用 query() 接口。這裡用的是 call() 接口,下文就以 call() 接口往下分析。cp.call() 會調用到 SettingsProvider 的 call()方法,讀者可以閱讀《Android System Server 大綱之 ContentService 和 ContentProvider 原理剖析》了解 ConatentProvider 的這個過程。注意參數 mCallGetCommand。
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;
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
上層傳過來的參數的 command 是 mCallGetCommand,即Settings.CALL_METHOD_GET_GLOBAL,所以調用 getGlobalSetting(),方法,在章節「SettingsProvider 的啟動過程」中可知,getGlobalSetting() 返回的 Setting setting 是封裝了設置項 name、value 等信息的,查看 getGlobalSetting() 方法:
private Setting getGlobalSetting(String name) {
synchronized (mLock) {
return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_GLOBAL,
UserHandle.USER_SYSTEM, name);
}
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
通過 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);
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
通過 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);
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
獲取到 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;
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java 中。
到 getSettingLocked() 最終獲取到 Setting setting,其實這個過程就是章節 「SettingsProvider 的啟動過程」 中的層次關係圖的反映,讀者可以查看這個圖更好理解這個過程。
從SettingsProvider的Global中插入數據,插入飛行模式的使用方法如下:
boolean isSuccess = Settings.System.putInt(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
和查詢一樣,代碼非常簡潔,這個過程和查詢幾乎類似,將快速遊覽這個過程。往下跟蹤:
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);
}
這個方法定義在文件 frameworks/base/core/java/android/provider/Settings.java 中。
和查詢一樣,繼續看 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/core/java/android/provider/Settings.java 中。
直接調用 SettingsProvider 的 call() 接口:
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;
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
首先調用 getSettingValue(args) 獲取對應的設置項,接著 insertGlobalSetting() 方法:
private boolean insertGlobalSetting(String name, String value, int requestingUserId,
boolean forceNotify) {
return mutateGlobalSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT,
forceNotify);
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
直接調用了 mutateGlobalSetting() 方法:
private boolean mutateGlobalSetting(String name, String value, int requestingUserId,
int operation, boolean forceNotify) {
enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
if (isGlobalOrSecureSettingRestrictedForUser(name, callingUserId, value)) {
return false;
}
synchronized (mLock) {
switch (operation) {
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry
.insertSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM,
name, value, getCallingPackage(), forceNotify);
}
.
}
}
return false;
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
首先對使用的進行權限檢查,然後調用 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;
}
這個方法定義在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java 中。
到這裡,就不往下分析了,其實這個即是查詢的反過程,結合章節 「SettingsProvider 的啟動過程」中的層次關係圖,能夠很好理解這個過程。
查閱 SettingsProvider 的設置項不需要聲明任何權限。
第三方 APP 可以通過 framework 的 Settings.java 查詢 SettingsProvider 中的設置項,使用方法查閱章節「查詢數據」。第三 APP 是否可以修改 SettingsProvider 的設置項 Android 系統不允許第三方 APP 修改 SettingsProvider 中的設置項。
查閱 SettingsProvider 的設置項不需要聲明任何權限。
修改 SettingsProvider 需要權限:
對已Global和Secure模塊,還需要關心上文中的 isGlobalOrSecureSettingRestrictedForUser() 方法設置到的限制。
本文從 SettingsProvider 的啟動過程到使用 SettingsProvider 查詢插入數據進行了詳細的過程描述,在 SettingsProvider 的啟動過程中,需要創建資料庫,把默認設置項值寫入到資料庫,把數據中的所有數據,遷移到 xml 文件中。SettingsProvider 的查詢和插入結合章節 「SettingsProvider 的啟動過程」 中的層次關係圖,一目了然。對於第三方 APP 只有讀沒有寫的能力。由於 SettingsProvider 的特性,雖然 SettingsProvider 是跨進程通信,但是由於從多個層次都做了緩存,且 SettingsProvider 中的同步協作機制,只要時間都是花費在 Binder 通信上面,但是 Binder 通信是一種快速的跨進程通信的過程,所以在主線程(UI線程)中可以直接使用 SettingsProvider 查閱插入數據而不會導致UI阻塞導致 ANR(應用程式無響應)。另外,由於 SettingsProvider 的特性和限制,SettingsProvider 不予寫入過多的數據,最好只是系統設置相關的設置項才保存到 SettingsProvider 中,同時也不適合寫入過大的數據,否則將會嚴重影響 SettingsProvider 的性能。
歡迎長按下圖 -> 識別圖中二維碼
或者 掃一掃 關注我的公眾號