SettingsProvider我们可以知道它是一个提供设置数据共享的Provider , SettingsProvider和Android其他系统的Provider有很多不一样的地方。
SettingsProvider只接受 int、float、string等基本类型的数据SettingsProvider由 Android 系统 framework 进行了封装,使用更加快捷方便SettingsProvider的数据由键值对组成
SettingsProvider类似于Android 的 properties系统: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对数据进行了分类,分别是Global、System、Secure 三种类型,他们的区分如下:
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);
具体代码实现比较简单,大家追着源码看就好了