在Android中,用户的默认设置和偏好设置是存在数据库中,在Android 6.0 以前,settings的数据是存在settings.db中,系统可以通过sqlite来进行读写。
这样的话,所有的第三方应用都可以对settings.db进行操作,修改用户设置的数据。
所以在 在Android 6.0版本以后,SettingsProvider被重构,从安全和性能等方面考虑,把SettingsProvider中原本保存在settings.db中的数据,目前全部保存在XML文件中。
Settingsprovider中对数据也进行了分类,分别是Global、System、Secure、Ssaid四种类型,说明如下:
Global:所有的偏好设置对系统的所有用户公开;
System:包含各种各样的用户偏好系统设置;
Secure:安全性的用户偏好系统设置;
另外,还有一个不被熟知的Ssaid 表:此表包括所有应用的id;
这样的话,只是可以从文件权限类型来做权限的管控,可以让第三方APP有读没有写的权限,或者直接不给读写权限等。
下面从settings数据读写设计和使用方面进行讲解说明:
settingsProvider的启动过程:
./frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
启动SettingsProvider相当于打开一个Activity类似,因为它继承了ContentProvider 类,所以会回调ContentProvider的生命周期方法,首先会调用OnCreate()方法
@Override
public boolean onCreate() {
Settings.setInSystemServer();
synchronized (mLock) {
mUserManager = UserManager.get(getContext());
mPackageManager = AppGlobals.getPackageManager();
mHandlerThread = new HandlerThread(LOG_TAG,
Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mSettingsRegistry = new SettingsRegistry();
}
mHandler.post(() -> {
registerBroadcastReceivers();
startWatchingUserRestrictionChanges();
});
ServiceManager.addService("settings", new SettingsService(this));
return true;
}
重点关注实例化SettingsRegistry对象,进入到SettingsRegistry class类中你就可以看到这个类定义的一些xml:
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";
public SettingsRegistry() {
mHandler = new MyHandler(getContext().getMainLooper());
mGenerationRegistry = new GenerationRegistry(mLock);
mBackupManager = new BackupManager(getContext());
migrateAllLegacySettingsIfNeeded();
syncSsaidTableOnStart();
}
关注构造函数里面的migrateAllLegacySettingsIfNeeded 方法,这个方法会迁移所有需要的settings数据,我们可以来关注一下它的迁移方式
private void migrateAllLegacySettingsIfNeeded() {
synchronized (mLock) {
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 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);
}
}
}
首先调用了makeKey方法来获得key,这个key就是前面写的的Global、System、Secure、Ssaid四种类型,获得之后通过getSettingsFile方法创建四种类型的File类型实例:
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 if (isSsaidSettingsKey(key)) {
final int userId = getUserIdFromKey(key);
return new File(Environment.getUserSystemDirectory(userId),
SETTINGS_FILE_SSAID);
} else {
throw new IllegalArgumentException("Invalid settings key:" + key);
}
}
上面的代码就会生成创建对应的文件:
继续回到migrateAllLegacySettingsIfNeeded方法中,它会获取系统中所有的应用服务用户id,然后逐个通过databaseHelper来进行数据迁移。
DatabaseHelper 是SQLiteOpenHelper的子类,我们看看DatabaseHelper的初始化:
./frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@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);
}
在onCreate中通过 db.execSQL("CREATE TABLE system…,createSecureTable(db),createGlobalTable(db)分别创建system,Secure,Global三个数据库表,
然后调用loadVolumlevels方法将默认的音乐音量,铃声音量,通知音量,震动设置写到数据库的system的数据表中,然后调用loadSettings方法:
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);
}
}
加载许多默认值写入数据库中,这些默认值大都放在defaults.xml文件中,所以一般我们在修改settings用户的默认值时,只有更新这个文件即可。
需要注意的是,defaults,xml数据优先级是最低的,当用户进行了偏好设置之后,对应偏好设置的数据库优先级会最高,同时因为偏好设置是存储在
data分区中,所以在用户没有进行恢复出厂设置或者刷机升级时,这些偏好设置是不能恢复到原始状态的,包括进行OTA升级。
SetttngsProvider的启动过程中,会创建数据库,把默认设置的数据值写入数据库,然后将数据库中的数据全部迁移到xml文件中
由于SetttingsProvider是向整个Android系统提供用户偏好设置的提供程序,在所保存的数据类型和方式上也有一定的规定和约束,为能够保证在整个Android的Java层任意一个地方里面能够方便,
快捷的使用SettingsProvider进行数据查询,数据插入,数据更新,所以在framework的provider里面有一个类Settings.java对使用SettingsProvider进行了封装。
frameworks/base/core/java/android/provider/Settings.java
public static final class System extends NameValueTable {
private static final float DEFAULT_FONT_SCALE = 1.0f;
/** @hide */
public static interface Validator {
public boolean validate(String value);
}
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/system");
private static final ContentProviderHolder sProviderHolder =
new ContentProviderHolder(CONTENT_URI);
private static final NameValueCache sNameValueCache = new NameValueCache(
CONTENT_URI,
CALL_METHOD_GET_SYSTEM,
CALL_METHOD_PUT_SYSTEM,
sProviderHolder);
private static final HashSet<String> MOVED_TO_SECURE;
|
在settings.java代码创建了三个静态内部类,System,Secure,Global分别对应SettingsProvider中的System,Secure,Global三种数据类型,Global、Secure、System三个静态内部类会分别持有自己NameValueCache的实例变量,
每个NameValueCache持有指向SettingsProvider中的SettingsProvider.java的AIDL远程调用IContentProvider,因此,查询数据需要经过NameValueCache的getStringForUser()方法,插入数据需要经过putStringForUser()方法。
同时,NameValueCache还持有一个变量mValues,用于保存查询过的设置项,以便下下次再次发起查询时,能够快速返回。
下面以部分调试项目中修改默认待机时间为例,待机时间设置是用户的一个偏好设置,所以用的preference来做处理,这里博阅写了自己的一个OptionBarLayoutPreference 类 ,这个类其实也是继承了系统原生的preference类而已。
这个设置用的preference是"machine_auto_sleep_preference",
在settings 的src/com/android/settings/boyue/device/BoyueDeviceSettings.java 中对这个preference进行初始化,
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.boyue_device_settings);
mHandler = new Handler();
mSettingsObserver = new SettingsObserver(mHandler, getActivity());
......
int sleepTimeout = Settings.System.getInt(getContentResolver(), SCREEN_OFF_TIMEOUT, 120000);
mOptionBarPrefSleepTimeout = (OptionBarLayoutPreference) findPreference("machine_auto_sleep_preference");
if (mOptionBarPrefSleepTimeout != null) {
mOptionBarPrefSleepTimeout.setValue(String.valueOf(sleepTimeout));
mOptionBarPrefSleepTimeout.setOnOptionChangeListener(this);
}
......
}
|
通过上面代码我们知道machine_auto_sleep_preference 的默认值是数据库中的SCREEN_OFF_TIMEOUT 关键字存储的值,
同时,我们可以得知可以通过Settings.System.getInt();方法来获取settings 里面system 表格的数据,与此相对应的设置方法为:
Settings.System.putInt(getContentResolver(), SCREEN_OFF_TIMEOUT, value);
同步,我们来看看SCREEN_OFF_TIMEOUT 这个值得初始化:
private void loadSystemSettings(SQLiteDatabase db) {
......
loadIntegerSetting(stmt, Settings.System.SCREEN_OFF_TIMEOUT,
R.integer.def_screen_off_timeout);
......
}
|
很熟悉的函数,loadSystemSettings 这个函数在我们前面在讲解databaseHelper.java的oncreate函数中调用的,所以当我们需要修改SCREEN_OFF_TIMEOUT 的默认值时,
只要修改defaults.xml中的def_screen_off_timeout 值即可。
./device/rockchip/rk3326/overlay/frameworks/base/packages/SettingsProvider/res/values/defaults.xml
<resources>
<integer name="def_screen_off_timeout">300000</integer>
<!-- Initial value for the Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS setting,
which is a comma separated list of packages that no longer need confirmation
for immersive mode.
Override to disable immersive mode confirmation for certain packages. -->
<string name="def_immersive_mode_confirmations" translatable="false">confirmed</string>
<bool name="def_bluetooth_on">false</bool>
<bool name="def_accelerometer_rotation">false</bool>
</resources>
|
最后,我们在调试settings的数据库时,别忘记有一个很实用的工具:
C:\Users\titan>adb shell
XM01A:/ # settings
Settings provider (settings) commands:
help
Print this help text.
get [--user <USER_ID> | current] NAMESPACE KEY
Retrieve 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 namespace
delete NAMESPACE KEY
Delete 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-insensitive
list NAMESPACE
Print all defined keys.
NAMESPACE is one of {system, secure, global}, case-insensitive
|
这个settings工具可以很实时的获取和设置 system secure global 三张表里面的数据,方便进行调试。
以下以获取和设置screen_off_timeout为例:
当然,如果你想获取整张表的数据,可以直接使用settings list system指令来获取system表的数据。