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 核心知识体系

下面这张图总结了本章的核心逻辑,方便你快速回顾:

ADB Content Provider 操作体系 adb shell content query 查询 insert 插入 update / delete --uri --projection --where --sort --uri --bind 类型:列名:值 实战:读取系统联系人 注意:Android 10+ 权限收紧,建议使用 run-as 切换进程

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 的内容就聊到这里。核心就是记住 queryinsert 的语法,以及 --bind 参数的类型写法。实战读取联系人时,注意 Android 10 以上的权限变化,用 run-as 绕过限制。这些技巧在实际开发中非常实用,希望能帮到你。

公众号:蓝海资料掘金营,微信 deep3321