28、ADB 与 Content Provider:adb shell content query(查询 ContentProvider)、adb shell content insert(插入数据)、实战:读取联系人。
Content Provider 是 Android 四大组件之一,说白了就是「跨进程数据共享的桥梁」。你想想看,一个 App 的数据,凭什么让另一个 App 随便读?就是靠 Content Provider 暴露出来的接口。而 ADB 的 content 命令,让我们能在 shell 里直接调用这些接口,调试起来非常方便。
我个人习惯在开发阶段用 adb shell content 快速验证数据是否正确,省得每次都要写测试代码。今天我们就来聊聊怎么用 ADB 操作 Content Provider,顺便实战一下读取系统联系人。
28.1 adb shell content query:查询数据
content query 是 Content Provider 调试中最常用的命令。它的基本语法如下:
adb shell content query --uri <URI> [--projection <列名>] [--where <条件>] [--sort <排序>]
我来拆解一下:
- --uri:必填,指定 Content Provider 的 URI,比如
content://settings/secure - --projection:可选,指定要查询的列,多个列用逗号分隔
- --where:可选,筛选条件,注意要用
%20代替空格 - --sort:可选,排序字段
举个例子:查询系统设置中的屏幕超时时间
adb shell content query --uri content://settings/system --projection name,value --where "name='screen_off_timeout'"
输出结果类似:
Row: 0 name=screen_off_timeout, value=30000
我在项目中遇到过一个问题:用 --where 参数时,条件里如果包含特殊字符,比如单引号,很容易解析失败。我的建议是尽量用双引号包裹整个条件,内部再用单引号包裹字符串值。
28.2 adb shell content insert:插入数据
插入数据的语法也很直观:
adb shell content insert --uri <URI> --bind <类型:列名:值> [--bind ...]
这里的 --bind 参数用来绑定列名和值,格式是 类型:列名:值。类型可以是 s(字符串)、i(整数)、l(长整型)、f(浮点数)等。
小技巧:插入数据前,最好先用 query 确认一下表结构,看看有哪些列、什么类型。我曾经直接凭记忆插入,结果类型写错了,数据死活写不进去。
举个例子,向自定义的备忘录 Content Provider 插入一条记录:
adb shell content insert --uri content://com.example.memo/memos --bind s:title:测试备忘录 --bind s:content:这是通过ADB插入的内容 --bind i:priority:1
如果插入成功,会返回新插入行的 URI,比如 content://com.example.memo/memos/3。
28.3 实战:读取联系人
系统联系人的 Content Provider URI 是 content://com.android.contacts/contacts。我们来实战一下,用 ADB 读取所有联系人姓名和电话号码。
首先,查询联系人表:
adb shell content query --uri content://com.android.contacts/contacts --projection _id,display_name
输出类似:
Row: 0 _id=1, display_name=张三
Row: 1 _id=2, display_name=李四
然后,根据联系人的 _id 查询电话号码。电话号码存在另一个表里,URI 是 content://com.android.contacts/data,需要加条件筛选:
adb shell content query --uri content://com.android.contacts/data --projection data1,mimetype --where "raw_contact_id=1%20AND%20mimetype='vnd.android.cursor.item/phone_v2'"
这里 %20 是空格的 URL 编码,因为 shell 里直接写空格会被拆分成多个参数。输出结果:
Row: 0 data1=13800138000, mimetype=vnd.android.cursor.item/phone_v2
注意:从 Android 10 开始,系统对联系人数据的访问权限收紧了。即使有 READ_CONTACTS 权限,通过 ADB shell 也可能查不到数据。这是因为 ADB shell 的 UID 是 shell,不是你的 App。如果遇到这种情况,可以试试用 run-as 命令切换到你的 App 进程再查询。
嗯,这里要特别提醒一下:我曾经在 Android 11 的设备上调试联系人读取,用 adb shell content query 死活返回空结果,折腾了半天才发现是权限模型变了。后来改用 adb shell run-as com.example.myapp content query ... 才成功。
28.4 核心知识体系
下面这张图总结了本章的核心逻辑,方便你快速回顾:
28.5 常用操作速查表
| 操作 | 命令示例 | 说明 |
|---|---|---|
| 查询所有联系人 | adb shell content query --uri content://com.android.contacts/contacts --projection _id,display_name |
获取联系人 ID 和姓名 |
| 查询电话号码 | adb shell content query --uri content://com.android.contacts/data --projection data1 --where "raw_contact_id=1%20AND%20mimetype='vnd.android.cursor.item/phone_v2'" |
根据联系人 ID 查电话 |
| 插入联系人 | adb shell content insert --uri content://com.android.contacts/raw_contacts --bind i:account_id:1 |
注意:插入联系人需要多个步骤,这里仅示意 |
| 查询系统设置 | adb shell content query --uri content://settings/system --where "name='screen_brightness'" |
获取屏幕亮度值 |
我的个人习惯:调试 Content Provider 时,我会先开一个终端窗口专门跑 ADB 命令,另一个窗口看 Logcat。这样一边查数据一边看日志,定位问题特别快。另外,--where 条件里的空格问题经常坑人,我建议你直接复制上面的命令模板,把条件里的空格全换成 %20,省得踩坑。
好了,关于 ADB 操作 Content Provider 的内容就聊到这里。核心就是记住 query 和 insert 的语法,以及 --bind 参数的类型写法。实战读取联系人时,注意 Android 10 以上的权限变化,用 run-as 绕过限制。这些技巧在实际开发中非常实用,希望能帮到你。