Android 11.0 SettingsProvider 源码分析

文章目录

    • 一、SettingsProvider 的概述
    • 二、SettingsProvider 的启动流程
    • 三、对 SettingsProvider 进行操作方法
    • 四、客制化示例


一、SettingsProvider 的概述

SettingsProvider 是一个为 Android 系统设置提供数据共享的 Provider,它包含全局、安全和系统级别的用户偏好设置。并且,它是跟随 framework 一起编译,以apk的形式内置到系统的应用程序,所以源码的位置在:

frameworks\base\packages\SettingsProvider

数据分类:
SettingsProvider 对数据进行了分类,主要分为 Global、System 和 Secure 三种类型:

  • Global:全局偏好设置 ,对系统中所有用户公开;
  • System:系统偏好设置
  • Secure:安全偏好设置。

此外,为了方便对数据的操作,系统对 SettingsProvider 的一些接口进行封装处理。在 Settings 类中,分别声明了 Global、Secure、System 三个静态内部类,分别对应上述的三种数据类型。通过调用 这三个内部类中定义好的方法及属性,就可以对系统设置的数据进行相应的操作。 Settings 类的路径在:

frameworks\base\core\java\android\provider\Settings.java

下面是它的部分代码:



/**
 * The Settings provider contains global system-level device preferences.
 */
public final class Settings {

	……	
	//内部类 System
    public static final class System extends NameValueTable {
        private static final float DEFAULT_FONT_SCALE = 1.0f;
        
       public static String getString(ContentResolver resolver, String name) {
            return getStringForUser(resolver, name, resolver.getUserId());
        }

        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.System"
                        + " to android.provider.Settings.Secure, returning read-only value.");
                return Secure.getStringForUser(resolver, name, userHandle);
            }
            if (MOVED_TO_GLOBAL.contains(name) || MOVED_TO_SECURE_THEN_GLOBAL.contains(name)) {
                Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
                        + " to android.provider.Settings.Global, returning read-only value.");
                return Global.getStringForUser(resolver, name, userHandle);
            }
            return sNameValueCache.getStringForUser(resolver, name, userHandle);
        }

        public static boolean putString(ContentResolver resolver, String name, String value) {
            return putStringForUser(resolver, name, value, resolver.getUserId());
        }

        public static boolean putStringForUser(ContentResolver resolver, String name, String value,
                int userHandle) {
            if (MOVED_TO_SECURE.contains(name)) {
                Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
                        + " to android.provider.Settings.Secure, value is unchanged.");
                return false;
            }
            if (MOVED_TO_GLOBAL.contains(name) || MOVED_TO_SECURE_THEN_GLOBAL.contains(name)) {
                Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
                        + " to android.provider.Settings.Global, value is unchanged.");
                return false;
            }
            return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle);
        }
        ……
	}
	
	……
	//内部类 Secure
    public static final class Secure extends NameValueTable {
        public static final Uri CONTENT_URI =
            Uri.parse("content://" + AUTHORITY + "/secure");

        @UnsupportedAppUsage
        private static final ContentProviderHolder sProviderHolder =
                new ContentProviderHolder(CONTENT_URI);
        ……        
        }               

	……
	//内部类 Global
    public static final class Global extends NameValueTable {
        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global");

        public static final String NOTIFICATION_BUBBLES = "notification_bubbles";

        private static final Validator NOTIFICATION_BUBBLES_VALIDATOR = BOOLEAN_VALIDATOR;

		……
 }       
 

数据储存位置及方式:

Android 6.0(M) 以前:

/data/data/com.android.providers.settings/databases/settings.db

Android 6.0(M) 及其以后:

/data/system/users/ <user_id>/settings_system.xml
/data/system/users/ <user_id>/settings_global.xml
/data/system/users/<user_id>/settings_secure.xml

数据存储方式:SettingsProvider只接受 int、float 和 String 等类型的数据,且这些数据最终都会被转换为 String 类型,然后以键-值对的形式存放在对应的 xml 文件中。

改变存储方式的目的主要有以下三点:

  • 提高系统性能(400ms降低到10ms);
  • 为每一个用户独立储存偏好设置;
  • 限制了第三方应用对偏好设置的写入,增加了安全性。

此外,除了上述三个数据文件以外,在8.0以后,用户所安装的每个 APP (package) 都会产生一组独立的 ID (SSAID)和对应的描述,这些数据被存放在 “/data/system/users/<user_id>/settings_ssaid.xml"。下面是系统用户 0 存放上述 xml 文件的示例图 :

在这里插入图片描述

二、SettingsProvider 的启动流程

程序启动流程图:

img-blog.csdnimg.cn/d91f8c009f084dfc9c9f3324f1995430.png)

1、SettingsProvider 是一个系统 Provider,和其他系统 Provider 的启动流程一样,它会在 SystemServer 启动系统服务的过程中被安装进系统。以下是实现代码:

FilePath: frameworks/base/services/java/com/android/server/SystemServer.java


    /**
     * Starts a miscellaneous grab bag of stuff that has yet to be refactored and organized.
     */
    private void startOtherServices() {
   		……
    	traceBeginAndSlog("InstallSystemProviders");
        mActivityManagerService.installSystemProviders();
        // Now that SettingsProvider is ready, reactivate SQLiteCompatibilityWalFlags
        SQLiteCompatibilityWalFlags.reset();
        traceEnd();
        ……
  } 
  

上述代码中,在 SystemServer 启动系统服务的 startOtherServices() 方法中,通过调用 ActivityManagerService 的 installSystemProviders() 方法完成 SettingsProvider 的安装和创建。

2、ActivityManagerService 的 installSystemProviders() 方法。

FilePath: frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java


public class ActivityManagerService extends IActivityManager.Stub
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
        ……
    public final void installSystemProviders() {
		// 1、获取系统中所有的 Provider。这个过程最终会通过调用包管理模块 PackageManagerService 中的
        // queryContentProviders() 方法来获取所有的 Provider
        List<ProviderInfo> providers;
        synchronized (this) {
            ProcessRecord app = mProcessList.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);
        mActivityTaskManager.installSystemProviders();
        mDevelopmentSettingsObserver = new DevelopmentSettingsObserver();
        SettingsToPropertiesMapper.start(mContext.getContentResolver());
        mOomAdjuster.initSettings();

        // Now that the settings provider is published we can consider sending
        // in a rescue party.
        
        //3、处理 SettingsProvider 安装之前某些依赖程序
        RescueParty.onSettingsProviderPublished(mContext);

        //mUsageStatsService.monitorPackages();
    }
  ……
  

其中,上述代码第二步调用到 ActivityThread 的 installSystemProviders() 方法涉及到比较复杂的处理逻辑,和 SettingsProvider 的启动流程关系不大,这里就不做分析了,但它最终是会调用到 SettingsProvider 中的 onCreate() 方法。

3、SettingsProvider 中的 onCreate() 方法。

FilePath: frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java


@SuppressWarnings("deprecation")
public class SettingsProvider extends ContentProvider {
……
    @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();
            //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(() -> {
            //3、注册广播接收器
            registerBroadcastReceivers();
            startWatchingUserRestrictionChanges();
        });
        //4、向系统添加SettingsService、DeviceConfigService 服务
        ServiceManager.addService("settings", new SettingsService(this));
        ServiceManager.addService("device_config", new DeviceConfigService(this));
        return true;
    }
……
    

上述中的关键部分是创建 SettingsRegistry 对象,而 SettingsRegistry 是SettingsProvider 的内部类。

4、内部类 SettingsRegistry

FilePath: 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";

        private static final String SSAID_USER_KEY = "userkey";

        private final SparseArray<SettingsState> mSettingsStates = new SparseArray<>();

        private GenerationRegistry mGenerationRegistry;

        private final Handler mHandler;

        private final BackupManager mBackupManager;

        private String mSettingsCreationBuildId;

        public SettingsRegistry() {
            mHandler = new MyHandler(getContext().getMainLooper());
            //1、创建 GenerationRegistry 对象,对xml文件的更改做版本管理
            mGenerationRegistry = new GenerationRegistry(mLock);
            //2、创建 BackupManager 对象,系统备份
            mBackupManager = new BackupManager(getContext());
            //3、迁移所有的系统设置数据
            migrateAllLegacySettingsIfNeeded();
            syncSsaidTableOnStart();
        }
……
}

在 SettingsRegistry 的构造方法中,注释1处创建了一个GenerationRegistry对象,GenerationRegistry对象的核心作用类似于对xml文件的更改做版本管理。
注释2处创建BackupManager对象,和系统备份有关。
注释3是迁移所有的系统设置数据,是重点方法。

5、migrateAllLegacySettingsIfNeeded()方法的分析:

FilePath: 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);
                //1、判断"settings_global.xml"文件是否存在
                //存在,则不做数据迁移
                //不存在,则要做数据迁移
                //文件不存在的情况有:1.机器首次启动;2.恢复出厂设置后的首次开机;3.系统版本低于6.0
                if (SettingsState.stateFileExists(globalFile)) {
                    return;
                }

                mSettingsCreationBuildId = Build.ID;

                final long identity = Binder.clearCallingIdentity();
                try {
                    //2、获取系统中所有的用户(多用户,一般用户0)
                    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;

                        //3、创建数据库 settings.db 和数据表,然后将 系统默认设置 加载到数据表中(临时数据库)
                        DatabaseHelper dbHelper = new DatabaseHelper(getContext(), userId);
                        SQLiteDatabase database = dbHelper.getWritableDatabase();
                        //4、生成xml文件,并迁移数据库数据到文件中
                        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);
                }
            }
        }
        

注释1处先判断"/data/system/users/0/settings_global.xml"文件是否存在,如果不存在,migrateAllLegacySettingsIfNeed()方法会直接返回,即不做后续的数据迁移操作,而文件不存在的情况一般发生机器首次启动或恢复出厂设置后的第一次开机。如果存在,则对系统设置做数据迁移。
注释2处是获取系统中所有用户,并通过for循环遍历对每个用户执行数据迁移的过程。
注释3处通过 DatabaseHelper 类创建了数据库和数据表,并使用默认设置对数据库表数据初始化。此外,这里数据库是一个临时数据库。之所以创建这个临时数据库,是为了兼容 Android 6.0 以前的版本而设计。
注释4处的代码是为用户生成 xml 文件,并将数据库数据迁移到 xml 文件的核心代码。

6、数据库创建和初始化的核心:DatabaseHelper

构造方法 DatabaseHelper() 中执行了dbNameForUser() 方法,SQLiteOpenHelper 创建了数据库 settings.db 。
在 settings.db 数据库创建后,会回调onCreate() 方法,并在方法中创建了数据表。

FilePath: frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java


class DatabaseHelper extends SQLiteOpenHelper {
    private static final String TAG = "SettingsProvider";
    private static final String DATABASE_NAME = "settings.db";

    // Please, please please. If you update the database version, check to make sure the
    // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
    // is properly propagated through your change.  Not doing so will result in a loss of user
    // settings.

	//此处省略
	……

	//获取数据库名称/路径
    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();
        }
    }

    public DatabaseHelper(Context context, int userHandle) {
    	//创建数据库
        super(context, dbNameForUser(userHandle), null, DATABASE_VERSION);
        mContext = context;
        mUserHandle = userHandle;
    }

	//此处省略isValidTable()、 isInMemory()、dropDatabase()、backupDatabase()
    ……
    
    private void createSecureTable(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE secure (" +
                "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
                "name TEXT UNIQUE ON CONFLICT REPLACE," +
                "value TEXT" +
                ");");
        db.execSQL("CREATE INDEX secureIndex1 ON secure (name);");
    }

    private void createGlobalTable(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE global (" +
                "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
                "name TEXT UNIQUE ON CONFLICT REPLACE," +
                "value TEXT" +
                ");");
        db.execSQL("CREATE INDEX globalIndex1 ON global (name);");
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    	//1、创建 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);");

		//2、创建 secure 数据表
        createSecureTable(db);

		//3、为用户0创建 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);
        }
		
		//4、将初始音量加载到DB中
        // Load initial volume levels into DB
        loadVolumeLevels(db);
		//5、将默认设置数据加载到数据表中
        // Load inital settings values
        loadSettings(db);
    }
    ……
    

上述代码注释1、2、3处分别为用户创建system、secure和global三张数据表。此外,注释3处多了一个判断,这是因为 “settings_global.xml” 文件是所有用户共享的,所以只存储在0用户下。
注释4处的 loadVolumeLevels() 和 loadSettings() 方法是将默认数据填充数据表中。

以下是 loadSettings( ) 方法的内容:

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

    private void loadSystemSettings(SQLiteDatabase db) {
        SQLiteStatement stmt = null;
        try {
            stmt = db.compileStatement("INSERT OR IGNORE INTO system(name,value)"
                    + " VALUES(?,?);");

            loadBooleanSetting(stmt, Settings.System.DIM_SCREEN,
                    R.bool.def_dim_screen);
            loadIntegerSetting(stmt, Settings.System.SCREEN_OFF_TIMEOUT,
                    R.integer.def_screen_off_timeout);

            // Set default cdma DTMF type
            loadSetting(stmt, Settings.System.DTMF_TONE_TYPE_WHEN_DIALING, 0);

            // Set default hearing aid
            loadSetting(stmt, Settings.System.HEARING_AID, 0);

            // Set default tty mode
            loadSetting(stmt, Settings.System.TTY_MODE, 0);

            loadIntegerSetting(stmt, Settings.System.SCREEN_BRIGHTNESS,
                    R.integer.def_screen_brightness);

            loadIntegerSetting(stmt, Settings.System.SCREEN_BRIGHTNESS_FOR_VR,
                    com.android.internal.R.integer.config_screenBrightnessForVrSettingDefault);

            loadBooleanSetting(stmt, Settings.System.SCREEN_BRIGHTNESS_MODE,
                    R.bool.def_screen_brightness_automatic_mode);

            loadBooleanSetting(stmt, Settings.System.ACCELEROMETER_ROTATION,
                    R.bool.def_accelerometer_rotation);

            loadDefaultHapticSettings(stmt);

            loadBooleanSetting(stmt, Settings.System.NOTIFICATION_LIGHT_PULSE,
                    R.bool.def_notification_pulse);

            loadUISoundEffectsSettings(stmt);

            loadIntegerSetting(stmt, Settings.System.POINTER_SPEED,
                    R.integer.def_pointer_speed);

            loadIntegerSetting(stmt, Settings.System.SCREENSHOT_BUTTON_SHOW,
                    R.integer.def_screenshot_button_show);

            /*
             * IMPORTANT: Do not add any more upgrade steps here as the global,
             * secure, and system settings are no longer stored in a database
             * but are kept in memory and persisted to XML.
             *
             * See: SettingsProvider.UpgradeController#onUpgradeLocked
             */
        } finally {
            if (stmt != null) stmt.close();
        }
    }
    

在上述 loadSystemSettings() 方法中,加载了很多 defaults.xml 文件中定义的与系统设置相关的默认值。在此期间,这些默认值会被加载到相应数据表中。以下是 defaults.xml 文件的部分内容:

FilePath: frameworks/base/packages/SettingsProvider/res/values/defaults.xml


<?xml version="1.0" encoding="utf-8"?>
……
<resources>
    <bool name="def_dim_screen">true</bool>
    <integer name="def_screen_off_timeout">60000</integer>
    <integer name="def_sleep_timeout">-1</integer>
    <bool name="def_airplane_mode_on">false</bool>
    <bool name="def_theater_mode_on">false</bool>
    <!-- Comma-separated list of bluetooth, wifi, and cell. -->
    <string name="def_airplane_mode_radios" translatable="false">cell,bluetooth,wifi,nfc,wimax</string>
    <string name="airplane_mode_toggleable_radios" translatable="false">bluetooth,wifi,nfc</string>
    <string name="def_bluetooth_disabled_profiles" translatable="false">0</string>
    <bool name="def_auto_time">true</bool>
    <bool name="def_auto_time_zone">true</bool>
    <bool name="def_accelerometer_rotation">false</bool>
    <!-- Default screen brightness, from 0 to 255.  102 is 40%. -->
    <integer name="def_screen_brightness">102</integer>
    <bool name="def_screen_brightness_automatic_mode">false</bool>
    <fraction name="def_window_animation_scale">100%</fraction>
    <fraction name="def_window_transition_scale">100%</fraction>
    <bool name="def_haptic_feedback">true</bool>

    <bool name="def_bluetooth_on">true</bool>
    <bool name="def_wifi_display_on">false</bool>
    <bool name="def_install_non_market_apps">false</bool>
    <bool name="def_package_verifier_enable">true</bool>
    <!-- 0 == off, 3 == on -->
    <integer name="def_location_mode">3</integer>
    <bool name="assisted_gps_enabled">true</bool>
    <bool name="def_netstats_enabled">true</bool>
    <bool name="def_usb_mass_storage_enabled">true</bool>
    <bool name="def_wifi_on">false</bool>
    <!-- 0 == never, 1 == only when plugged in, 2 == always -->
    <integer name="def_wifi_sleep_policy">2</integer>
    <bool name="def_wifi_wakeup_enabled">true</bool>
    <bool name="def_networks_available_notification_on">true</bool>

    <bool name="def_backup_enabled">false</bool>
    <string name="def_backup_transport" translatable="false">com.android.localtransport/.LocalTransport</string>

    <!-- Default value for whether or not to pulse the notification LED when there is a
         pending notification -->
    <bool name="def_notification_pulse">true</bool>

    <bool name="def_mount_play_notification_snd">true</bool>
    <bool name="def_mount_ums_autostart">false</bool>
    <bool name="def_mount_ums_prompt">true</bool>
    <bool name="def_mount_ums_notify_enabled">true</bool>
	……
<resources>

7、将 settings.db 数据库的数据迁移到 xml 文件中

FilePath: frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java


        private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper,
                SQLiteDatabase database, int userId) {
            //1、迁移与 system 设置相关的数据
            // Move over the system settings.
            //1.1、生成与xml文件唯一对应的key
            final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
            ensureSettingsStateLocked(systemKey);
            //1.2、将临时数据库中设置数据存放到 SettingsState 对象中
            SettingsState systemSettings = mSettingsStates.get(systemKey);
            migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);
            //1.3、把数据写入到xml文件中
            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);
            ensureSettingsStateLocked(secureKey);
            SettingsState secureSettings = mSettingsStates.get(secureKey);
            migrateLegacySettingsLocked(secureSettings, database, TABLE_SECURE);
            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);
                ensureSettingsStateLocked(globalKey);
                SettingsState globalSettings = mSettingsStates.get(globalKey);
                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();
            }

			//4、删除数据库或备份数据库数据
            // Drop the database as now all is moved and persisted.
            if (DROP_DATABASE_ON_MIGRATION) {
                dbHelper.dropDatabase();
            } else {
                dbHelper.backupDatabase();
            }
	}
 

三、对 SettingsProvider 进行操作方法

1、操作方法

由于 framework下的 Settings.java 文件中对 SettingsProvider 进行了封装,而且Global、Secure、System 三种数据类型的使用方式几乎一样,所以对系统设置的操作也是相当简便的。以下是对系统设置的读写操作的示例:


//查询数据
String globalValue = Settings.Global.getString(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON);

//写入数据
boolean isSuccess = Settings.System.putInt(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);

2、权限限制

因为 SettingsProvider 是系统应用,所以对它的操作可能会有一些权限限制。首先查询 SettingsProvider 中的设置项数据是不需要声明任何权限,也就是说第三方应用也可以通过 Settings 类查询到 SettingsProvider 中的设置项数据,但是 Android 系统一般不允许第三方应用直接修改系统的设置项,需要用户授权才能修改,即修改设置项数据需要声明下权限:

  • android.permission.WRITE_SETTINGS
  • android.permission.WRITE_SECURE_SETTINGS

所以,一般可以直接修改系统设置的都是系统签名过的应用,也就是系统应用,如下图所示:

这里是引用

四、客制化示例

1、默认搜狗输入法

默认输入法属于安全设置, 设置项的数据类型是 Secure 类型。首先,在 defaults.xml 文件中并没有定义默认输入法设置项数据,所以只能在 DatabaseHelper 类里面的 loadSecureSettings() 添加相应方法可以进行修改,这个方法会在 loadSettings(SQLiteDatabase db) 方法被调用,然后将设置项数据加载到数据表中去。


@Deprecated
class DatabaseHelper extends SQLiteOpenHelper {
	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);
	        }
	}
	……
	private void loadSecureSettings(SQLiteDatabase db) {
	        SQLiteStatement stmt = null;
	        try {
			……
+				//启用搜狗输入法
+	            loadSetting(stmt, Settings.Secure.ENABLED_INPUT_METHODS, "com.sohu.inputmethod.sogou/.SogouIME");
+				//将搜狗输入法设置为默认输入法
+		        loadSetting(stmt, Settings.Secure.DEFAULT_INPUT_METHOD, "com.sohu.inputmethod.sogou/.SogouIME");
	
	            /*
	             * IMPORTANT: Do not add any more upgrade steps here as the global,
	             * secure, and system settings are no longer stored in a database
	             * but are kept in memory and persisted to XML.
	             *
	             * See: SettingsProvider.UpgradeController#onUpgradeLocked
	             */
	        } finally {
	            if (stmt != null) stmt.close();
	        }
	    }
	    

获取输入法MID的命令:

adb shell ime list

2、自定义系统设置项开关

(1)在 Settings 应用相应的 xml 文件中定义一个开关控件 SwitchPreference,例如:


<SwitchPreference
            android:key="deep_sleep"
            android:title="@string/deep_sleep_title"
            settings:controller="com.android.settings.development.DeepSleepPreferenceController"/>
            

(2)定义开关的控制类,这个类需要继承一个抽象类TogglePreferenceController,然后重写 isChecked() 、setChecked()和 getAvailabilityStatus()方法,例如:


public class DeepSleepPreferenceController extends TogglePreferenceController{

    static final String DEEPSLEEP_ON = "1";
    static final String DEEPSLEEP_OFF = "0";
    private String mPreferenceKey = null;

    public DeepSleepPreferenceController(Context context, String preferenceKey) {
        super(context, preferenceKey);
        mPreferenceKey = preferenceKey;
    }

    //Synchronization switch status: set the previously saved switch status
    // when the user enters the interface where the switch is located again.
    @Override
    public boolean isChecked() {
        boolean deepSleepFlag = false;
        if (DEEPSLEEP_ON.equals(Settings.Global.getString(mContext.getContentResolver(), mPreferenceKey))){
            deepSleepFlag = true;
        }
        return deepSleepFlag;
    }

    //When the user clicks the switch, the switch state is saved.
    @Override
    public boolean setChecked(boolean isChecked) {
        final String newValue = isChecked ? DEEPSLEEP_ON : DEEPSLEEP_OFF;
        Settings.Global.putString(mContext.getContentResolver(), mPreferenceKey, newValue);
        return true;
    }

    @Override
    public int getAvailabilityStatus() {
    	//启用开关
        return AVAILABLE;
    }

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI大模型对话(上下文)缓存能力

互联网应用中&#xff0c;为了提高数据获取的即时性&#xff0c;产生了各种分布式缓存组件&#xff0c;比如Redis、Memcached等等。 大模型时代&#xff0c;除非是免费模型&#xff0c;否则每次对话都会花费金钱来进行对话&#xff0c;对话是不是也可以参照缓存的做法来提高命…

207.贪心算法:最大子数组和(力扣)

代码展示 class Solution { public:int maxSubArray(vector<int>& nums) {int result INT_MIN; // 初始化结果为最小可能的整数int sum 0; // 初始化当前子数组和为0// 遍历数组中的每一个元素for (int i 0; i < nums.size(); i){sum nums[i]; //…

PHP电商系统开发指南最佳实践

电子商务系统开发的最佳实践包括&#xff1a;数据库设计&#xff1a;选择适合关系型数据库&#xff0c;优化数据结构&#xff0c;考虑表分区&#xff1b;安全&#xff1a;加密数据&#xff0c;防止 sql 注入&#xff0c;处理会话管理&#xff1b;用户界面&#xff1a;遵循 ux 原…

【仪器仪表】 矢量网络分析仪 Vector Network Analyzer

主要功能&#xff1a; 测量S参数&#xff1a; S11&#xff08;输入反射系数&#xff09;&#xff1a;测量输入端口的反射。S21&#xff08;正向传输系数&#xff09;&#xff1a;测量从输入端口到输出端口的传输。S12&#xff08;反向传输系数&#xff09;&#xff1a;测量从输…

MobPush 第三方插件:Uni-app

插件集成 访问MobPush插件、MobCommon插件点击购买并添加到项目当中。在uniapp的“manifest.json”中选择“app原生插件配置”&#xff0c;点击勾选上方添加的两个插件完成上述两步后请务必先打自定义基座哦&#xff0c;否则SDK代码无法生效&#xff01; iOS平台相关配置 添…

软考《信息系统运行管理员》-2.2 信息系统运维的组织

2.2 信息系统运维的组织 信息系统运维的任务 数据资源管理 数据收集、数据校验、数据录入、数据处理 软件资源管理 采购、保存、相关文档保管、分发、安装、支持、评价、培训 硬件资源管理 检查、维护、故障处理、更新、修复、扩充 系统安全管理 可用性、完整性、保密性、可控…

【C语言】typedef 关键字

在C语言中&#xff0c;typedef关键字用于给现有的数据类型起一个新的名字。它在提高代码可读性、简化复杂类型声明、增强可维护性方面非常有用。typedef通常用于定义结构体、指针、函数指针以及其他复杂类型。 基本用法 typedef int MyInt; MyInt x 10;在这个例子中&#xf…

ROS2 RQT

1. RQT是什么 RQT是一个GUI框架&#xff0c;通过插件的方式实现了各种各样的界面工具。 强行解读下&#xff1a;RQT就像插座&#xff0c;任何电器只要符合插座的型号就可以插上去工作。 2.选择插件 这里我们可以选择现有的几个RQT插件来试一试&#xff0c;可以看到和话题、参…

从深度学习到音乐创作:AI如何重新定义音乐行业

&#x1f4d1;引言 近一个月来&#xff0c;随着几款音乐大模型的轮番上线&#xff0c;AI在音乐产业的角色迅速扩大。这些模型不仅将音乐创作的门槛降至前所未有的低点&#xff0c;还引发了一场关于AI是否会彻底颠覆音乐行业的激烈讨论。从初期的兴奋到现在的理性审视&#xff0…

【FPGA 学习与实践】<初阶> 项目周计划

第1-2周&#xff1a;基础项目 - 4位加法器和计数器 目标&#xff1a;掌握Verilog基本语法和模块设计。 第1周&#xff1a; 学习Verilog的基本语法和结构&#xff08;模块、端口、数据类型&#xff09;。设计并实现一个4位加法器。编写测试平台&#xff08;Testbench&#xff0…

吴晓波:企业出海的最佳时间窗口只有5-10年,中国企业如何把握出海机遇?

鼓励企业参与绿色“一带一路”建设&#xff0c;带动先进的环保技术、装备、产能走出去。 出海计划&#xff01;马来西亚水环境项目国际考察暨2024中马水务合作论坛

控制台厂商配额查询

概述 厂商推送限制 每个厂商通道都有对应的厂商配额和 QPS 限制&#xff0c;当请求超过限制且已配置厂商回执时&#xff0c;MobPush会采取以下措施&#xff1a; 当开发者推送请求超过厂商配额时&#xff0c;MobPush将通过自有通道进行消息下发。当开发者推送请求超过厂商 QPS…

LLM大模型工程师面试经验宝典--进阶版2(2024.7月最新)

目录 1 大模型怎么评测&#xff1f; 2 大模型的honest原则是如何实现的&#xff1f;模型如何判断回答 的知识是训练过的已知的知识&#xff0c;怎么训练这种能力&#xff1f; 3 如何衡量大模型水平&#xff1f; 4 大模型评估方法 有哪些&#xff1f; 5 大模型评估工具 有哪…

Linux——查找文件-find(详细)

查找文件-find 作用 - 按照文件名、大小、时间、权限、类型、所属者、所属组来搜索文件 格式 find 查找路径 查找条件 具体条件 操作 注意 - find命令默认的操作是print输出 - find是检索文件的&#xff0c;grep是过滤文件中字符串 参数 参数 …

S7-1500PLC通过工艺对象实现V90总线伺服定位控制(105报文)

S7-1500PLC通过工艺对象实现V90总线伺服定位控制,伺服驱动器工作在速度模式,S7-1500PLC工作在位置模式,具体控制原理框图,可以参考下面文章链接: 1、S7-1200PLC和V90总线伺服位置控制 S7-1200PLC和V90总线伺服通过工艺对象实现定位控制(标准报文3应用)_v90伺服 报文3 设…

Python 获取字典中的值(八种方法)

Python 字典(dictionary)是一种可变容器模型&#xff0c;可以存储任意数量的任意类型的数据。字典通常用于存储键值对&#xff0c;每个元素由一个键&#xff08;key&#xff09;和一个值(value&#xff09;组成&#xff0c;键和值之间用冒号分隔。 以下是 Python 字典取值的几…

创新校园服务模式 跑腿小程序平台源码构建与实践 前后端分离 带完整的安装代码包以及部署教程

系统概述 本项目是一个集任务发布、接单、支付、评价于一体的跑腿服务小程序平台&#xff0c;专为高校校园设计。系统采用前后端分离架构&#xff0c;前端负责用户界面展示和交互逻辑&#xff0c;后端处理业务逻辑、数据存取等&#xff0c;两者通过API接口进行通信&#xff0c…

MySQL数据核心技术:理解主键与外键的关系与作用

在进行数据库设计时&#xff0c;合理的添加主键和外键能有效保障数据的完整性和一致性&#xff0c;使得数据管理更加科学高效。本文将详细介绍MySQL中主键和外键的基本概念、它们之间的关系、作用及一些高级知识点。 一、主键&#xff08;Primary Key&#xff09;的概念 主键…

Bootstrap 缩略图

Bootstrap 缩略图 引言 Bootstrap 是一个流行的前端框架,它提供了一套丰富的组件和工具,帮助开发者快速构建响应式和移动优先的网页。缩略图(Thumbnails)是 Bootstrap 中的一种组件,用于展示图片或其他媒体内容,通常与标题和文本描述一起使用,形成一个整洁的布局。本文…

Flink实现准确和高效流处理的关键问题

时间相关: Watermark 水位线 水位线是插入到数据流中的一个标记,可以认为是一个特殊的数据。水位线主要的内容是一个时间戳,用来表示当前事件时间的进展。水位线是基于数据的时间戳生成的。水位线的时间戳必须单调递增,以确保任务的事件时间时钟一直向前推进,进展。水位线…