SettingsProvider系列2-重要的几个API方法

本文源码基于Android 4.2

onCreate()

既然SettingsProvider是一个ContentProvider,那么对onCreate()的回调肯定是其生命周期中不可或缺的一步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Override
public boolean onCreate() {
//初始化BackupManager
mBackupManager = new BackupManager(getContext());
//初始化UserManager
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
//确保创建ANDROID_ID;
//创建UserHandle对应的DatabaseHelper缓存
//将Global、System、Secure中的内容dump到对应的缓存中。
establishDbTracking(UserHandle.USER_OWNER);
//注册广播接收器,接收UserHandle移除事件。
//原因是当UserHandle需要将UserHandle对应的缓存移除。
IntentFilter userFilter = new IntentFilter();
userFilter.addAction(Intent.ACTION_USER_REMOVED);
getContext().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
UserHandle.USER_OWNER);
if (userHandle != UserHandle.USER_OWNER) {
//设备拥有者不用移除
onUserRemoved(userHandle);
}
}
}
}, userFilter);
return true;
}

其中比较重要的便是establishDbTracking函数,该函数完成了很多初始化准备工作。

  • establishDbTracking(int userHandle) 为UserHandle创建相应的SYSTEM和SECURE缓存,GLOBAL则属于USER_OWNER,因此是所有User共享的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
DatabaseHelper dbhelper;
//获取UserId对应的DataBaseHelper
synchronized (this) {
//先从缓存中获取
dbhelper = mOpenHelpers.get(userHandle);
if (dbhelper == null) {
//获取不到则重新创建一个
dbhelper = new DatabaseHelper(getContext(), userHandle);
mOpenHelpers.append(userHandle, dbhelper);

sSystemCaches.append(userHandle, new SettingsCache(TABLE_SYSTEM));
sSecureCaches.append(userHandle, new SettingsCache(TABLE_SECURE));
sKnownMutationsInFlight.append(userHandle, new AtomicInteger(0));
}
}
//通过DataBaseOpenHelper获取可写DB
SQLiteDatabase db = dbhelper.getWritableDatabase();
// Watch for external modifications to the database files,
// keeping our caches in sync. We synchronize the observer set
// separately, and of course it has to run after the db file
// itself was set up by the DatabaseHelper.
synchronized (sObserverInstances) {
if (sObserverInstances.get(userHandle) == null) {
SettingsFileObserver observer = new SettingsFileObserver(userHandle, db.getPath());
sObserverInstances.append(userHandle, observer);
observer.startWatching();
}
}
//device第一次boot时为UserHandle生成一个对应的随机64 ID,并保存到Secure中
ensureAndroidIdIsSet(userHandle);
//开启一个线程来异步将Settings几个表中(/global,/secure,/system)的内容填充到缓存当中。
startAsyncCachePopulation(userHandle);

query()

接下来便是几乎在所有ContentProvider中调用频率都是最高的查询函数query()。query()直接进入到queryForUser()中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
private Cursor queryForUser(Uri url, String[] select, String where, String[] whereArgs,
String sort, int forUser) {
if (LOCAL_LOGV) Slog.v(TAG, "query(" + url + ") for user " + forUser);
//解析content://settings/xxx, 条件语句和条件的值都Sql查询语句。
SqlArguments args = new SqlArguments(url, where, whereArgs);
DatabaseHelper dbH;
//通过UserHandle获取对应的DatabaseHelper,Global表属于整个设备。
dbH = getOrEstablishDatabase(
TABLE_GLOBAL.equals(args.table) ? UserHandle.USER_OWNER : forUser);
SQLiteDatabase db = dbH.getReadableDatabase();

// The favorites table was moved from this provider to a provider inside Home
// Home still need to query this table to upgrade from pre-cupcake builds
// However, a cupcake+ build with no data does not contain this table which will
// cause an exception in the SQL stack. The following line is a special case to
// let the caller of the query have a chance to recover and avoid the exception
if (TABLE_FAVORITES.equals(args.table)) {
return null;
} else if (TABLE_OLD_FAVORITES.equals(args.table)) {
args.table = TABLE_FAVORITES;
Cursor cursor = db.rawQuery("PRAGMA table_info(favorites);", null);
if (cursor != null) {
boolean exists = cursor.getCount() > 0;
cursor.close();
if (!exists) return null;
} else {
return null;
}
}

SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(args.table);
//查表返回Cursor
Cursor ret = qb.query(db, select, args.where, args.args, null, null, sort);
// the default Cursor interface does not support per-user observation
try {
AbstractCursor c = (AbstractCursor) ret;
c.setNotificationUri(getContext().getContentResolver(), url, forUser);
} catch (ClassCastException e) {
// details of the concrete Cursor implementation have changed and this code has
// not been updated to match -- complain and fail hard.
Log.wtf(TAG, "Incompatible cursor derivation!");
throw e;
}
return ret;
}

通过获取缓存中的DatabaseHelper(没有则创建)来查询数据库。

insert()

insert()最终也是进入到insertForUser()当中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
final int callingUser = UserHandle.getCallingUserId();
if (callingUser != desiredUserHandle) {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"Not permitted to access settings for other users");
}
if (LOCAL_LOGV) Slog.v(TAG, "insert(" + url + ") for user " + desiredUserHandle
+ " by " + callingUser);
//将Uri 转换成SQL语句
SqlArguments args = new SqlArguments(url);
//favorite表已经迁移,直接返回。
if (TABLE_FAVORITES.equals(args.table)) {
return null;
}

// Location providers的处理。
// Android的位置服务都是由一堆的provider来提供。
// Support enabling/disabling a single provider (using "+" or "-" prefix)
String name = initialValues.getAsString(Settings.Secure.NAME);
if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
if (!parseProviderList(url, initialValues)) return null;
}

//对于已经迁移到Global表中的Key/Value,则直接写入到Global表中。
if (name != null) {
if (sSecureGlobalKeys.contains(name) || sSystemGlobalKeys.contains(name)) {
if (!TABLE_GLOBAL.equals(args.table)) {
if (LOCAL_LOGV) Slog.i(TAG, "Rewrite of insert() of now-global key " + name);
}
args.table = TABLE_GLOBAL; // next condition will rewrite the user handle
}
}

// Check write permissions only after determining which table the insert will touch
checkWritePermissions(args);
// The global table is stored under the owner, always
if (TABLE_GLOBAL.equals(args.table)) {
desiredUserHandle = UserHandle.USER_OWNER;
}
//寻找到table对应的LRU SettingsCache
SettingsCache cache = cacheForTable(desiredUserHandle, args.table);
String value = initialValues.getAsString(Settings.NameValueTable.VALUE);
if (SettingsCache.isRedundantSetValue(cache, name, value)) {
//该Key/Value在对应的SettingsCache中已经存在,直接返回对应的Uri
return Uri.withAppendedPath(url, name);
}
//UserHandle正在操作的计数 + 1
final AtomicInteger mutationCount = sKnownMutationsInFlight.get(desiredUserHandle);
mutationCount.incrementAndGet();
//获取UserHandle对应的DatabaseHelper和DB
DatabaseHelper dbH = getOrEstablishDatabase(desiredUserHandle);
SQLiteDatabase db = dbH.getWritableDatabase();
//插入进去
final long rowId = db.insert(args.table, null, initialValues);
//UserHandle正在操作的计数 - 1
mutationCount.decrementAndGet();
//插入失败,返回空
if (rowId <= 0) return null;
//保存Key/Value到SettingsCache中。
SettingsCache.populate(cache, initialValues); // before we notify
if (LOCAL_LOGV) Log.v(TAG, args.table + " <- " + initialValues
+ " for user " + desiredUserHandle);
// Note that we use the original url here, not the potentially-rewritten table name
//封装Uri
url = getUriFor(url, initialValues, rowId);
//notify一些重要的监听者
//比如BackUpManager、ContentResolver框架、SystemProperties的监听者。
sendNotify(url, desiredUserHandle);
return url;

这里主要做了几件事情:(1)权限检查;(2)处理位置相关的provider;(3)处理Global表的迁移;(4)如果是新的值则插入到DB;(5) 写入到缓存;(6)通知一些监听Key/Value值变化的人,具体在下面:

  • sendNotify()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//获取不同表在SystemProperties中的监视点,
//该值变化时,对应的监听者会重新获取新的值。
//只针对Global、System、Secure
boolean backedUpDataChanged = false;
String property = null, table = uri.getPathSegments().get(0);
final boolean isGlobal = table.equals(TABLE_GLOBAL);
if (table.equals(TABLE_SYSTEM)) {
property = Settings.System.SYS_PROP_SETTING_VERSION;
backedUpDataChanged = true;
} else if (table.equals(TABLE_SECURE)) {
property = Settings.Secure.SYS_PROP_SETTING_VERSION;
backedUpDataChanged = true;
} else if (isGlobal) {
property = Settings.Global.SYS_PROP_SETTING_VERSION; // this one is global
backedUpDataChanged = true;
}
//通知对应的监听者。
if (property != null) {
long version = SystemProperties.getLong(property, 0) + 1;
if (LOCAL_LOGV) Log.v(TAG, "property: " + property + "=" + version);
SystemProperties.set(property, Long.toString(version));
}
// Inform the backup manager about a data change
if (backedUpDataChanged) {
mBackupManager.dataChanged();
}
// Now send the notification through the content framework.
String notify = uri.getQueryParameter("notify");
if (notify == null || "true".equals(notify)) {
final int notifyTarget = isGlobal ? UserHandle.USER_ALL : userHandle;
final long oldId = Binder.clearCallingIdentity();
try {
getContext().getContentResolver().notifyChange(uri, null, true, notifyTarget);
} finally {
Binder.restoreCallingIdentity(oldId);
}
if (LOCAL_LOGV) Log.v(TAG, "notifying for " + notifyTarget + ": " + uri);
} else {
if (LOCAL_LOGV) Log.v(TAG, "notification suppressed: " + uri);
}

其实就是三个,BackUpManager、ContentResolver框架、通过SystemProperties监听Settings变化的组件。后期定制化SettingsProvider时可以在这里加上自己的监听者

delete()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int callingUser = UserHandle.getCallingUserId();
if (LOCAL_LOGV) Slog.v(TAG, "delete() for user " + callingUser);
SqlArguments args = new SqlArguments(url, where, whereArgs);
//处理已经迁移的favorite和特殊的Global
if (TABLE_FAVORITES.equals(args.table)) {
return 0;
} else if (TABLE_OLD_FAVORITES.equals(args.table)) {
args.table = TABLE_FAVORITES;
} else if (TABLE_GLOBAL.equals(args.table)) {
callingUser = UserHandle.USER_OWNER;
}
checkWritePermissions(args);
//操作计数
final AtomicInteger mutationCount = sKnownMutationsInFlight.get(callingUser);
mutationCount.incrementAndGet();
DatabaseHelper dbH = getOrEstablishDatabase(callingUser);
SQLiteDatabase db = dbH.getWritableDatabase();
//删除
int count = db.delete(args.table, args.where, args.args);
mutationCount.decrementAndGet();
if (count > 0) {
//删除成功,移除所有LRU Settings cache
invalidateCache(callingUser, args.table); // before we notify
//通知
sendNotify(url, callingUser);
}
//重新从表中获取所有的的值到Settings cache中。
startAsyncCachePopulation(callingUser);
if (LOCAL_LOGV) Log.v(TAG, args.table + ": " + count + " row(s) deleted");
return count;

delete的流程很多地方跟insert很相似。值得注意的是,在删除后,会先清空该表在LRU SettingsCache中对应UserHandle所Mapping的所有的值,然后再异步从DB中重新全部读取一遍。

update()

update()的流程跟delete()近乎一致,而且使用的地方也非常少,就此略过。

call()

该方法主要提供给adb shell commands使用,提供一种快速获取Settings中Key/Value的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public Bundle call(String method, String request, Bundle args) {
int callingUser = UserHandle.getCallingUserId();
//获取正确的UserHandle
if (args != null) {
int reqUser = args.getInt(Settings.CALL_METHOD_USER_KEY, callingUser);
if (reqUser != callingUser) {
callingUser = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), reqUser, false, true,
"get/set setting for user", null);
if (LOCAL_LOGV) Slog.v(TAG, " access setting for user " + callingUser);
}
}
//获取对应的DatabaseHelper和SettingsCache
DatabaseHelper dbHelper;
SettingsCache cache;
//get方法
if (Settings.CALL_METHOD_GET_SYSTEM.equals(method)) {
if (LOCAL_LOGV) Slog.v(TAG, "call(system:" + request + ") for " + callingUser);
dbHelper = getOrEstablishDatabase(callingUser);
cache = sSystemCaches.get(callingUser);
return lookupValue(dbHelper, TABLE_SYSTEM, cache, request);
}
if (Settings.CALL_METHOD_GET_SECURE.equals(method)) {
if (LOCAL_LOGV) Slog.v(TAG, "call(secure:" + request + ") for " + callingUser);
dbHelper = getOrEstablishDatabase(callingUser);
cache = sSecureCaches.get(callingUser);
return lookupValue(dbHelper, TABLE_SECURE, cache, request);
}
if (Settings.CALL_METHOD_GET_GLOBAL.equals(method)) {
if (LOCAL_LOGV) Slog.v(TAG, "call(global:" + request + ") for " + callingUser);
// fast path: owner db & cache are immutable after onCreate() so we need not
// guard on the attempt to look them up
return lookupValue(getOrEstablishDatabase(UserHandle.USER_OWNER), TABLE_GLOBAL,
sGlobalCache, request);
}
//put方法
final String newValue = (args == null)
? null : args.getString(Settings.NameValueTable.VALUE);
final ContentValues values = new ContentValues();
values.put(Settings.NameValueTable.NAME, request);
values.put(Settings.NameValueTable.VALUE, newValue);
//调用insertForUser插入
if (Settings.CALL_METHOD_PUT_SYSTEM.equals(method)) {
if (LOCAL_LOGV) Slog.v(TAG, "call_put(system:" + request + "=" + newValue + ") for " + callingUser);
insertForUser(Settings.System.CONTENT_URI, values, callingUser);
} else if (Settings.CALL_METHOD_PUT_SECURE.equals(method)) {
if (LOCAL_LOGV) Slog.v(TAG, "call_put(secure:" + request + "=" + newValue + ") for " + callingUser);
insertForUser(Settings.Secure.CONTENT_URI, values, callingUser);
} else if (Settings.CALL_METHOD_PUT_GLOBAL.equals(method)) {
if (LOCAL_LOGV) Slog.v(TAG, "call_put(global:" + request + "=" + newValue + ") for " + callingUser);
insertForUser(Settings.Global.CONTENT_URI, values, callingUser);
} else {
Slog.w(TAG, "call() with invalid method: " + method);
}
return null;
}

先转化UserHandle, 再根据不同的方法进行后续的执行:get — lookupValue; put – insertForUser.
insertForUser上面已经介绍过。lookupValue则是具体查Key对应Value值的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private Bundle lookupValue(DatabaseHelper dbHelper, String table,
final SettingsCache cache, String key) {
if (cache == null) {
Slog.e(TAG, "cache is null for user " + UserHandle.getCallingUserId() + " : key=" + key);
return null;
}
//先尝试从SettingsCache中获取
synchronized (cache) {
Bundle value = cache.get(key);
if (value != null) {
if (value != TOO_LARGE_TO_CACHE_MARKER) {
return value;
}
// else we fall through and read the value from disk
} else if (cache.fullyMatchesDisk()) {
// Fast path (very common). Don't even try touch disk
// if we know we've slurped it all in. Trying to
// touch the disk would mean waiting for yaffs2 to
// give us access, which could takes hundreds of
// milliseconds. And we're very likely being called
// from somebody's UI thread...
return NULL_SETTING;
}
}
//缓存中不存在再尝试从DB中获取,并保存到SettingsCache中。
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = db.query(table, COLUMN_VALUE, "name=?", new String[]{key},
null, null, null, null);
if (cursor != null && cursor.getCount() == 1) {
cursor.moveToFirst();
return cache.putIfAbsent(key, cursor.getString(0));
}
} catch (SQLiteException e) {
Log.w(TAG, "settings lookup error", e);
return null;
} finally {
if (cursor != null) cursor.close();
}
cache.putIfAbsent(key, null);
return NULL_SETTING;
}