news 2026/9/23 13:51:16

Android 获取最新短信实战:ContentResolver 查询与 TaoToken 配置骨架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android 获取最新短信实战:ContentResolver 查询与 TaoToken 配置骨架

1. 短信读取这件事,为什么总在真机上翻车

Android 获取最新短信,说白了就是拿ContentResolver去查content://sms/这个系统收件箱,按date desc倒序排一下,取第一条就完事。听起来三行代码能搞定,但真正在真机上跑,你会发现坑一个接一个:模拟器上查得到、真机返回空 Cursor;权限在 Android 13 上申请了还是 SecurityException;查出来的date字段是毫秒时间戳,直接塞进SimpleDateFormat格式化出来是 1970 年;更别提有些厂商 ROM 对短信 Provider 做了额外限制。

这篇就聚焦一件事:在 Android 端通过 ContentResolver 读取收件箱里最新的一条短信,把运行时权限申请、按日期倒序查询、结果解析这条链路完整跑通。同时给出一份 TaoToken 统一 Key/API 通道的settings.json配置骨架,方便你在调试短信解析逻辑时,把模型调用也一并接进来做内容识别或格式化。适合正在做短信验证码自动填充、通知类短信归类、或者单纯想跑通 ContentResolver 查询的开发者。

我试过在几台不同 Android 版本的机器上反复验证,下面这套流程是能稳定拿到最新一条短信的。核心检索词就三个:Android、短信、ContentResolver,围绕它们展开。

2. 前置准备:权限、Provider 与 TaoToken 通道

2.1 短信权限的声明与运行时申请

读取短信属于危险权限,必须在AndroidManifest.xml里声明,然后在运行时动态申请。注意READ_SMSRECEIVE_SMS是两回事:前者是读已有短信,后者是监听新到短信。本篇只读收件箱,所以核心是READ_SMS

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.smsreader"> <uses-permission android:name="android.permission.READ_SMS" /> <!-- 如果后续要监听新短信,再加下面这条 --> <uses-permission android:name="android.permission.RECEIVE_SMS" /> <application android:allowBackup="true" android:label="SmsReader" android:theme="@style/Theme.AppCompat"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

运行时申请用ActivityResultLauncher,别再用老的onRequestPermissionsResult回调,容易漏掉分支:

private final ActivityResultLauncher<String> smsPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { if (granted) { readLatestSms(); } else { Toast.makeText(this, "未授予短信读取权限", Toast.LENGTH_SHORT).show(); } }); private void ensurePermissionAndRead() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED) { readLatestSms(); } else { smsPermissionLauncher.launch(Manifest.permission.READ_SMS); } }

注意:Android 6.0 以下权限在安装时授予,但现在的项目基本不用管那么老的版本。Android 13 及以上对短信权限没有额外收紧,但部分厂商 ROM 会在应用商店审核层面限制,真机调试时如果一直返回空,先确认权限状态是不是PERMISSION_GRANTED

2.2 TaoToken 统一 Key/API 通道的 settings.json 骨架

短信读出来之后,很多时候你要做的是把短信内容丢给模型做解析,比如识别验证码、判断是不是营销短信。这时候如果每个模型都单独配 Key,管理起来很乱。TaoToken 提供统一 Key 和 API 通道,官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。

下面是一份settings.json配置骨架,放在项目根目录或者你的配置目录下,用于统一管理模型通道。字段名按你实际接入的 SDK 调整,这里给的是通用结构:

{ "taotoken": { "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的统一Key", "defaultModel": "claude-sonnet-4-20250514", "timeoutMs": 30000, "retry": { "maxAttempts": 3, "backoffMs": 800 } }, "smsParser": { "model": "claude-sonnet-4-20250514", "systemPrompt": "你是一个短信解析助手,从短信正文中提取验证码、发送方、业务类型,输出 JSON。", "maxTokens": 512 } }

Key 的获取在控制台的 API Keys 页面:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。如果你只是想先验证模型能不能正常返回,可以直接用模型对话页面试一条:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。长期做编码或 Agent 类任务,可以看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。

提示:baseUrl末尾不要带斜杠,很多 SDK 拼接路径时会出双斜杠导致 404。API 地址统一用 https://taotoken.net/api ,不要加 UTM 参数到代码里。

3. 可复制配置:ContentResolver 查询最新短信

3.1 按 date desc 查询并取第一条

原始做法是query之后while (cursor.moveToNext())遍历全部,再在循环里判断i == cursor.getCount() - 1。这样写有两个问题:一是把整个收件箱都读了一遍,短信多的时候很慢;二是边界判断容易出错。既然只要最新一条,直接在查询参数里用sortOrder = "date desc",然后moveToFirst()就够了。

private void readLatestSms() { Cursor cursor = null; try { cursor = getContentResolver().query( Uri.parse("content://sms/inbox"), new String[]{"_id", "address", "body", "date", "type"}, null, null, "date desc" ); if (cursor == null) { Log.e("SmsReader", "cursor 为 null,Provider 可能不可用"); return; } if (!cursor.moveToFirst()) { Log.d("SmsReader", "收件箱为空"); return; } int idxAddress = cursor.getColumnIndex("address"); int idxBody = cursor.getColumnIndex("body"); int idxDate = cursor.getColumnIndex("date"); String address = cursor.getString(idxAddress); String body = cursor.getString(idxBody); long dateMillis = cursor.getLong(idxDate); String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) .format(new Date(dateMillis)); Log.d("SmsReader", "最新短信来自: " + address); Log.d("SmsReader", "时间: " + time); Log.d("SmsReader", "内容: " + body); } catch (SecurityException e) { Log.e("SmsReader", "缺少 READ_SMS 权限", e); } finally { if (cursor != null) { cursor.close(); } } }

几个关键点:content://sms/inbox只查收件箱,比content://sms/更精确,避免把已发送、草稿也带出来;sortOrder"date desc"让 Provider 在数据库层排序,比在 Java 层排序高效;moveToFirst()直接定位第一条,不用遍历。

3.2 结果解析与字段说明

短信 Provider 的字段里,常用的就这几个:

字段名类型说明
_idint短信在数据库中的主键
addressString发送方号码,可能是手机号或服务号
bodyString短信正文
datelong收到时间,毫秒时间戳
typeint1 为收件,2 为发件,3 为草稿

date字段是毫秒时间戳,直接new Date(long)再格式化就行。如果你拿到的是字符串,先Long.parseLong转一下。address有时为 null,比如某些系统短信,做展示时给个「未知」兜底。

如果你要把短信内容交给模型解析,可以在拿到body之后调用 TaoToken 通道。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有各语言 SDK 的调用示例。Claude Code 相关的接入说明在 https://taotoken.net/claudecode-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite 。

4. 真机验证:从安装到看到日志

4.1 验证步骤

第一步,把上面的权限声明和readLatestSms()代码放进项目,在MainActivity.onCreate里调用ensurePermissionAndRead()

第二步,用 USB 连接真机,打开开发者选项和 USB 调试,adb devices确认设备在线。

第三步,安装并启动应用,系统会弹出短信权限对话框,点允许。

第四步,用另一台手机给测试机发一条短信,内容随便写,比如「测试验证码 123456」。

第五步,看 Logcat,过滤 tagSmsReader,应该能看到类似输出:

D/SmsReader: 最新短信来自: 106xxxxxxxx D/SmsReader: 时间: 2025-01-15 14:32:08 D/SmsReader: 内容: 测试验证码 123456

如果日志里addressbody都正常,说明整条链路通了。这时候你可以把body丢给 TaoToken 的模型通道做进一步解析,比如提取验证码。

4.2 用 adb 直接验证 Provider 是否可读

有时候代码没问题,但真机就是返回空。可以先绕过应用,用 adb 直接查 Provider,确认系统层面是否允许:

adb shell content query --uri content://sms/inbox --projection address:body:date --sort "date DESC"

如果这条命令能返回数据,说明 Provider 正常,问题在应用权限或代码;如果返回No result found或者权限错误,那就是系统层面限制了。部分厂商 ROM 对content query也有保护,这条命令失败不代表应用一定读不到,但能作为参考。

5. 本篇常见错排查

5.1 返回空 Cursor 或 SecurityException

最常见的原因是权限没真正授予。检查checkSelfPermission的返回值,别只看对话框弹没弹。Android 11 及以上如果用户选了「仅本次允许」,应用退到后台再回来权限会失效,需要重新申请。另外,targetSdkVersion太高时,部分 ROM 会要求你在 Manifest 里同时声明READ_SMSRECEIVE_SMS才放行读取,可以先都加上试试。

还有一种情况是查询的 URI 写错了。content://sms/content://sms/inbox行为不同,前者包含所有类型,后者只有收件箱。如果你要的是最新收到的短信,用inbox更准。

5.2 时间格式化出来是 1970 年

date字段是毫秒时间戳,但如果你用cursor.getString(idxDate)拿到的是字符串,再Long.parseLong一般没问题。出 1970 通常是因为getColumnIndex返回了 -1,导致取到了错误的列,或者date字段本身为 0。先打印idxDate确认列索引有效,再打印原始dateMillis看是不是 0。

5.3 查询很慢或 ANR

如果你用了content://sms/并且没加sortOrder,Provider 可能返回全部短信,几千条的时候主线程直接卡死。两个改法:一是查询放到子线程,用ExecutorServiceCoroutine;二是 URI 用inbox并加"date desc",让数据库层排序并配合moveToFirst()只取一条。如果确实要读多条,用CursorLoader或者分页查询。

5.4 TaoToken 调用返回 401 或超时

先确认apiKey是不是从控制台复制的完整 Key,有没有多余空格。baseUrl用 https://taotoken.net/api ,不要自己拼/v1之类的路径,具体路径以接入文档为准。超时的话把timeoutMs调到 60000 试试,短信解析这种短文本一般不会超时,除非网络本身有问题。如果一直 401,去 API Keys 页面重新生成一个 Key 再试:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。

6. 把短信读取接进你的调试链路

短信读取本身不复杂,难的是权限状态、URI 选择、时间戳解析这几个细节。把content://sms/inbox+date desc+moveToFirst()这套组合用熟,基本能覆盖「获取最新一条短信」的所有场景。读出来之后,如果你要做内容识别,把body通过 TaoToken 的统一通道发给模型,settings.json里配好baseUrlapiKey就能跑。验证模型连通性用模型对话页面最快,长期编码任务再考虑 Coding Plan。接入过程中遇到报错,先查接入文档里的错误码说明,大部分问题都能定位到。

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

基于Python的灰度图像彩色化:特征计算与颜色迁移全解析

简介&#xff1a;针对“灰度图像彩色化”这一经典图像处理实验&#xff0c;资源提供了可直接运行的Python源码与三页实验报告&#xff0c;适合正在学习图像特征计算与表示的本科生、研究生&#xff0c;以及想快速上手图像彩色化实践的开发者。压缩包共36个文件&#xff0c;包含…

作者头像 李华
网站建设 2026/9/23 13:50:44

电梯电动车识别实战:从YOLO选型到训练调参的完整指南

简介&#xff1a;面向电梯监控场景的目标识别项目资源&#xff0c;用于识别电梯内视角的电动车与自行车&#xff0c;适合毕业设计、课程设计、实训及学科竞赛使用。项目基于电梯内视角数据集微调 YOLO 预训练模型&#xff0c;涉及迁移学习、目标检测与多目标跟踪等知识点&#…

作者头像 李华
网站建设 2026/9/23 13:50:41

飞腾D2000数据手册实战:从DDR4到PCIe的板级设计要点

简介&#xff1a;飞腾D2000数据手册是飞腾信息技术有限公司发布的官方技术文档&#xff0c;面向使用腾锐D2000系列处理器进行嵌入式开发、板卡设计及系统集成的软硬件工程师。压缩包内为1个PDF文件&#xff0c;整体约3.28MB&#xff0c;便于下载后按章节查阅。内容系统覆盖技术…

作者头像 李华
网站建设 2026/9/23 13:49:02

腾讯Agent Suite办公智能体套件:WorkBuddy与CodeBuddy实战指南

1. 办公智能体套件到底在解决什么问题1.1 从一个真实场景说起我所在的技术团队有二十多号人&#xff0c;日常协作里最让人头疼的不是写代码本身&#xff0c;而是那些“夹缝中的工作”——需求文档整理、会议纪要分发、代码评审提醒、周报汇总、跨部门信息同步。这些事情单件耗时…

作者头像 李华
网站建设 2026/9/23 13:48:54

游泳溺水检测实战:从YOLO数据清洗到NVR端部署

简介&#xff1a;本资源是面向计算机视觉初学者与算法工程师的溺水行为检测专用数据集&#xff0c;聚焦YOLO系列目标检测模型训练与验证&#xff0c;适用于游泳场馆智能监控、水域安全预警等实际场景。数据集共2000个文件&#xff0c;包含874张带标注的JPEG图像、874份YOLO格式…

作者头像 李华
网站建设 2026/9/23 13:48:11

OpenAI推出SWE-bench Verified?用TaoToken统一Key跑通评测配置

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

作者头像 李华