news 2026/9/26 18:05:40

Android 内容提供器入门:用 ContentResolver 查询数据与权限配置实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android 内容提供器入门:用 ContentResolver 查询数据与权限配置实战

1. 为什么你查联系人总是返回空 Cursor

很多人第一次写 Android 内容提供器相关代码时,都会遇到一个很迷惑的现象:代码没报错,ContentResolver.query()也正常返回了一个 Cursor 对象,但getCount()就是 0,界面上什么都显示不出来。我试过在模拟器上反复检查 URI、列名、排序参数,最后发现真正的问题出在权限上——清单里少了一行<uses-permission>,或者运行时权限没申请,系统直接把查询结果过滤成了空集。

内容提供器(ContentProvider)是 Android 四大组件之一,它把数据封装成类似"数据库表"的接口,让不同应用之间可以安全地共享数据。而 ContentResolver 就是你作为客户端去访问这些数据的入口。它适合谁?适合所有需要在应用之间读写共享数据(联系人、媒体库、日历、用户字典等)的 Android 开发者,尤其是刚接触跨进程数据访问、对 URI 和 Cursor 还不熟的同学。

这篇内容我会带你跑通一个最小闭环:从清单权限声明,到 URI 构造,到 ContentResolver 查询,再到 Cursor 读取和运行时权限申请,最后用一个读取联系人的完整例子验证结果。每一步都给可复制的代码骨架,你照着改包名就能用。

2. 前置准备:权限、URI 和查询四要素

在动手写查询之前,先把三件事理清楚,否则后面报错你会不知道从哪查。

第一是权限。内容提供器的读权限分两种:一种是普通权限,在清单里声明后安装时系统自动授予;另一种是危险权限(比如读取联系人READ_CONTACTS),除了清单声明,还必须在运行时动态申请。你查的是哪个提供器,就要去它的官方文档里找确切的权限名,不能猜。

第二是 URI。每个提供器都暴露一个或多个 Content URI,格式通常是content://authority/path。比如联系人提供器的ContactsContract.Contacts.CONTENT_URI,用户字典的UserDictionary.Words.CONTENT_URI。URI 决定了你访问哪张"表"。

第三是查询四要素,对应query()的五个参数(URI 之外):projection(要返回哪些列)、selection(筛选条件)、selectionArgs(条件里的占位参数)、sortOrder(排序)。这四样和 SQL 的 SELECT 子句一一对应,理解了 SQL 就理解了它。

注意:查询一定要放在子线程或异步任务里执行,主线程查询大数据量会触发 ANR。本文为了演示清晰用同步写法,实际项目请用 CursorLoader 或协程。

3. 可复制配置:清单权限与查询代码骨架

3.1 清单文件声明权限

以读取联系人为例,在AndroidManifest.xml的<manifest>下、<application>外添加:

<uses-permission android:name="android.permission.READ_CONTACTS" />

如果你还要写数据,再加WRITE_CONTACTS。声明之后,普通权限安装即授予;危险权限还需要下一步的运行时申请。

3.2 构造查询并读取 Cursor

下面是一个查询联系人姓名和 ID 的骨架,注意 projection 里必须包含_ID列,否则后面配合 SimpleCursorAdapter 会直接崩:

// 1. 定义要返回的列 String[] projection = { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME }; // 2. 筛选条件:只取有电话号码的联系人 String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " = ?"; String[] selectionArgs = { "1" }; // 3. 排序 String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " ASC"; // 4. 执行查询 Cursor cursor = getContentResolver().query( ContactsContract.Contacts.CONTENT_URI, projection, selection, selectionArgs, sortOrder ); // 5. 读取结果 if (cursor != null) { int idIndex = cursor.getColumnIndex(ContactsContract.Contacts._ID); int nameIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); while (cursor.moveToNext()) { String id = cursor.getString(idIndex); String name = cursor.getString(nameIndex); Log.d("ContactQuery", "id=" + id + ", name=" + name); } cursor.close(); // 必须关闭,否则泄漏 } else { Log.e("ContactQuery", "cursor is null, 查询失败"); }

这里 selection 用了?占位符加 selectionArgs,而不是把用户输入拼进字符串。这是防 SQL 注入的关键:用户输入被当作参数绑定,不会被解释成 SQL 语句。如果你写成"name = " + userInput,恶意输入就可能拼出破坏性语句。

3.3 运行时权限申请

Android 6.0 起,危险权限必须动态申请。在查询前检查并请求:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( this, new String[]{ Manifest.permission.READ_CONTACTS }, REQUEST_CODE_READ_CONTACTS); } else { queryContacts(); // 已授权,直接查 }

然后在onRequestPermissionsResult里根据授权结果决定是否继续查询。授权通过再调用查询方法,否则提示用户。

4. 验证请求:跑通一次完整的联系人读取

把上面的代码串起来,完整流程是这样的:

先在 Activity 的onCreate里调用权限检查逻辑。用户点"允许"后,onRequestPermissionsResult收到PERMISSION_GRANTED,触发queryContacts()。查询返回 Cursor 后,遍历打印日志。

实测下来,在模拟器里预置几个联系人,运行后 Logcat 会输出类似:

D/ContactQuery: id=1, name=Alice D/ContactQuery: id=2, name=Bob D/ContactQuery: id=3, name=Carol

如果你想把结果直接显示到列表,可以用 SimpleCursorAdapter 绑定 ListView,但记得 Cursor 必须含_ID列:

String[] fromColumns = { ContactsContract.Contacts.DISPLAY_NAME }; int[] toViews = { android.R.id.text1 }; SimpleCursorAdapter adapter = new SimpleCursorAdapter( this, android.R.layout.simple_list_item_1, cursor, fromColumns, toViews, 0); listView.setAdapter(adapter);

看到列表里出现联系人名字,就说明从提供器获取数据的最小闭环跑通了。整个过程的核心就是:权限到位、URI 正确、projection 含_ID、Cursor 及时关闭。

5. 本篇常见错排查

查询返回 null:部分提供器内部出错时返回 null,另一些直接抛异常。先判空再使用,别直接cursor.getCount()。

getCount() 为 0 但数据明明存在:九成是权限问题。检查清单权限名是否拼错、运行时权限是否真的授予。可以在设置里手动看应用权限状态。

列名找不到,getColumnIndex 返回 -1:projection 里没写这一列,或者列名拼错。用getColumnIndexOrThrow()能在开发期直接暴露问题。

配合 ListView 崩溃:Cursor 缺少_ID列。SimpleCursorAdapter 强制要求结果集里有_ID,查询时务必带上。

主线程查询卡顿或 ANR:把查询放到子线程、CursorLoader 或协程里,别在主线程做大数据量遍历。

SQL 注入风险:永远用?加 selectionArgs,不要字符串拼接用户输入。

6. 接入与调试:把查询能力接到实际工程

如果你在本地调试时想快速验证某个提供器的返回结构,或者需要对比不同模型给出的查询代码是否正确,可以借助在线工具做交叉验证。我平时会把查询逻辑和报错信息整理好,用模型对话快速定位是 URI 写错还是权限漏了,省去反复翻文档的时间。

对于需要长期写 Android 代码、频繁调试 ContentResolver 和权限逻辑的场景,可以考虑用 Coding Plan 把这类重复性的代码骨架生成和排错固化下来,减少手动试错。

具体入口:

  • 想直接对话验证查询代码和报错:模型对话 https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
  • 长期编码与 Agent 工作流:Coding Plan https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
  • 生成和管理接入密钥:API Keys https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
  • 接入文档与参数说明:接入文档 https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite

官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=

最后留一个实用习惯:每次写完查询,先在 Logcat 里打印cursor.getColumnNames(),确认返回的列和你 projection 里写的一致。这一步能帮你提前发现大部分列名不匹配的问题,比等到界面空白再回头查要快得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 18:03:58

Notepad++深度配置指南:从安装到高效文本处理的完整工作流

1. 这不是“又一个编辑器安装教程”&#xff0c;而是你每天打开电脑后最该花15分钟做的事Notepad 官方下载、完整安装、全套优化配置——这三件事加起来&#xff0c;其实只解决一个核心问题&#xff1a;让文本处理这件事&#xff0c;从“能用”变成“顺手到不用思考”。我做技术…

作者头像 李华
网站建设 2026/9/26 18:03:58

Vue3 源码中的位掩码:从 ShapeFlags 到 PatchFlags 的高效设计

"if (shapeFlag & ShapeFlags.COMPONENT)" 这种代码&#xff0c;第一次在 Vue3 源码里看到时&#xff0c;我愣了好几秒。日常业务开发里判断类型&#xff0c;我们早就习惯了component.isFunctional或者type functional这种直白的写法&#xff0c;突然蹦出一个按…

作者头像 李华
网站建设 2026/9/26 18:03:23

豆瓣评分逆势上涨背后:“后劲大”电影的创作密码与口碑发酵

《豆瓣评分上涨&#xff01;观众喊话&#xff1a;开年好片&#xff0c;后劲太大&#xff01;》这个话题&#xff0c;我太有感触了。开年到现在&#xff0c;我身边好几个从不主动聊电影的朋友&#xff0c;都在同一个群里安利同一部片子&#xff0c;而且口径惊人一致&#xff1a;…

作者头像 李华
网站建设 2026/9/26 18:01:14

Java后端转AI:避开三大坑,守住根据地,掌握AI工程化核心技能

先说实话&#xff1a;2026年还在纠结“Java是不是过时了”“要不要转Python去做AI”的开发者&#xff0c;大概率还没搞明白AI赛道真正缺什么。我见过太多Java后端的朋友&#xff0c;一看各种AI大模型的消息就焦虑&#xff0c;觉得自己的技术栈要被淘汰了。但实际去接触那些真正…

作者头像 李华
网站建设 2026/9/26 18:00:45

四足机器人建模与步态规划:用 TaoToken 统一 Key 打通仿真配置链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 17:58:26

Linux学习路线与运维实战:从命令到系统排障的完整指南

不知道你是不是和我当年一样&#xff0c;看到“Linux学习”这四个字&#xff0c;第一反应是打开浏览器搜“linux常用命令大全”&#xff0c;然后收藏了一堆文章&#xff0c;结果三天后全忘光。我入行做运维的头一年&#xff0c;最深的体会就是&#xff1a;Linux根本不该靠“背命…

作者头像 李华