Spinner 的 SimpleCursorAdapter 缺 People._ID 报异常让走 TaoToken 的 Codex 对照字段映射Spinner 绑定 SimpleCursorAdapter 后一跑到 setAdapter 就崩日志里出现java.lang.IllegalArgumentException: column _id does not exist这是 AdapterView 绑定游标数据时很典型的一类字段映射错误。原文在讲用 AdapterView 填充布局时给了 Spinner 两条绑定路径一条是 R.array.colors 配 ArrayAdapter另一条是 managedQuery(People.CONTENT_URI, PROJECTION, null, null) 拿游标后配 SimpleCursorAdapter并且明确提醒字段映射里必须有 People._ID 列。这段代码不需要逐行硬啃把游标查询和 SimpleCursorAdapter 构造整块交给走 TaoToken 通道的 Codex让它比着 PROJECTION 逐项核对更省事。TaoToken 在这里只负责给 Codex 供 Key 和通道地址字段映射到底缺哪一列仍由 Codex 对着原文代码查。开始前先打开官网注册并创建一个 Keyhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentspinner_cursor_id 。1. Spinner 的 SimpleCursorAdapter 缺 People._ID 报 column _id does not exist先把现场还原清楚。AdapterView 是 ViewGroup 的子类子 View 由 Adapter 决定它有两个主要职责用数据填充布局、处理用户选择。Gallery、ListView、Spinner 都是它的子类。原文演示填充数据时同时绑了两个 Spinner第一个 Spinner 走 ArrayAdapter.createFromResource(this, R.array.colors, android.R.layout.simple_spinner_item)数据源是本地字符串数组不涉及游标不需要_id。第二个 Spinner 走 managedQuery(People.CONTENT_URI, PROJECTION, null, null) 查通信录再用 SimpleCursorAdapter 把游标里的列映射到布局中的 TextView。问题只会出在第二条路径上。SimpleCursorAdapter 继承自 CursorAdapter而 CursorAdapter 有一个硬性前提传入的 Cursor 必须包含名为_id的列。原因是 AdapterView 在展示每一项时要拿到一个稳定的 item idSpinner 的 onItemSelected 回调里第四个参数long id就来自这一列。People._ID 这个常量的值就是_id所以 PROJECTION 里少了它查询结果里就没有_id列框架在校验游标结构时直接抛异常。这里要区分三个数组很多人会混名称作用是否必须含_idPROJECTIONSQL 查询要返回哪些列必须含 People._IDfrom游标里哪些列参与展示映射只放要展示的列通常只有 People.NAMEto每个展示列对应的 View id与 from 长度、顺序一一对应原文提醒的「字段映射里必须有 People._ID 列」实际落在 PROJECTION 上而不是 from 数组上。如果你把 People._ID 塞进 from就必须在 to 里再补一个对应的 TextView id否则 from 和 to 长度不一致会换一种异常继续崩。这个区分就是后面交给 Codex 核对的第一条规则。2. TaoToken 接入前置给 Codex 拿 Key 与模型 IDCodex 要能读到你的代码并给出核对结论先得有一条可用的模型通道。这一步在 TaoToken 官网完成打开官网注册账号进入控制台创建一个 API Key先复制保存后面填进环境变量。到模型广场确认你要用的模型 ID配置里的model就填这个 ID不要自己拼写。记住通道地址是https://taotoken.net/api填进工具时末尾不加/v1也不要带任何 UTM 参数。UTM 只用于网页链接跳转不用于 API 基址。TaoToken 在这条链路里只承担 Key 与通道地址两件事。字段映射缺哪一列、drop-down 模板有没有设上、Cursor 生命周期怎么处理这些判断仍然由 Codex 基于你贴过去的代码完成。3. Codex config.toml 里填 Base URL 与 KeyCodex 使用~/.codex/config.toml作为配置文件下面这份可以直接改成你自己的值# ~/.codex/config.toml model MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api responsesMODEL_ID到官网模型广场取wire_api按 Codex 版本与所选模型的要求填写。Key 不写进配置文件用环境变量传入。Linux 或 macOSexport TAOTOKEN_API_KEYYOUR_API_KEYWindows PowerShell$env:TAOTOKEN_API_KEYYOUR_API_KEY设置完成后新开一个终端让环境变量生效。配置检查点只有两个base_url是不是https://taotoken.net/api以及这个环境变量名和 config.toml 里的env_key是否一致。4. 把 PROJECTION 与 SimpleCursorAdapter 构造整块交给 Codex 核对配置好通道后不要一句一句问 Codex「People._ID 是什么」。直接把原文里那一整块游标查询和 Adapter 构造贴过去让它按清单核对。待查代码如下注意 PROJECTION 已经被我改成缺_id的版本用来复现报错// 复现PROJECTION 里只有 NAME游标没有 _id 列 private static final String[] PROJECTION new String[] { People.NAME }; Spinner s2 (Spinner) findViewById(R.id.spinner2); Cursor cur managedQuery(People.CONTENT_URI, PROJECTION, null, null); SimpleCursorAdapter adapter2 new SimpleCursorAdapter( this, android.R.layout.simple_spinner_item, cur, new String[] { People.NAME }, new int[] { android.R.id.text1 }); adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); s2.setAdapter(adapter2);配套的提示词可以这样写下面是我按 Android AdapterView 示例写的 Spinner 绑定代码运行时抛 java.lang.IllegalArgumentException: column _id does not exist。 请只做字段映射核对不要重写业务逻辑按四条输出结论 1. 对照 PROJECTION 数组检查游标查询结果是否包含 _id 列 2. 检查 from 数组与 to 数组的长度、顺序是否一一对应 3. 检查 item 模板与 drop-down 模板是否分别使用了 simple_spinner_item 和 simple_spinner_dropdown_item 4. 指出 managedQuery 与 People 的弃用风险给出最小改动方案 不要引入第三方库。 代码 把 PROJECTION、managedQuery、SimpleCursorAdapter 构造、 setDropDownViewResource、setAdapter 整段贴在这里Codex 按这份提示词核对的顺序大致会给出这一类结论第一条命中_id缺失第二条确认 from 与 to 各一个元素、长度一致第三条确认两个模板都已设置第四条提示 managedQuery 在 API 11 之后被弃用、Contacts.People 也已弃用但当前报错与这两点无关属于后续迁移事项。按核对结果改回来的最小版本如下// 修复PROJECTION 保留 _id 供 CursorAdapter 使用 private static final String[] PROJECTION new String[] { People._ID, People.NAME }; Spinner s2 (Spinner) findViewById(R.id.spinner2); Cursor cur managedQuery(People.CONTENT_URI, PROJECTION, null, null); SimpleCursorAdapter adapter2 new SimpleCursorAdapter( this, android.R.layout.simple_spinner_item, cur, new String[] { People.NAME }, new int[] { android.R.id.text1 }); adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); s2.setAdapter(adapter2);改动只有一处PROJECTION 从只查 NAME 改成先查_ID再查 NAME。from 与 to 保持一对一People.NAME对应 item 模板里的android.R.id.text1。对照原文第一条 Spinner 绑定路径ArrayAdapter 那部分不需要做任何改动因为它的数据来自 R.array.colors与游标无关。5. Spinner 绑定 Cursor 时还会踩的坑与排查顺序字段映射只是第一层继续跑原文的 setAdapter 流程时下面这些点建议按顺序过一遍。第一column _id does not exist只认列名不认你写的是People._ID还是字面量_id只要最终查询结果里有这一列就行。反过来Cursor 里有_id但 from 数组没写它不会报这个错只是这一列不参与展示。第二from 与 to 长度不一致会抛IllegalArgumentException报错信息通常提示两个数组元素数量不同。这两个数组是从列到视图的一一映射多一个少一个都不行。第三item 模板里必须存在android:idandroid:id/text1。如果你用了自定义布局to 数组里就不能再写android.R.id.text1而要写自定义布局里的 TextView id写错会直接空指针或视图找不到。第四Spinner 不支持setOnItemClickListener。原文末尾演示的是 ListView 用 OnItemClickListener 处理选择Spinner 要用的是setOnItemSelectedListener。给 Spinner 调setOnItemClickListener会抛RuntimeException: setOnItemClickListener cannot be used with a Spinner这个错误和_id无关但经常和字段映射问题混在一起排查。第五drop-down 模板要和 item 模板分开设置。simple_spinner_item是收起时显示的布局simple_spinner_dropdown_item是展开列表的布局少了setDropDownViewResource这一步功能不崩但下拉样式会不对。第六数据变化后的通知方式不同。ArrayAdapter 走notifyDataSetChanged()CursorAdapter 更推荐swapCursor(newCursor)或changeCursor()让 Adapter 重新接管游标单纯 notifyDataSetChanged 不一定能反映游标内容变化。第七managedQuery与People在较新的 API 上都已弃用。前者应换成getContentResolver().query(...)并配合后台线程或 CursorLoader后者应迁移到 ContactsContract。当前这一步先不扩大改动范围等_id问题解决、绑定跑通之后再迁移避免一次引入多个变量。6. 一条最简请求验证 Codex 已走通 TaoToken 通道代码改完后先确认 Codex 请求确实走通了再去跑 Spinner。用一条不涉及业务代码的最小请求即可codex 只回复 pong不要解释如果返回内容正常说明 Key、base_url、模型 ID 这条链路已经通了。此时再回到 Android 工程把修复后的 PROJECTION 与 SimpleCursorAdapter 放回原文的 setAdapter 流程里运行观察 Spinner 是否正常展开、选择某一项时onItemSelected回调里的 id 是否非负。若此时仍有异常把新的日志整段贴回 Codex让它继续沿字段映射这条线核对。7. 验证通过后回到 setAdapter 流程顺序上建议保持这样先修 PROJECTION再确认 from 与 to 一一对应再看两个 item 模板最后回到 Spinner 的 setAdapter 调用。这个顺序的好处是每一步只改变一个变量报错信息能直接指回刚改过的那一行。原文字段映射代码不需要额外逐行背诵把游标查询与 Adapter 构造整块留给 Codex 对照 PROJECTION 检查人只负责按结论改代码、跑一次验证、看一次日志。8. 继续排查下一处绑定问题时从哪里管理 Key 和看接入文档这次是在 Codex 里核对完字段映射、并用一条最简请求确认通道可用之后才回到 Spinner 的 setAdapter 流程。接下来如果要在别的工程里复用同一个 Key或者把 Claude Code 也接到同一个 Base URL 上去控制台管理 Key 即可https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentspinner_cursor_id 。Claude Code 的settings.json与ANTHROPIC_*环境变量写法、以及https://taotoken.net/api这个基址怎么填按接入文档逐步操作https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentspinner_cursor_id 。