ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Codex 配 TaoToken:解析 mmssms.db 短信读取的 ContentResolver 查询

2026/9/15 2:16:00 拓冰建站 浏览量
Codex 配 TaoToken:解析 mmssms.db 短信读取的 ContentResolver 查询 把 /data/data/com.android.providers.telephony/databases/mmssms.db 导出后用 SQLite DataBase Browser 打开你会看到二十来张表但真正影响 ContentResolver 查询结果的只有 Canonical_addresses、Threads、Sms 三张。很多人照着教程写 query拿到的 Cursor 里没有 address或者 message_count 永远为 0问题几乎都出在这三张表的关联理解上。如果你也卡在这一步可以先去 TaoToken 创建一把 API Key把 Codex 的模型通道切到 TaoToken 的统一接入地址然后让 Codex 基于 mmssms.db 的表结构和你的原始代码片段一起定位问题。TaoToken 只负责提供模型通道真正改写查询逻辑的是 Codex所以在动手前先把手上的代码和报错整理好。1. 短信数据库三张表没对上ContentResolver 查询就先废一半1.1 Canonical_addresses、Threads、Sms 到底谁关联谁短信数据库 mmssms.db 里除了这三张还有 pending_msg、attachments、part 等表但会话列表和短信详情根本用不到它们。用 SQLite DataBase Browser 打开后你会发现每个表都有各自的主键和外键关联。Canonical_addresses 可以理解成“地址薄映射表”它把一串电话号码映射成一个_id。Threads 表则是按联系人分组的会话列表相当于你在短信应用里看到“和某个人把所有往来短信摞在一起”的那个列表项。Sms 表则存放每一条具体的短信记录包括发件人/收件人地址、短信正文 body、时间 date、类型 type收/发。三张表的关联方式如下Threads 表里的recipient_ids指向 Canonical_addresses 表的_idCanonical_addresses 表里的address字段也就是你在 Sms 表里看到的address字段Threads 表里的_id是会话线程 IDSms 表里每条短信都有thread_id指向它。所以你要拿到“某个会话一共多少条消息、最近一条消息内容是什么、对方电话号码是多少”就得先查 Threads或直接查mms-sms/conversations这个联合视图拿到_id和message_count再用_id去 Sms 表里查 address 和 body。这里有个容易混淆的点Sms 表本身就有address列所以查单条短信时可以直接用content://sms拿到电话号码不需要回 Canonical_addresses 去 join。Canonical_addresses 的价值主要是把 Threads 的recipient_ids翻译成可读的电话号码。当你用content://mms-sms/conversations查会话列表时系统已经在内部把 Threads 和相关的 address 信息合并好了你只需要在 projection 里写出想要的列名。1.2 ContentResolver 查询常见翻车点原文代码里第一个 query 用了Uri.parse(content://mms-sms/conversations)projection 写的是new String[]{* from threads--}。在 ContentResolver 里projection 数组里的每一项会被拼进 SQL 查询的 SELECT 子句写成* from threads--意味着你并不是在取具体列而是想通过 SQL 注入的写法把整张表带出来。这种写法在早期 Android 版本里可能侥幸返回数据但它很容易让getColumnIndex(message_count)或getColumnIndex(snippet)拿不到列索引进而返回 -1最终 Cursor 里读出来的是 null 或直接抛异常。另一个常见翻车点在查询电话号码时用了Uri.parse(content://sms/)却只在 projection 里放了address一个列。这本身没问题可如果 cursor 的列名和实际表结构对不上getColumnIndex(address)就会返回 -1。大多数时候这是因为没弄清 address 是在 Canonical_addresses 表里还是可以直接在 Sms 表里取。实际上content://sms这个 Uri 返回的列是包含 address 的但需要你在 projection 里明确写出来并且确认当前 App 已申请 READ_SMS 权限Android 6.0 以上还要动态申请。否则查询不会报 SQL 错误但会抛SecurityException或拿不到数据。2. 先把 TaoToken 的 Key 和 Base URL 准备好2.1 去 TaoToken 创建 API Key准备材料很简单打开 TaoToken注册并登录进入控制台的 API Keys 页面创建一把 Key。创建之后把 Key 复制下来后面在 Codex 的配置里要用到代码和配置里统一写成YOUR_API_KEY。TaoToken 的模型广场上会列出当前可用的模型 IDCodex 配置里的model字段不要自己猜以模型广场当时列表为准。如果你之前配过其他模型平台注意这里有一个关键区分浏览器里访问的https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end是官网落地页用来注册、创建 Key、看模型广场和看用量而 Codex 的 config.toml 里填的是接口地址https://taotoken.net/api末尾不要加/v1。这两个地址用途不同不能混填尤其不要把带 UTM 参数的链接写进配置文件。2.2 Codex 的 config.toml 指向 TaoTokenCodex 的配置文件在~/.codex/config.toml。打开这个文件把 model provider 指向 TaoTokenmodel 你的模型ID # 以 TaoToken 模型广场为准 model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api wire_api chat env_key CODEX_API_KEY配置好之后在 shell 里导出环境变量export CODEX_API_KEYYOUR_API_KEY然后重新启动 Codex让配置生效。如果你是在 Windows 上开发环境变量用set CODEX_API_KEYYOUR_API_KEY。设置完可以用echo $CODEX_API_KEYWindows 是echo %CODEX_API_KEY%确认值已经写进去。3. 让 Codex 基于 mmssms.db 表结构改写查询3.1 把原始代码和报错喂给 Codex现在把原文里那段 AsyncTask 代码、你正在排查的报错信息以及三张表的关联结构一起贴给 Codex。比如你可以这样说这是 Android 里用 ContentResolver 查短信数据库的代码目标是从 threads 表拿会话列表日期、消息数量、部分消息内容再从 sms 表拿对应电话号码。现在message_count查出来是 0getColumnIndex(address)返回 -1请基于 Canonical_addresses、Threads、Sms 三张表的关系帮我重写查询。Codex 看到表结构后会先帮你确认列名Threads 表里有_id、date、message_count、snippet、recipient_idsSms 表里有thread_id、address、body、type、date。你不需要自己去猜列名直接让它对照 mmssms.db 的实际 schema 给你列出来。如果报错信息里有java.lang.IllegalStateException: Couldnt read row 0, col -1 from CursorWindowCodex 会立刻指出这是getColumnIndex返回 -1 导致的进一步引导你检查 projection 里是否漏了对应列名。3.2 改写后的会话列表查询下面是一段重写后的会话列表查询你可以直接放进 AsyncTask 的 doInBackground 里先在本地跑通再把结果贴回对话继续优化ContentResolver resolver getContentResolver(); Cursor conversations resolver.query( Uri.parse(content://mms-sms/conversations), new String[]{_id, date, message_count, snippet}, null, null, date desc); while (conversations.moveToNext()) { String threadId conversations.getString(conversations.getColumnIndex(_id)); String date conversations.getString(conversations.getColumnIndex(date)); String count conversations.getString(conversations.getColumnIndex(message_count)); String snippet conversations.getString(conversations.getColumnIndex(snippet)); Cursor phones resolver.query( Uri.parse(content://sms), new String[]{address}, thread_id?, new String[]{threadId}, null); if (phones.moveToNext()) { String address phones.getString(phones.getColumnIndex(address)); // 这里把 address 填进你的 MyMessageList } phones.close(); } conversations.close();这段代码里content://mms-sms/conversations已经把 threads 表和 message_count、snippet 关联好了不需要再写* from threads--。取电话号码时用thread_id?作为 selection从content://sms里查 address比从 Canonical_addresses 去 join 要直白得多。需要注意的是这段查询必须运行在 Android 应用的进程里并且要在主线程之外的线程执行AsyncTask 正好满足这个要求。不要把短信数据库直接导到电脑上让 Codex 连它只能帮你生成和解释代码真正执行还是在你的手机上。3.3 改写后的单联系人短信查询原文后面那段“获取和某个人通话的短信”同样可以简化。你只需要维护一个threadidString然后查询content://sms按thread_id过滤按date desc排序ContentResolver resolver getContentResolver(); Cursor cursor resolver.query( Uri.parse(content://sms), new String[]{date, body, type}, thread_id?, new String[]{threadidString}, date desc); if (cursor.moveToFirst()) { do { String body cursor.getString(cursor.getColumnIndex(body)); long date cursor.getLong(cursor.getColumnIndex(date)); int type cursor.getInt(cursor.getColumnIndex(type)); // type 1 收到type 2 发出 } while (cursor.moveToNext()); } cursor.close();注意这段查询只是从content://sms读取不会直接连接你的 Android 设备也不需要在电脑上跑任何服务。Codex 帮你生成这段代码后你要在自己的 Android 工程里编译运行然后把 Logcat 里的输出或报错贴回对话它才能继续帮你调。4. 验证调用并解决几个绕不开的报错4.1 如何在 Codex 里确认这次请求已打到 TaoToken配置完成后随便让 Codex 写一段“把上面三张表的关系画成 SQLite 查询”的代码如果它正常响应说明模型调用已经走通。你也可以在 Codex 对话里问一句“你现在用的 base_url 是什么”它通常能根据系统提示回答。更可靠的方式是登录 TaoToken 控制台看用量记录里有没有刚产生的调用。注意用量记录有几分钟延迟刚发完消息没立刻看到是正常的。4.2 报错对照401、404、模型 ID 不正确如果你在 Codex 里收到 401先检查CODEX_API_KEY环境变量是否真的设置了echo $CODEX_API_KEY看一眼有没有把YOUR_API_KEY原样留在里面。收到 404最常见的是模型 ID 和 Base URL 的拼接问题Base URL 末尾多了/v1或者 model 字段写了一个模型广场上不存在的 ID。TaoToken 的模型列表以 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 模型广场当时列表为准不要凭记忆填日期后缀。如果你之前用过别的平台顺手把config.toml里的wire_api改了也会导致 404保持chat即可。如果你不确定 Base URL 有没有写错可以打开 config.toml 检查base_urlhttps://taotoken.net/api是不是末尾没有斜杠和版本号。Codex 会在请求时自动拼上对应的路径所以这里多一个/v1反而会让最终请求地址变成https://taotoken.net/api/v1/v1自然拿不到模型响应。5. 跑通之后去控制台对一下这次调用5.1 在模型对话里再试一条消息确认 Key 与模型 ID 都正常Codex 配好后建议先在 模型对话 里用同一把 Key 发一条“请解释 Canonical_addresses 和 Threads 的关系”的测试消息。这样能快速区分问题出在 Codex 配置还是在 TaoToken 的 Key/模型上。确认没问题后再回 Codex 继续处理短信查询避免把两边的报错混在一起。如果模型对话里能正常返回但 Codex 还是 401那就是环境变量没加载重启 Codex 就好。5.2 把单联系人短信查询也交给 Codex继续对照原文收尾会话列表查询跑通后你还需要处理原文里“从 sms 中获取具体的和某个人通话的短信”那部分。把 3.3 的代码贴给 Codex同时告诉它你的threadidString是从哪个 Activity 传过来的、ListView 的 Adapter 是哪个它会帮你把 AsyncTask 的泛型、onPostExecute 里的 adapter 通知都补完整。这轮调整完成后去 Coding Plan 看下你的套餐用量是否够用Key 也可以直接在 控制台 API Keys 页面随时重新创建或吊销。整个流程里TaoToken 只负责把 Codex 的请求转成可用的大模型响应真正读懂 mmssms.db 表结构、帮你修正 ContentResolver 查询逻辑的还是 Codex 和你一起完成的那几轮对话。