Android中SettingsProvider源碼解析

2022-01-30 郭霖


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 的性能。

歡迎長按下圖 -> 識別圖中二維碼

或者 掃一掃 關注我的公眾號

相關焦點

  • Android 上百實例源碼分析以及開源分析
    23、從android中調用web service的源碼24、從網絡上獲取圖片,主要通過InputStream,FileOutputStream,HttpURLConnection實現。51、簡單的訂機票系統源碼用調用webservice獲取xml,將獲得xml再解析出來52、獲取Android系統程序信息53、加載網頁進度條,標題欄顯示網頁標題並且滾動,並且用進度條顯示網頁的加載進度(重新自定義標題欄。詳細見代碼。54、仿大眾點評源碼,只是簡單的實現了UI,沒有與伺服器交流。學好Ui非常好的例子。
  • App安全評估手冊-Android
    :展示content provider URI的各列app.provider.delete :刪除content provider URI的內容app.provider.download :使用openInputStream讀取指定uri的內容,並下載在電腦中app.provider.info :獲取 content providers信息app.provider.insert :插入數據到content
  • Android Study之跳轉自啟動管理頁
    介紹實現關鍵類1.獲取手機型號(Build)所屬包: android.os.Build作用(含義): 從系統屬性中提取設備硬體和版本信息靜態屬性:1.1 BOARD 主板:        1.20 USER2.打開其他應用程式中的Activity或服務(ComponentName)所屬包: android.content.ComponentName構造方法使用方式如下:2.1 傳遞當前上下文和將要跳轉的類名;2.2
  • 史上最全 | Android 常用 adb 命令總結
    pm 的源碼 Pm.java , 直接運行 adb shell pm 可以獲取到該命令的幫助信息。, 發送按鍵事件,KeyEvent.javaadb shell input keyevent KEYCODE_HOME模擬按下 Home 鍵 ,源碼裡面有定義:public static final int KEYCODE_HOME = 3;因此可以將命令中的 KEYCODE_HOME 替換為 3
  • 阿里 ARouter 全面解析,總有你沒了解的
    ,只不過預處理服務是早於攔截器的,等到分析源碼的時候我們分析他們的具體區別。示例代碼https://github.com/xiangcman/ArouterApphttps://github.com/alibaba/ARouter這節主要圍繞Arouter源碼的設計,以及通過源碼我們能學習到什麼,以及如何應對面試過程中Arouter的問題。
  • 了解一下,Android 10中鏡像文件的製作
    Android 10後,AOSP內置了android verified boot(代碼中叫AVB,或小寫的avb),即啟動校驗。這個應該是配合動態分區來做的。啟動校驗肯定一直就有,但之前都是設備廠商/晶片廠商自己實現的。現在AOSP提供了AVB框架,貌似還支持Android Things相關系統。
  • Android ANR日誌分析進行曲
    ANR出現的原因1:主線程頻繁進行耗時的IO操作:如資料庫讀寫(UI線程等待其它線程釋放某個鎖,導致UI線程無法處理用戶輸入);2:多線程操作的死鎖,主線程被block;3:主線程被Binder 對端block;4:System Server中WatchDog
  • 解析Android上強大的圖表庫MPAndroidChart—某些人的福音
    74款APP完整源碼!Android第一神器—Xposed框架,堪稱黑科技,功能強大!來源:http://www.jianshu.com/p/c6e8ea5e9ba0既然有這樣的外部需求,Android世界裡肯定要有圖表庫才行,今天解析的就是其中最強大的一個MPAndroidChart。
  • 字節碼插件平臺 ByteX 源碼解析
    作者:今日Android連結:https://www.jianshu.com/p/090ecbc9e6ed寫這篇文章的目的有三個原因,首先在 ByteX 的交流群裡有同學反饋源碼不易閱讀,看起來比較費力,所以希望通過自己的理解和梳理能夠幫助大家學習 ByteX 的源碼。
  • 下載安裝APK(兼容Android7.0)
    上面用到的代碼中的Uri.fromFile 其實就是生成一個file://URL。使用FileProvider使用FileProvider的大致步驟如下:第一步:在AndroidManifest.xml清單文件中註冊provider,因為provider也是Android四大組件之一,可以簡單把它理解為向外提供數據的組件,這種組件在實際開發中用的頻率並不高,四大組件都可以在清單文件中進行配置
  • Android TV機頂盒監聽遙控器全局按鍵
    frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java      ...AccessibilityService中有一個onKeyEvent函數,凡是系統接收到的所有按鍵事件,都會優先路由到此,HOME、TV之類的全局鍵自然不在話下。
  • Android用戶態啟動流程分析
    其代碼在 system/core/init/init.cpp[2],這裡也不做源碼複讀機了,而是根據自己的閱讀理解介紹其工作過程,以及背後一些可能會被忽略的點。按照執行流程,init 實際上被執行了 4 次,分別是:int main(int argc, char** argv) { if (!
  • 如何方便快速的整編Android 9.0系統源碼?
    前言在上一篇文章是時候下載Android 9.0系統源碼了中,我們順利的將AOSP下載了下來,很多時候我們不僅僅需要去查看源碼,還有以下的幾個需求:為了實現這些需求,就需要我們去編譯系統源碼。1.編譯系統概述了解以下一些概念,會對Android編譯系統有大概的了解。
  • Android逆向之旅---動態方式破解apk前奏篇(Eclipse動態調試smail源碼)
    源碼這裡不解釋了,網上有介紹。1》修改AndroidManifest.xml中的android:debuggable="true"這個是smali語法的,其實對應的Java代碼就是:android.os.Debug.waitForDebugger();
  • 玩轉Android10源碼開發定製(16)LineageOS中編譯user模式的系統
    一、安卓系統編譯選項簡介android編譯的時候可以選擇編譯選項 eng、user 和 userdebug。1.eng編譯選項(1).二、lineageOs中編譯user選項系統lineageOs源碼中,官方提供的編譯方式命令如下(oneplus3說明):source build/envsetup.shbreakfast oneplus3brunch oneplus3
  • 神器 celery 源碼解析 - 7
    大家好,我是肖恩,源碼解析每周見Celery是一款非常簡單、靈活、可靠的分布式系統,可用於處理大量消息,並且提供了一整套操作此系統的工具。Celery 也是一款消息隊列工具,可用於處理實時數據以及任務調度。
  • 了解一下,Android 10中的ART虛擬機(2)
    緣起接著上期」了解一下,Android 10中的ART虛擬機(I)「,今天繼續介紹ART。
  • Android 安全更新的發展與沿革
    >> Treble 項目https://source.android.com/devices/architecture/treble>> 更輕鬆、更快速地升級至 Android Phttps://arstechnica.com/gadgets/2018/06/talkin-treble-how-android-engineers-are-winning-the-war-on-fragmentation
  • HIDL詳解-Android10.0 HwBinder通信原理(二)
    italics 是一個令牌系列,如 integer 或 identifier(標準 C 解析規則)。constexpr 是 C 樣式的常量表達式(例如 1 + 1 和 1L << 3)。import_name 是軟體包或接口名稱,按 HIDL 版本編號中所述的方式加以限定。小寫 words 是文本令牌。