SettingsProvider 理解

4,225 阅读6分钟

SettingsProvider我们可以知道它是一个提供设置数据共享的Provider , SettingsProviderAndroid其他系统的Provider有很多不一样的地方。

  • SettingsProvider 只接受 int、float、string等基本类型的数据
  • SettingsProvider 由 Android 系统 framework 进行了封装,使用更加快捷方便
  • SettingsProvider 的数据由键值对组成

SettingsProvider类似于Androidproperties系统:SystemProperties。它除了具备SettingsProvider以上的几个特征之外,还具有几个不同之处:

  • 数据保存方式不同:SystemProperties 的数据保存属性文件中(/system/build.prop等),开机后会被加载到 system properties store;SettingsProvider 的数据保存在文件/data/system/users/0/settings_***.xml 和数据库 settings.db
  • 作用范围不同:SystemProperties可以实现跨进程、跨层次调用,即底层的 c/c++ 可以调用,java 层也可以调用;SettingProvider 只能能在 java 层(APP)使用
  • 公开程度不同:SystemProperties上层第三方App不能使用,SettingsProvider有部分功能上层第三方App可以使用。

概括SettingsProvider的作用:

SettingsProvider包含全局性、系统级别的用户偏好设置。在手机中有一个Settings应用,用户可以在Settings里面做很多设备的设置,这些用户的设置就保存在SettingsProvider里面。比如飞行设置···

在 Android 6.0 版本时,SettingsProvider 被重构,Android 从性能、安全等方面考虑,把 SettingsProvider 中原本保存在 Settings.db 中的数据,目前全部保存在 XML 文件中。

SettingsProvider对数据进行了分类,分别是GlobalSystemSecure 三种类型,他们的区分如下:

  • Global : 所有的偏好设置对系统的所有用户公开,第三方App有只读权限
  • System : 包含各种各样的用户偏好系统设置
  • Secure : 安全性的用户偏好系统设置,第三方App有只读权限
AndroidManifest.xml配置
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.android.providers.settings"
        coreApp="true"
        android:sharedUserId="android.uid.system">

    <application android:allowClearUserData="false"
                 android:label="@string/app_label"
                 android:process="system"
                 android:backupAgent="SettingsBackupAgent"
                 android:killAfterRestore="false"
                 android:restoreAnyVersion="true"
                 android:icon="@mipmap/ic_launcher_settings"
                 android:defaultToDeviceProtectedStorage="true"
                 android:directBootAware="true">

        <provider android:name="SettingsProvider"
                  android:authorities="settings"
                  android:multiprocess="false"
                  android:exported="true"
                  android:singleUser="true"
                  android:initOrder="100"
                  android:visibleToInstantApps="true" />
    </application>
</manifest>

这些代码定义在文件 frameworks/base/packages/SettingsProvider/AndroidManifest.xml中。

上面的 Manifest 配置由 sharedUserId 可知,SettingsProvider 运行在系统进程中,定义的 ContentProvider 实现类是 SettingsProvider,Uri 凭证是 settings。

在Android中,如果要创建自己的内容提供者的时候,需要扩展抽象类ContentProvider,并重写其中定义的各种方法。然后在AndroidManifest.xml文件中注册该ContentProvider即可。ContentProvider是内容提供者,实现Android应用之间的数据交互,对于数据操作,无非也就是CRUD而已。下面是ContentProvider必须要实现的几个方法:

  • onCreate():初始化提供者。
  • query(Uri, String[], String, String[], String):查询数据,返回一个数据Cursor对象。
  • insert(Uri, ContentValues):插入一条数据。
  • update(Uri, ContentValues, String, String[]):根据条件更新数据。
  • delete(Uri, String, String[]):根据条件删除数据。
  • getType(Uri) 返回MIME类型对应内容的URI。
接下来我们去看看SettinsProvider的具体实现

首先我们去看看OnCreate()方法:

@Override
    public boolean onCreate() {
        Settings.setInSystemServer();
        // 如果这些备份设置没有非空验证的话会启动失败
        ensureAllBackedUpSystemSettingsHaveValidators();
        ensureAllBackedUpGlobalSettingsHaveValidators();
        ensureAllBackedUpSecureSettingsHaveValidators();

        synchronized (mLock) {
            mUserManager = UserManager.get(getContext());
            mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
            mPackageManager = AppGlobals.getPackageManager();
            // 1. 首先实例化 HandlerThread  
            mHandlerThread = new HandlerThread(LOG_TAG,
                    Process.THREAD_PRIORITY_BACKGROUND);
            mHandlerThread.start();
            mHandler = new Handler(mHandlerThread.getLooper());
            2. 实例化 SettingsRegistry
            mSettingsRegistry = new SettingsRegistry();
        }
        mHandler.post(() -> {
            registerBroadcastReceivers();
            startWatchingUserRestrictionChanges();
        });
        3. 注册广播 
        ServiceManager.addService("settings", new SettingsService(this));
        ServiceManager.addService("device_config", new DeviceConfigService(this));
        return true;
    }

我们去看看 SettingsRegistry 的实例化过程

 public SettingsRegistry() {
            mHandler = new MyHandler(getContext().getMainLooper());
            mGenerationRegistry = new GenerationRegistry(mLock);
            mBackupManager = new BackupManager(getContext());
            // 重点看下这个方法 
            migrateAllLegacySettingsIfNeeded();
            syncSsaidTableOnStart();
        }
private void migrateAllLegacySettingsIfNeeded() {
            synchronized (mLock) {
                // 返回对应的key 通过 getSettingsFile 生成对应的文件
                // 生成三种文件分别对应
                // /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中。
                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 {
                    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 ,DatabaseHelper 是 SQLiteOpenHelper 的子类
                        // 然后调用 getWritableDatabase() 获取到指向数据库文件的 
                        // SQLiteDatabase实例database。这个过程会调用 DatabaseHelper.onCreate 方法
                        DatabaseHelper dbHelper = new DatabaseHelper(getContext(), userId);
                        SQLiteDatabase database = dbHelper.getWritableDatabase();
                        migrateLegacySettingsForUserLocked(dbHelper, database, userId);

                        // 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);
                }
            }
        }
@Override
    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);

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

        // Load initial volume levels into DB
        loadVolumeLevels(db);

        // Load inital settings values
        loadSettings(db);
    }

这里我们可以看到db.execSQL(“CREATE TABLE system …、createSecureTable()、createGlobalTable() 分别创建 System、Secure、Global、bluetooth_devices、bookmarks这些数据库表。接着调用了 loadVolumeLevels(db) 把系统音量存起来,最后调用 loadSettings(db)

查阅资料知:

loadSettings() 这个方法和 loadVolumeLevels() 方法类似,都是加载很多默认值写入到数据库中,这些默认值很大一部分被定义在文件 frameworks/base/packages/SettingsProvider/res/values/defaults.xml 中,也有一些来自其它地方。总之,loadVolumeLevels()loadSettings() 的作用就是在手机第一次启动时,把手机编好设置的默认值写入到数据库 settings.db中。

db创建完毕后,让我们再回到 migrateAllLegacySettingsIfNeeded() ,我们可以看到这个时候它调用了 migrateLegacySettingsForUserLocked()

  private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper,
                SQLiteDatabase database, int userId) {
            // Move over the system settings.
            final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
            ensureSettingsStateLocked(systemKey);
            SettingsState systemSettings = mSettingsStates.get(systemKey);
            migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);
            systemSettings.persistSyncLocked();
            ···
            // Drop the database as now all is moved and persisted.
            if (DROP_DATABASE_ON_MIGRATION) {
                dbHelper.dropDatabase();
            } else {
                dbHelper.backupDatabase();
            }
        }

ensureSettingsStateLocked 实例化了一个 SettingsState 对象,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);

            if (cursor == null) {
                return;
            }

            try {
                if (!cursor.moveToFirst()) {
                    return;
                }

                final int nameColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.NAME);
                final int valueColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.VALUE);

                settingsState.setVersionLocked(database.getVersion());

                while (!cursor.isAfterLast()) {
                    String name = cursor.getString(nameColumnIdx);
                    String value = cursor.getString(valueColumnIdx);
                    settingsState.insertSettingLocked(name, value, null, true,
                            SettingsState.SYSTEM_PACKAGE_NAME);
                    cursor.moveToNext();
                }
            } finally {
                cursor.close();
            }
        }

在上面这个方法,我们可以看到方法查询数据库中 System 所有的设置,然后在 while 循环中把每个值的信息作为insertSettingLocked() 的参数,insertSettingLocked() 方法如下:

  // The settings provider must hold its lock when calling here.
    @GuardedBy("mLock")
    public boolean insertSettingLocked(String name, String value, String tag,
            boolean makeDefault, boolean forceNonSystemPackage, String packageName) {
        if (TextUtils.isEmpty(name)) {
            return false;
        }

        Setting oldState = mSettings.get(name);
        String oldValue = (oldState != null) ? oldState.value : null;
        String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null;
        Setting newState;

        if (oldState != null) {
           ···
        } else {
            newState = new Setting(name, value, makeDefault, packageName, tag);
            mSettings.put(name, newState);
        }
        ···
        return true;
    }

操作完毕后,我们再回到 migrateLegacySettingsForUserLocked 方法,可以看到

// Drop the database as now all is moved and persisted.
            if (DROP_DATABASE_ON_MIGRATION) {
                dbHelper.dropDatabase();
            } else {
                dbHelper.backupDatabase();
            }
    public void dropDatabase() {
        close();
        // No need to remove files if db is in memory
        if (isInMemory()) {
            return;
        }
        File databaseFile = mContext.getDatabasePath(getDatabaseName());
        if (databaseFile.exists()) {
            SQLiteDatabase.deleteDatabase(databaseFile);
        }
    }

    public void backupDatabase() {
        close();
        // No need to backup files if db is in memory
        if (isInMemory()) {
            return;
        }
        File databaseFile = mContext.getDatabasePath(getDatabaseName());
        if (!databaseFile.exists()) {
            return;
        }
        File backupFile = mContext.getDatabasePath(getDatabaseName()
                + DATABASE_BACKUP_SUFFIX);
        if (backupFile.exists()) {
            return;
        }
        databaseFile.renameTo(backupFile);
    }

我们可以看到这两个方法,一个是删除数据库,一个是更换名字。

SettnigsProvider 启动时会创建 settings.db 数据库,然后把所有的默认设置项写入到数据库,接着会把数据库中所有的设置项从数据库转移到 xml 文件中,随后便会对数据库执行删除操作。

Android 有单独管理 SettingsProvider 的类

路径frameworks/base/core/java/android/provider/Settings

里面封装了 System 、Secure 、Global

插入飞行模式的使用方法

boolean isSuccess = Settings.System.putInt(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);

具体代码实现比较简单,大家追着源码看就好了

如有不足请指出 谢谢大家

参考文章 《Android系统APP之SettingsProvider》