广州模板建站哪家好,成都全网营销推广,dede网站迁移,网站建设会计帐务处理Android Settings 系列文章#xff1a;
Android Settings解析SettingsIntelligenceSettingsProvider
首语
为啥要聊到这个模块呢#xff1f;因为Settings里存在大量的设置项#xff0c;这些设置项的状态需要保存#xff0c;它们就是通过SettingsProvider来处理的。以状态…Android Settings 系列文章
Android Settings解析SettingsIntelligenceSettingsProvider
首语
为啥要聊到这个模块呢因为Settings里存在大量的设置项这些设置项的状态需要保存它们就是通过SettingsProvider来处理的。以状态栏显示电量百分比菜单为例Battery-Battery percentage,分析下它的状态保存。 本文以Android 13 SettingsProvider源码进行分析。
Settings模块调用
这个菜单的核心实现在BatteryPercentagePreferenceController.java中可以发现菜单的状态保存实现在Settings类中状态读取通过getInt方法状态保存通过putInt方法
public class BatteryPercentagePreferenceController extends BasePreferenceController implementsPreferenceControllerMixin, Preference.OnPreferenceChangeListener {Overridepublic void updateState(Preference preference) {//菜单状态保存读取int setting Settings.System.getInt(mContext.getContentResolver(),SHOW_BATTERY_PERCENT, 0);((SwitchPreference) preference).setChecked(setting 1);}Overridepublic boolean onPreferenceChange(Preference preference, Object newValue) {boolean showPercentage (Boolean) newValue;//菜单状态保存Settings.System.putInt(mContext.getContentResolver(), SHOW_BATTERY_PERCENT,showPercentage ? 1 : 0);FeatureFactory.getFactory(mContext).getMetricsFeatureProvider().action(mContext, SettingsEnums.OPEN_BATTERY_PERCENTAGE, showPercentage);return true;}
}Settings类分析
在Settings中可以看到getInt最终实现是通过ContentProvider的query方法去查询数据putInt方法同理。mProviderHolder通过NameValueCache构造函数传入uri为content://settings/system。mCallGetCommand为CALL_METHOD_GET_GLOBAL调用ContentProvider的call方法。mContentProvider是authority为settings的ContentProvider。这里其实就知道为啥跟SettingsProvider相关联了。
因为在SettingsProvider中定义了一个SettingsProviderauthority为settings。
继续分析下Settings类可以发现它只能保存intfloatstring等基本类型的数据同时以键值对的形式保存Settings中定义了大量的设置项KEY。其次除了System类外还有GlobalSecureConfigBookmarks类分别构造了不同URI操作数据。因为Settings对数据进行了分类。
System。包含各种系统设置。Global。包含各种对用户公开的系统设置第三方应用程序可以读取不可以写入。Secure。包含各种安全系统设置。第三方应用程序可以读取不可以写入。Config。配置系统设置。只有Android可以读取特定的配置服务可以写入。Bookmarks。用户定义的书签和快捷方式。 每个书签的目标是一个 Intent URL允许它是网页或特定的应用程序活动。
修改数据需要权限
android.permission.WRITE_SETTINGSandroid.permission.WRITE_SECURE_SETTINGS
public final class Settings {public static final class System extends NameValueTable {public static final Uri CONTENT_URI Uri.parse(content:// AUTHORITY /system);public static int getInt(ContentResolver cr, String name, int def) {return getIntForUser(cr, name, def, cr.getUserId());}/** hide */UnsupportedAppUsagepublic static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {String v getStringForUser(cr, name, userHandle);return parseIntSettingWithDefault(v, def);} public static String getStringForUser(ContentResolver resolver, String name,int userHandle) {return sNameValueCache.getStringForUser(resolver, name, userHandle);}}public static final class Global extends NameValueTable {public static final Uri CONTENT_URI Uri.parse(content:// AUTHORITY /global); private static final NameValueCache sNameValueCache new NameValueCache(CONTENT_URI,CALL_METHOD_GET_GLOBAL,CALL_METHOD_PUT_GLOBAL,CALL_METHOD_DELETE_GLOBAL,sProviderHolder,Global.class);}public static final class Secure extends NameValueTable {public static final Uri CONTENT_URI Uri.parse(content:// AUTHORITY /secure); } public static final class Config extends NameValueTable {public static final Uri CONTENT_URI Uri.parse(content:// AUTHORITY /config); } private static class NameValueCache {T extends NameValueTable NameValueCache(Uri uri, String getCommand,String setCommand, String deleteCommand, ContentProviderHolder providerHolder,ClassT callerClass) {this(uri, getCommand, setCommand, deleteCommand, null, null, providerHolder,callerClass);}public String getStringForUser(ContentResolver cr, String name, final int userHandle) {IContentProvider cp mProviderHolder.getProvider(cr);...if (mCallGetCommand ! null) {b cp.call(cr.getAttributionSource(),mProviderHolder.mUri.getAuthority(), mCallGetCommand, name,args);String value b.getString(Settings.NameValueTable.VALUE);return value;}if (Settings.isInSystemServer() Binder.getCallingUid() ! Process.myUid()) {final long token Binder.clearCallingIdentity();try {c cp.query(cr.getAttributionSource(), mUri,SELECT_VALUE_PROJECTION, queryArgs, null);} finally {Binder.restoreCallingIdentity(token);}} else {c cp.query(cr.getAttributionSource(), mUri,SELECT_VALUE_PROJECTION, queryArgs, null);}...}private static final class ContentProviderHolder {private final Object mLock new Object();private final Uri mUri;private IContentProvider mContentProvider;public ContentProviderHolder(Uri uri) {mUri uri;}public IContentProvider getProvider(ContentResolver contentResolver) {synchronized (mLock) {if (mContentProvider null) {mContentProvider contentResolver.acquireProvider(mUri.getAuthority());}return mContentProvider;}}
}SettingsProvider模块分析
SettingsProvider模块源码为frameworks/base/packages/SettingsProvider/模块名为SettingsProvider包名为com.android.providers.settingsManifest中定义了authority为settings的ContentProvider。
provider android:nameSettingsProviderandroid:authoritiessettingsandroid:multiprocessfalseandroid:exportedtrueandroid:singleUsertrueandroid:initOrder100android:visibleToInstantAppstrue /查看下SettingsProvider的实现首先在onCreate方法中有迁移处理用户相关监听添加了两个服务SettingsServiceDeviceConfigService。
public class SettingsProvider extends ContentProvider {Overridepublic boolean onCreate() {...synchronized (mLock) {//迁移处理mSettingsRegistry.migrateAllLegacySettingsIfNeededLocked();mSettingsRegistry.syncSsaidTableOnStartLocked();}mHandler.post(() - {//用户移除停止广播注册registerBroadcastReceivers();//用户限制变更监听startWatchingUserRestrictionChanges();});ServiceManager.addService(settings, new SettingsService(this));ServiceManager.addService(device_config, new DeviceConfigService(this));}
}SettingsService类重写onShellCommand方法来处理adb shell 命令。
final public class SettingsService extends Binder {Overridepublic void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,String[] args, ShellCallback callback, ResultReceiver resultReceiver) {(new MyShellCommand(mProvider, false)).exec(this, in, out, err, args, callback, resultReceiver);}
}执行 adb shell settings打印了以下command使用信息。可以使用这些命令快速进行数据操作。
Settings provider (settings) commands:helpPrint this help text.get [--user USER_ID | current] NAMESPACE KEYRetrieve the current value of KEY.put [--user USER_ID | current] NAMESPACE KEY VALUE [TAG] [default]Change the contents of KEY to VALUE.TAG to associate with the setting.{default} to set as the default, case-insensitive only for global/secure namespacedelete [--user USER_ID | current] NAMESPACE KEYDelete the entry for KEY.reset [--user USER_ID | current] NAMESPACE {PACKAGE_NAME | RESET_MODE}Reset the global/secure table for a package with mode.RESET_MODE is one of {untrusted_defaults, untrusted_clear, trusted_defaults}, case-insensitivelist [--user USER_ID | current] NAMESPACEPrint all defined keys.NAMESPACE is one of {system, secure, global}, case-insensitiveSettingsService进行了adb shell命令的扩展让我们操作数据更加方便。DeviceConfigService同理通过adb shell device_config查看command信息。
分析了SettingsProvider的onCreate方法后再看下insert方法是如何插入数据的它从uri取出table对应不同uri为system/global/secure等。以插入global数据为例分析SystemGlobal等实现类似。operation来判断是增删改查那种操作通过SettingsState类的insertSettingLocked方法来进行插入操作而SettingsState是通过ensureSettingsStateLocked方法创建的然后保存到mSettingsStates中。
public class SettingsProvider extends ContentProvider {public static final String TABLE_SYSTEM system;Overridepublic Uri insert(Uri uri, ContentValues values) {...String table getValidTableOrThrow(uri);switch (table) {case TABLE_GLOBAL: {if (insertGlobalSetting(name, value, null, false,UserHandle.getCallingUserId(), false, /* overrideableByRestore */ false)) {return Uri.withAppendedPath(Settings.Global.CONTENT_URI, name);}} break;}}private boolean insertGlobalSetting(String name, String value, String tag,boolean makeDefault, int requestingUserId, boolean forceNotify,boolean overrideableByRestore) {return mutateGlobalSetting(name, value, tag, makeDefault, requestingUserId,MUTATION_OPERATION_INSERT, forceNotify, 0, overrideableByRestore);}private boolean mutateGlobalSetting(String name, String value, String tag,boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,int mode, boolean overrideableByRestore) {switch (operation) {//插入操作case MUTATION_OPERATION_INSERT: {return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL,UserHandle.USER_SYSTEM, name, value, tag, makeDefault,callingPackage, forceNotify,CRITICAL_GLOBAL_SETTINGS, overrideableByRestore);}}}final class SettingsRegistry {private static final String SETTINGS_FILE_GLOBAL settings_global.xml;public boolean insertSettingLocked(int type, int userId, String name, String value,String tag, boolean makeDefault, String packageName, boolean forceNotify,SetString criticalSettings, boolean overrideableByRestore) {return insertSettingLocked(type, userId, name, value, tag, makeDefault, false,packageName, forceNotify, criticalSettings, overrideableByRestore);}public boolean insertSettingLocked(int type, int userId, String name, String value,String tag, boolean makeDefault, boolean forceNonSystemPackage, String packageName,boolean forceNotify, SetString criticalSettings, boolean overrideableByRestore) {...SettingsState settingsState peekSettingsStateLocked(key);if (settingsState ! null) {success settingsState.insertSettingLocked(name, value,tag, makeDefault, forceNonSystemPackage, packageName, overrideableByRestore);}}Nullableprivate SettingsState peekSettingsStateLocked(int key) {...if (!ensureSettingsForUserLocked(getUserIdFromKey(key))) {return null;}return mSettingsStates.get(key);}public boolean ensureSettingsForUserLocked(int userId) {...if (userId UserHandle.USER_SYSTEM) {final int globalKey makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);ensureSettingsStateLocked(globalKey);}}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);}}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);...}}}
}
继续分析SettingsState类的insertSettingLocked方法先将数据保存到mSettings创建了一个Handler延时加锁进行写数据操作核心写数据操作在doWriteState方法里。mStatePersistFile是从SettingsState传递过来的由创建SettingsState的ensureSettingsStateLocked方法可知通过getSettingsFile创建mStatePersistFile文件路径为用户系统目录(/data/system/users/0/)文件名为settings_global.xml然后在xml中进行写数据。
final class SettingsState {public boolean insertSettingLocked(String name, String value, String tag,boolean makeDefault, boolean forceNonSystemPackage, String packageName,boolean overrideableByRestore) {...mSettings.put(name, newState);scheduleWriteIfNeededLocked();}private void scheduleWriteIfNeededLocked() {...writeStateAsyncLocked();}private void writeStateAsyncLocked() {...Message message mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS);mHandler.sendMessageDelayed(message, writeDelayMillis);}private final class MyHandler extends Handler {Overridepublic void handleMessage(Message message) {switch (message.what) {case MSG_PERSIST_SETTINGS: {Runnable callback (Runnable) message.obj;doWriteState();if (callback ! null) {callback.run();}}private void doWriteState() {synchronized (mLock) {version mVersion;settings new ArrayMap(mSettings);namespaceBannedHashes new ArrayMap(mNamespaceBannedHashes);mDirty false;mWriteScheduled false;}synchronized (mWriteLock){AtomicFile destination new AtomicFile(mStatePersistFile, mStatePersistTag);FileOutputStream out null;try {out destination.startWrite();TypedXmlSerializer serializer Xml.resolveSerializer(out);serializer.startDocument(null, true);serializer.startTag(null, TAG_SETTINGS);serializer.attributeInt(null, ATTR_VERSION, version);final int settingCount settings.size();for (int i 0; i settingCount; i) {Setting setting settings.valueAt(i);if (setting.isTransient()) {if (DEBUG_PERSISTENCE) {Slog.i(LOG_TAG, [SKIPPED PERSISTING] setting.getName());}continue;}if (writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),setting.getValue(), setting.getDefaultValue(), setting.getPackageName(),setting.getTag(), setting.isDefaultFromSystem(),setting.isValuePreservedInRestore())) {}}}serializer.endTag(null, TAG_SETTINGS);serializer.startTag(null, TAG_NAMESPACE_HASHES);for (int i 0; i namespaceBannedHashes.size(); i) {String namespace namespaceBannedHashes.keyAt(i);String bannedHash namespaceBannedHashes.get(namespace);if (writeSingleNamespaceHash(serializer, namespace, bannedHash)) {}}}serializer.endTag(null, TAG_NAMESPACE_HASHES);serializer.endDocument();destination.finishWrite(out);}}到这里才知道数据是保存在xml文件中的而并非数据库里。Global类型数据保存在settings_global.xml中System类型数据保存在settings_system.xml中Secure类型数据保存在settings_secure.xml中都在用户系统目录(/data/system/users/0/)下保存截取部分内容如下
settings version213
setting id127 nameadb_wifi_enabled value0 packageandroid defaultValue0 defaultSysSettrue /
setting id44 namelow_battery_sound_timeout value0 packageandroid defaultValue0 defaultSysSettrue /
setting id95 namewear_os_version_string value packageandroid defaultValue defaultSysSettrue /...
/settings 查看时可能乱码这是因为Android13保存的xml文件使用的是一种二进制格式通过以下命令修改
adb shell setprop persist.sys.binary_xml falsexml配置文件的格式就变为ASCII 码格式文件就不会乱码可以正常查看了。
对于其它的queryupdatedelete方法也不需赘述了都是对mSettings进行操作根据mSettings变化重新写入xml。核心实现都在SettingsState类中通过锁来确保多个修改以原子方式持久保存到内存和磁盘中。
再看下call方法前面Settings类中getStringForUser方法就调用了call方法去获取数据。method是区分各种类型数据操作的,不同类型数据操作有不同的method定义之后的数据操作流程就和增删改查方法中的一致。
public class SettingsProvider extends ContentProvider {Overridepublic Bundle call(String method, String name, Bundle args) {case Settings.CALL_METHOD_GET_GLOBAL: {Setting setting getGlobalSetting(name);return packageValueForCallResult(setting, isTrackingGeneration(args));}case Settings.CALL_METHOD_PUT_GLOBAL: {String value getSettingValue(args);String tag getSettingTag(args);final boolean makeDefault getSettingMakeDefault(args);final boolean overrideableByRestore getSettingOverrideableByRestore(args);insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false,overrideableByRestore);break;}}
}对SettingsProvider的基本方法分析以后我们分析下数据迁移方法migrateLegacySettingsForUserIfNeededLocked它在onCreate方法中调用。通过DatabaseHelper类获取数据库实例来操作数据库在TABLE_GLOBAL表内查询name 、value列然后通过SettingsState的insertSettingLocked方法将数据插入到xml插入完成后删除数据库。
public class SettingsProvider extends ContentProvider {private static final boolean DROP_DATABASE_ON_MIGRATION true;public static final String TABLE_GLOBAL global;private void migrateLegacySettingsForUserIfNeededLocked(int userId) {// Every user has secure settings and if no file we need to migrate.final int secureKey makeKey(SETTINGS_TYPE_SECURE, userId);File secureFile getSettingsFile(secureKey);if (SettingsState.stateFileExists(secureFile)) {return;}DatabaseHelper dbHelper new DatabaseHelper(getContext(), userId);SQLiteDatabase database dbHelper.getWritableDatabase();migrateLegacySettingsForUserLocked(dbHelper, database, userId);}private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper,SQLiteDatabase database, int userId) {...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 createdif (mSettingsCreationBuildId ! null) {globalSettings.insertSettingLocked(Settings.Global.DATABASE_CREATION_BUILDID,mSettingsCreationBuildId, null, true,SettingsState.SYSTEM_PACKAGE_NAME);}globalSettings.persistSyncLocked();}// 已经迁移删除数据库if (DROP_DATABASE_ON_MIGRATION) {dbHelper.dropDatabase();} else {dbHelper.backupDatabase();}private void migrateLegacySettingsLocked(SettingsState settingsState, SQLiteDatabase database, String table) {SQLiteQueryBuilder queryBuilder new SQLiteQueryBuilder();queryBuilder.setTables(table);Cursor cursor queryBuilder.query(database, LEGACY_SQL_COLUMNS,null, null, null, null, null);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);//插入数据到xmlsettingsState.insertSettingLocked(name, value, null, true,SettingsState.SYSTEM_PACKAGE_NAME);cursor.moveToNext();}} finally {cursor.close();}}}
}那看下DatabaseHelper实现数据库名为settings.dbonCreate方法中创建了多张表还是以Global为例其它同理。在global表插入数据KEY一般都是在Settings中定义VALUE则一般都是本地资源。给这些KEY对应的设置项添加了初始值。可以在res/values/defaults.xml文件中看到定义了大量菜单的初始值。
class DatabaseHelper extends SQLiteOpenHelper {private static final String DATABASE_NAME settings.db;Overridepublic 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);...//加载数据// Load initial volume levels into DBloadVolumeLevels(db);// Load inital settings valuesloadSettings(db);}private void loadSettings(SQLiteDatabase db) {loadSystemSettings(db);loadSecureSettings(db);// The global table only exists for the owner/system userif (mUserHandle UserHandle.USER_SYSTEM) {loadGlobalSettings(db);}}private void loadGlobalSettings(SQLiteDatabase db) {SQLiteStatement stmt null;final Resources res mContext.getResources();try {//插入sqlstmt db.compileStatement(INSERT OR IGNORE INTO global(name,value) VALUES(?,?););loadBooleanSetting(stmt, Settings.Global.AIRPLANE_MODE_ON,R.bool.def_airplane_mode_on);loadStringSetting(stmt, Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,R.string.airplane_mode_toggleable_radios);loadIntegerSetting(stmt, Settings.Global.WIFI_SLEEP_POLICY,R.integer.def_wifi_sleep_policy);...}private void loadBooleanSetting(SQLiteStatement stmt, String key, int resid) {loadSetting(stmt, key,mContext.getResources().getBoolean(resid) ? 1 : 0);} private void loadSetting(SQLiteStatement stmt, String key, Object value) {stmt.bindString(1, key);stmt.bindString(2, value.toString());//执行sqlstmt.execute();}
}其它源码就是关于升级和备份相关的这里就不展开分析了。
整理下SettingsProvider的流程Settings.db初始化往表里添加大量数据然后从Settings.db将数据迁移到到不同类型(Global/System/Secure)数据的xml中最后删除数据库。
总结
SettingsProvider 模块使用 ContentProvider 的方式来管理和访问设置数据。它提供了一组标准的 URI用于访问不同类型的设置信息。通过使用这些 URI应用程序可以读取、写入和监听设置的变化。
通过与 SettingsProvider 模块交互Settings等应用程序和系统组件可以轻松地管理设备的各种设置为用户提供更好的个性化和控制体验。