Android系统APP之SettingsProvider

news2024/11/26 5:30:54

前言

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概览

主要源码

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三种类型,它们的区别如下:

  • Global:所有的偏好设置对系统的所有用户公开,第三方APP有读没有写的权限;
  • System:包含各种各样的用户偏好系统设置;
  • Secure:安全性的用户偏好系统设置,第三方APP有读没有写的权限。
    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即运行SettingsProvider,和打开一个Activity类似,会回调ContentProvider的生命周期方法,首先的,会调用OnCreate()方法,如下:
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java中。

public boolean onCreate() {
    synchronized (mLock) {
        ......
        mHandlerThread = new HandlerThread(LOG_TAG,
                Process.THREAD_PRIORITY_BACKGROUND);
        mHandlerThread.start();
        mSettingsRegistry = new SettingsRegistry();
    }
    registerBroadcastReceivers();
    startWatchingUserRestrictionChanges();
    return true;
}

上面的代码首先是实例化一个HandlerThread的实例mHandlerThread,优先级为Process.THREAD_PRIORITY_BACKGROUND,下文会用到。然后实例化SettingsRegistry的实例mSettingsRegistry,这一步很重要。接着会注册广播接收器,所关心的广播包括设备用户变化以及APP卸载的广播,设备用户的变化对大多数地方使用SettingProvider的影响不是很大,本文就不再阐述和用户变化相关的内容了。但是APP卸载这里需要关注一下,当一个APP有数据保存在SettingsProvider时,APP被卸载后,被卸载的APP设置的所有数据都会被清除。回到SettingsRegistry的实例化过程,构造方法如下:
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java中。

    final class SettingsRegistry {
        private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid";

        private static final String SETTINGS_FILE_GLOBAL = "settings_global.xml";
        private static final String SETTINGS_FILE_SYSTEM = "settings_system.xml";
        private static final String SETTINGS_FILE_SECURE = "settings_secure.xml";
        private static final String SETTINGS_FILE_SSAID = "settings_ssaid.xml";
        private static final String SETTINGS_FILE_CONFIG = "settings_config.xml";

        public SettingsRegistry() {
        	.....
            migrateAllLegacySettingsIfNeeded();
        }

migrateAllLegacySettingsIfNeeded()方法,从命名上是迁移settings数据,迁移什么数据呢?从哪里迁移到哪里呢?继续往下看:
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java中。

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);
        
        // Upgrade to the latest version.
        UpgradeController upgrader = new UpgradeController(userId);
        upgrader.upgradeIfNeededLocked();
        ......
    }
}

上面的代码首先是调用了makeKey()方法,所谓makeKey()就是和上文中的数据分类小章节中提到的System、Global和Secure三种key。然后调用getSettingsFile()方法获取到一个File对象的实例,如下:
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java中。

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

上面的代码中对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指南Android SQLite API 指南。

frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java中。

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);
    }
    ......
    // Load initial volume levels into DB
    loadVolumeLevels(db);
    // Load inital settings values
    loadSettings(db);
}

这个方法调用db.execSQL("CREATE TABLE system …、createSecureTable()、createGlobalTable()分别创建System、Secure、Global三个数据库表,这个和上文中数据分类章节中的内容一致。接着调用loadVolumeLevels(db)方法,把默认的铃声音量、音乐音量、通知音量以及震动设置等等写入到数据库的System表格中。处理完后调用方法loadSettings(db),如下:
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java中。

private void loadSettings(SQLiteDatabase db) {
    loadSystemSettings(db);
    loadSecureSettings(db);
    // The global table only exists for the 'owner/system' user
    if (mUserHandle == UserHandle.USER_SYSTEM) {
        loadGlobalSettings(db);
}

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

DatabaseHelper的onCreate()方法执行完毕后,这里又回到migrateAllLegacySettingsIfNeeded()方法中,DatabaseHelper创建完毕后,继续调用migrateLegacySettingsForUserLocked()方法,如下:
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java中。

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(systemKey),如下:
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java中。

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

上面代码实例化一个SettingsState对象,这个对象指向文件/data/system/users/0/settings_system.xml,然后把settingsState放置在对象mSettingsStates中。回到migrateLegacySettingsForUserLocked()方法,ensureSettingsStateLocked()执行完毕后,调用migrateLegacySettingsLocked()方法,如下:
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java中。

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()的参数,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放置到ArrayMap<String, 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的实例。它们的层次关系如下图:

20170301143940227.png

再次回到方法migrateLegacySettingsForUserLocked(),在把数据中的数据转移到xml文件后,执行下面这段代码:

// Drop the database as now all is moved and persisted.
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接口
由章节前言中的描述,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原理剖析》了解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);
上面的代码,用起来代码量很少,只需要一行代码即可查询到所需要的值。深入Settings.java看getString()方法:

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

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

这个方法定义在文件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) {
    // Get the value.
    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) {
    // 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;
}

这个方法定义在文件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的启动过程”中的层次关系图,能够很好理解这个过程。

第三方APP使用SettingsProvider
第三方APP可以通过framework的Settings.java查询SettingsProvider中的设置项,使用方法查阅章节“查询数据”。第三APP是否可以修改SettingsProvider的设置项?Android系统不允许第三方APP修改SettingsProvider中的设置项。

权限问题
查阅SettingsProvider的设置项不需要声明任何权限。

修改SettingsProvider需要权限:

android.permission.WRITE_SETTINGS,Protection level: signature
Secure数据:android.permission.WRITE_SECURE_SETTINGS,Not for use by third-party applications.
对已Global和Secure模块,还需要关心上文中的isGlobalOrSecureSettingRestrictedForUser()方法设置到的限制。

总结
本文从SettingsProvider的启动过程到使用SettingsProvider查询插入数据进行了详细的过程描述,在SettingsProvider的启动过程中,需要创建数据库,把默认设置项值写入到数据库,把数据中的所有数据,迁移到xml文件中。SettingsProvider的查询和插入结合章节“SettingsProvider的启动过程”中的层次关系图,一目了然。对于第三方APP只有读没有写的能力。由于SettingsProvider的特性,虽然SettingsProvider是跨进程通信,但是由于从多个层次都做了缓存,且SettingsProvider中的同步协作机制,只要时间都是花费在Binder通信上面,但是Binder通信是一种快速的跨进程通信的过程,所以在主线程(UI线程)中可以直接使用SettingsProvider查阅插入数据而不会导致UI阻塞导致ANR(应用程序无响应)。另外,由于SettingsProvider的特性和限制,SettingsProvider不予写入过多的数据,最好只是系统设置相关的设置项才保存到SettingsProvider中,同时也不适合写入过大的数据,否则将会严重影响SettingsProvider的性能。

原作者:FamilyYuan
链接:https://www.jianshu.com/p/d48977f220ee

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/835644.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

喜讯!箱讯AnyCase荣获“2023年度苏州市服务型制造示范平台”

近日&#xff0c;苏州市工业和信息化局公示了“2023年度苏州市服务型制造示范企业&#xff08;平台&#xff09;”名单。箱讯科技&#xff08;上海&#xff09;有限公司子公司苏州箱讯供应链管理有限公司荣耀上榜。 ​ 添加图片注释&#xff0c;不超过 140 字&#xff08;可选…

IFC纹理及着色器研究

最近&#xff0c;yorgunkirmizi 讨论了纹理、着色器、纹理坐标以及所有此类内容在 IFC 中的工作原理。 我们在破译什么是可能的、什么是不可能的方面已经取得了一些重大进展&#xff0c;所以我想我应该打开这个线程&#xff0c;以便其他人也可以参与其中&#xff0c;或者至少密…

恒运资本:货币调控精准有力 8月流动性合理充裕

8月3日&#xff0c;中国人民银行以利率投标方式展开30亿元逆回购操作&#xff0c;由于当日有1140亿元逆回购到期&#xff0c;公开商场完成净回笼1110亿元。 专家表示&#xff0c;为坚持流动性合理富余&#xff0c;估计央即将根据流动性供求和商场利率改变&#xff0c;灵敏运用多…

【雕爷学编程】MicroPython动手做(39)——机器视觉之图像基础

MixPY——让爱(AI)触手可及 MixPY布局 主控芯片&#xff1a;K210&#xff08;64位双核带硬件FPU和卷积加速器的 RISC-V CPU&#xff09; 显示屏&#xff1a;LCD_2.8寸 320*240分辨率&#xff0c;支持电阻触摸 摄像头&#xff1a;OV2640&#xff0c;200W像素 扬声器&#…

阿里云平台WoSignSSL证书应用案例

沃通CA与阿里云达成合作并在阿里云平台上线WoSign品牌SSL证书。自上线以来&#xff0c;WoSignSSL证书成为阿里云“数字证书管理服务”热销证书产品&#xff0c;获得阿里云平台客户认可&#xff0c;助力阿里云平台政府、金融、教育、供应链、游戏等各类行业客户实现网站系统数据…

美团前端研发框架Rome实践和演进趋势

本文整理自美团技术沙龙第76期《大前端研发协同效能提升与实践》&#xff0c;为大家介绍了美团到店前端研发框架Rome实践和演进趋势。 具体来讲&#xff0c;本文首先介绍了Rome整体的工程生态、演变路径、规模化升级以及工程框架外的开发辅助工具&#xff1b;第二部分&#xff…

主流CRM有哪些特点和优势?

现如今&#xff0c;CRM系统是企业实现数字化转型&#xff0c;提高销售收入的首选工具。但市场上有众多CRM品牌&#xff0c;每家都有自己的特点和优势&#xff0c;企业该如何进行选择&#xff1f;下面我们就来进行主流CRM系统比较&#xff0c;并说说什么CRM产品比较好? 主流CR…

控制器(IP盒子类似网关)收不到工位板的状态数据包的问题排查解决

控制器(IP盒子类似网关)收不到工位板的状态数据包 问题描述 如下图通信框图所示&#xff0c;控制器工位板程序通过RS422和控制器(类似网关)通信&#xff0c;控制器在将数据转发给Linux应用程序。 一开始设备装好&#xff0c;整个通信是没有任何问题的。 然后在很久之后&…

【C#学习笔记】装箱和拆箱

文章目录 装箱和拆箱性能消耗装箱拆箱 比较var&#xff0c;object&#xff0c;dynamic&#xff0c;\<T\>varobject\<T\> 泛型dynamic 装箱和拆箱 在讲引用类型object的时候&#xff0c;我们说它是万能的&#xff0c;却没说它万能在哪里。 除了object为每一种变量…

收集 301 医院 451 名老年冠心病患者数据,湖北麻城人民医院推出机器学习模型,准确预测患者一年内死亡率

内容一览&#xff1a;据国际糖尿病联盟 (IDF) 统计&#xff0c;2021 年中国糖尿病患者数量占全球 26%。而糖尿病患者血糖长期失控&#xff0c;有极高风险引起冠心病等并发症。近期&#xff0c;湖北省麻城市人民医院研究人员分析比较了多种模型&#xff0c;并用其中表现最优的机…

Scratch 之 两点之间距离的测算

1.前言 在Scratch中&#xff0c;对于坐标系上的两点&#xff0c;我们可以确定通过x坐标或y坐标之差确定两点横坐标或是纵坐标上的距离&#xff0c;那么如何知道两点之间的直线距离呢&#xff1f; 2.勾股定理 对于一个直角三角形&#xff0c;两条直角边的平方和等于斜边的平方&a…

电动自行车上架eBay的UL2849、16CFR1512测试标准

在奥运经济的带动下&#xff0c;今年以来运动自行车消费有较大幅度增长&#xff0c;其中高端消费者对进口自行车需求扩张&#xff0c;上半年竞赛型自行车进口量同比增长49.5%。另外&#xff0c;电助力自行车在国际市场也倍受追捧&#xff0c;国际自行车贸易总额的60%来自中国&a…

记一次ubuntu16误删libc.so.6操作的恢复过程

背景 操作系统&#xff1a;ubuntu16 glibc版本&#xff1a;2.23 修改原因&#xff1a; 经过一系列报错和手工构建之后&#xff0c;vulkansdk成功安装&#xff08;起码运行./vulkansdu成功&#xff09;&#xff0c;在进行./vulkaninfo进行验证时&#xff0c;报错&#xff1a…

SpringCloud-Hystrix服务熔断与降级工作原理源码 | 京东物流技术团队

先附上Hystrix源码图 在微服务架构中&#xff0c;根据业务来拆分成一个个的服务&#xff0c;服务与服务之间可以相互调用&#xff08;RPC&#xff09;&#xff0c;在Spring Cloud可以用RestTemplateRibbon和Feign来调用。为了保证其高可用&#xff0c;单个服务通常会集群部署。…

odoo16 上传/下载 文件接口的实现

突然有个需求说需要编写一个上传pdf 接口 首先需要准备如下 xx.xx模型 module 部分 如下&#xff1a; attachment_count fields.Integer(compute_compute_attachment_count, string附件数量, requiredTrue)def _compute_attachment_count(self):# 附件数量计算attachment_dat…

虹科干货 | DevOps 团队为什么独独青睐 Redis Enterprise ?

虹科干货 | DevOps 团队为什么独独青睐 Redis Enterprise &#xff1f; 快速部署是保障成功的 DevOps 的关键要素。虹科Redis Enterprise 提供了一种快速的数据库。 DevOps 团队面临的挑战 提高应用程序处理速度&#xff0c;赢得商业竞争 许多企业中&#xff0c;DevOps 团队&…

工厂模式:简化对象的创建过程

工厂模式&#xff1a;简化对象的创建过程 介绍 在软件开发中&#xff0c;对象的创建是一个常见的操作。通常情况下&#xff0c;我们可以直接使用 new 关键字来创建对象&#xff0c;但是在某些情况下&#xff0c;对象的创建过程可能会比较复杂&#xff0c;涉及到多个步骤或者依…

为生成式AI提速,亚马逊云科技Amazon EC2 P5满足GPU需求

生成式AI&#xff08;Generative AI&#xff09;已经成为全球范围内的一个重要趋势&#xff0c;得到越来越多企业和研究机构的关注和应用。纽约时间7月26日&#xff0c;亚马逊云科技数据库、数据分析和机器学习全球副总裁Swami Sivasubramanian在亚马逊云科技举办的纽约峰会上更…

React Native获取手机屏幕宽高(Dimensions)

import { Dimensions } from react-nativeconsole.log(Dimensions, Dimensions.get(window)) 参考链接&#xff1a; https://www.reactnative.cn/docs/next/dimensions#%E6%96%B9%E6%B3%95 https://chat.xutongbao.top/

程序员自由创业周记#5:加一上线

程序员自由创业周记#5&#xff1a;加一上线 这是一位程序员进行独立开发创业的记录&#xff0c;将分享创业过程中的所思所想以及收支明细。 充实 如果说程序员独立创业的成功率只有5%&#xff0c;那如果家里有一位3岁多还没上幼儿园的小朋友要照顾&#xff0c;成功的概率至少还…