SettingsProvider簡單分析

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);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章