[ Android 调试技巧 ] 为什么 content update 命令修改设置不生效?

本文介绍了解决Android中使用contentupdate命令修改设置时遇到的问题。通过详细分析命令参数和源码,揭示了命令失效的原因,并给出了正确的命令格式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[ Android 调试技巧 ] 为什么 content update 命令修改设置不生效?

尊重原创,转载请注明出处!
创作不易,如有帮助请点赞支持~

问题背景

开发以及定位问题的过程中,遇到 ContentProvider 相关的问题时,可以通过 adb shell content 的命令来进行调试。直接执行这个命令会在命令行打印它的用法(只截取了最常用的 updatequery 的说明):

> 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” 参数只能有一个,所以没往正确的方向考虑。遇到命令执行失败或者执行结果与预期不符的情况,除了百度以外,最好的方法还是得研究源码!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值