尊重原创,转载请注明出处!
创作不易,如有帮助请点赞支持~
问题背景
开发以及定位问题的过程中,遇到 ContentProvider 相关的问题时,可以通过 adb shell content
的命令来进行调试。直接执行这个命令会在命令行打印它的用法(只截取了最常用的 update 和 query 的说明):
> adb shell content
usage: adb shell content [subcommand] [options]
usage: adb shell content update --uri <URI> [--user <USER_ID>] [--where <WHERE>]
<WHERE> is a SQL style where clause in quotes (You have to escape single quotes - see example below).
Example:
# Change "new_setting" secure setting to "newer_value".
adb shell content update --uri content://settings/secure --bind value:s:newer_value --where "name='new_setting'"
usage: adb shell content query --uri <URI> [--user <USER_ID>] [--projection <PROJECTION>] [--where <WHERE>] [--sort <SORT_ORDER>]
<PROJECTION> is a list of colon separated column names and is formatted:
<COLUMN_NAME>[:<COLUMN_NAME>...]
<SORT_ORDER> is the order in which rows in the result should be sorted.
Example:
# Select "name" and "value" columns from secure settings where "name" is equal to "new_setting" and sort the result by name in ascending order.
adb shell content query --uri content://settings/secure --projection name:value --where "name='new_setting'" --sort "name ASC"
[ERROR] Unsupported operation: null
其中 query 用法很简单,直接对照实例说明修改一下就行了,比如查询屏幕超时时间:
XXX:/ # content query --uri content://settings/system --projection name:value --where "name='screen_off_timeout'"
Row: 0 name=screen_off_timeout, value=60000
但是执行 update 命令的时候遇到了麻烦,设置不生效:
XXX:/ # content update --uri content://settings/system --bind value:i:30000 --where "name='screen_off_timeout'"
XXX:/ # content query --uri content://settings/system --projection name:value --where "name='screen_off_timeout'"
Row: 0 name=screen_off_timeout, value=60000
同样的命令,分别在 Android 5,7,8 上测试,发现只有 Android 5 能成功修改,而 Android 7,8 均设置无法生效。看起来只能从源码的角度分析问题了。
源码分析
以下分析的代码均为 Android 8 的源码~
content 命令源码位于以下路径: /frameworks/base/cmds/content/Content.java
首先来看看它的 main 方法,看看是怎么解析并执行命令的:
public static void main(String[] args) {
Parser parser = new Parser(args);
// 解析命令参数,得到对应的Command
Command command = parser.parseCommand();
if (command != null) {
// 调用对应的Command.execute执行命令
command.execute();
}
}
我们关注的是 update 命令,它是在 parseUpdateCommand
方法中进行解析的:
private UpdateCommand parseUpdateCommand() {
......
ContentValues values = new ContentValues();
for (String argument; (argument = mTokenizer.nextArg())!= null;) {
......
// 解析"--bind"参数
if (ARGUMENT_BIND.equals(argument)) {
parseBindValue(values);
}
......
}
......
// 解析命令参数的结果,是通过UpdateCommand执行的
return new UpdateCommand(uri, userId, values, where);
}
要确认我们的命令是否有问题,关键还是要看 parseBindValue
方法的实现:
private void parseBindValue(ContentValues values) {
......
// 关键代码就这3句,格式应该为:--bind ,<column>:<type>:<value>
String column = argument.substring(0, firstColonIndex);
String type = argument.substring(firstColonIndex + 1, secondColonIndex);
String value = argument.substring(secondColonIndex + 1);
......
// 下面省略部分代码,其实就是把value转换为type类型,再和column成对存放到values中
if (TYPE_STRING.equals(type)) {
values.put(column, value);
}
......
}
从 parseBindValue
的代码实现来看,我们的参数应该没问题才对啊,于是继续往下看调用流程。
UpdateCommand
最终还是通过 IContentProvider.update
进行更新的,服务端就是 SettingsProvider。
[frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java]
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
......
// 取出key为"name"的值
String name = values.getAsString(Settings.Secure.NAME);
// 检查name是否合法,即是否属于UCS(Universal Character Set)
if (!isKeyValid(name)) {
return 0;
}
// 取出key为"value"的值,进行更新
String value = values.getAsString(Settings.Secure.VALUE);
switch (args.table) {
......
case TABLE_SYSTEM: {
final int userId = UserHandle.getCallingUserId();
return updateSystemSetting(args.name, value, userId) ? 1 : 0;
}
......
}
}
到这里,就已经发现问题了。我们传的 ContentValues 只有 value,没有 name 啊,那么这里肯定会因为 name 不合法而直接 return!
再回到前面 parseUpdateCommand
,发现它是 for 循环遍历参数进行解析的,也就是 “–bind” 参数可以指定不止一个!
那就直接做下测试,验证我们的猜想吧:
XXX:/ # content update --uri content://settings/system --bind name:s:screen_off_timeout --bind value:i:30000 --where "name='screen_off_timeout'"
XXX:/ # content query --uri content://settings/system --projection name:value --where "name='screen_off_timeout'"
Row: 0 name=screen_off_timeout, value=30000
果然,命令执行成功了,对应设置项也修改成功了!
总结
1、查询 Settings 的相关设置项可以通过以下指令(<KEY> 表示需要查询的设置项):
content query --uri content://settings/system --projection name:value --where "name=<KEY>"
2、更新 Settings 的相关设置项可以通过以下指令(<KEY> 表示需要更新的设置项,<VALUE> 表示要更新的值,前面的 <TYPE> 也可以根据需要做相应的修改):
content update --uri content://settings/system --bind name:<TYPE1>:<KEY> --bind value:<TYPE2>:<VALUE> --where "name=<KEY>"
3、对于其他的 ContentProvider,可能没做 key 的限制,直接通过示例的命令即可更新:
content update --uri <URI> --bind value:<TYPE>:<VALUE> --where "name=<KEY>"
4、分析问题千万不能思维定式,比如之前一直以为 “–bind” 参数只能有一个,所以没往正确的方向考虑。遇到命令执行失败或者执行结果与预期不符的情况,除了百度以外,最好的方法还是得研究源码!