Android Cursor moveToFirst 判空TaoToken 这样配 Codex 排查 SQLite 游标用 Codex 排查 Android SQLite 游标问题时TaoToken官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 提供 Key 和 Base URL 通道。这篇文章从 Cursor 取数为空的现场切入Cursor 明明有数据moveToFirst() 返回 false 就直接 return或者 while(cur.moveToNext()) 第一次判断就不进循环最后查出来的 People.NAME、People.NUMBER 全是空。排查这类问题时把 Codex 的通道配好再把判空、循环取数、isAfterLast 这几段代码贴进去让它对照 moveToFirst、moveToNext、getColumnIndex、close() 检查游标是否关闭、列名是否存在、循环边界是否正确。TaoToken 只负责 Key 和 Base URL 这段通道真正执行 SQLiteDatabase.query()、移动游标、读取列值的仍然是本地 Android 项目验证也要在本地跑一遍才算数。一、Android Cursor 取数为空的现场moveToFirst() 返回 false 之后发生了什么在 Android 里用 SQLiteDatabase.query() 查表拿到 Cursor 之后很多取数异常不是 SQL 语法写错而是游标状态和列索引处理错了。典型表现有三种列表页明明应该有几条 People 记录却一条都不显示日志里看到 moveToFirst() 返回 false代码直接 return或者循环只取到第二行开始的数据第一行被跳过再或者 getColumnIndex(People.NAME) 返回 -1getString(-1) 直接抛异常。这些现象看起来像数据库没写入实际上数据已经在表里问题出在 Cursor 的定位、索引和关闭时机上。Cursor 可以理解为查询结果的行集合同时带一个指向当前位置的指针。query() 返回时指针并不一定停在第一行所以要先 moveToFirst()。如果返回 false说明结果集为空但也要注意列名写错、投影字段不存在同样可能让后续取值失败不能把所有 false 都当成“表里没数据”。同样while(cur.moveToNext()) 的语义是“移动到下一行”如果一开始指针在第一行之前第一次 moveToNext() 会到第一行但如果之前已经调用过 moveToFirst()再进 while(moveToNext())第一行就会被跳过。这个边界问题在分页、详情页、列表页混用时特别容易踩。还有一个容易被忽略的点是 Cursor 的资源属性。它背后通常会占用 CursorWindow查询结果集较大时窗口会分页加载。如果不 close()Activity 反复创建销毁后资源会累积如果在 close() 之后继续 getString()又会遇到非法状态异常。所以排查 Cursor 问题时不能只看“有没有取到值”还要看“取完有没有关、关完有没有再用”。二、TaoToken 前置注册、创建 Key 与 Base URL 的边界本文用 Codex 做静态排查TaoToken 提供的是 Key 和 Base URL 通道。先打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册账号进入控制台创建 API Key。Key 只在创建时完整显示一次复制后放到本地环境变量里不要写进 Git 仓库的 config.toml 明文里。然后明确边界TaoToken 只负责把 Codex 的请求送到模型侧不代替 Android 设备执行 SQLite 查询也不代替 Cursor 去 moveToFirst 或 close。排查逻辑、代码修改、真机运行仍然在本地完成。Codex 在这里的角色是“读代码、对照 API 语义、指出边界错误”最终验证必须在 Android 项目里跑。Base URL 要填 https://taotoken.net/api不带 /v1也不带任何 UTM 参数。这点在 Codex 的 config.toml 里尤其要注意有些配置模板会默认把 /v1 拼在 base_url 后面如果重复写入请求路径会变成 /api/v1/v1/...表现就是 404 或模型列表拉不到。Key 使用刚生成的 TaoToken Key对应环境变量可以命名为 TAOTOKEN_API_KEY避免和系统里已有的 OPENAI_API_KEY 混淆。创建 Key 和查看接入参数的入口API Keyshttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi-keys接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc三、Codex 可复制配置config.toml 接上 TaoToken 通道Codex 的配置文件默认在 ~/.codex/config.tomlWindows 下对应 %USERPROFILE%.codex\config.toml。下面是一份最小配置示例model 字段用 MODEL_ID 占位具体可用的模型 ID 以 TaoToken 接入文档和控制台为准。model_provider taotoken model MODEL_ID [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY然后在终端里导出 Keyexport TAOTOKEN_API_KEYYOUR_API_KEYWindows PowerShell$env:TAOTOKEN_API_KEYYOUR_API_KEY配置完成后进入 Android 项目目录启动 Codex。注意不要把 base_url 写成 https://taotoken.net/api/v1也不要在后面加 ?utm_source... 这类查询串。配置文件里只保留通道本身。如果团队里多人共用一台构建机建议把 Key 放到系统环境变量或密钥管理工具而不是提交到仓库。config.toml 可以进版本库Key 不行。这是一个很基础的边界但在排查 Cursor 问题时经常被忽略最后变成了“Codex 能不能连上”和“SQLite 为什么取不到数”两件事混在一起。四、验证请求与成功结果把 People.NAME / People.NUMBER 取出来先验证通道不必一上来就跑完整对话。用 curl 请求模型列表curl -s https://taotoken.net/api/v1/models \ -H Authorization: Bearer YOUR_API_KEY如果返回 JSON 列表说明 Key、Base URL、网络链路都通。如果 401检查 Key 是否复制完整、是否被空格污染如果 404检查 base_url 是否写成了别的路径。通道确认后再回到 Codex 做代码排查。把下面这段 Android 查询逻辑贴给 Codex让它对照 Cursor 的 API 语义逐条检查Cursor cur db.query(People.TABLE, null, null, null, null, null, null); if (cur.moveToFirst() false) { return; } int nameColumnIndex cur.getColumnIndex(People.NAME); String name cur.getString(nameColumnIndex); while (cur.moveToNext()) { int phoneColumn cur.getColumnIndex(People.NUMBER); String phone cur.getString(phoneColumn); }提示词可以这样写这是一段 Android SQLite 查询代码。请只做静态排查不要重写业务逻辑。 1. 检查 moveToFirst() 返回 false 时空表、列名不存在、投影为空三种情况是否被区分 2. 检查 while(cur.moveToNext()) 是否会漏掉第一行 3. 检查 getColumnIndex(People.NAME) 和 getColumnIndex(People.NUMBER) 是否可能返回 -1 4. 检查 for(cur.moveToFirst(); !cur.isAfterLast(); cur.moveToNext()) 的循环边界 5. 检查 close() 是否在所有 return 分支、异常分支都被调用。Codex 通常会指出几个点moveToFirst() 只负责定位不等于结果一定非空getColumnIndex 返回值必须判 -1如果前面已经 moveToFirst()后面的 while(moveToNext()) 会从第二行开始Cursor 用完必须 close()否则 CursorWindow 资源会累积。把这些意见落到本地项目里改完再跑一次真机或模拟器。成功结果的判断标准很直接日志里能打印出 People.NAME 和 People.NUMBER列表长度和数据库里的行数一致没有 SQLiteException、没有 CursorIndexOutOfBoundsException退出页面后 Cursor 已关闭。不要只看“Codex 说没问题”要看 Android 项目实际跑出来的行数。五、本篇常见错排查getColumnIndex-1、循环边界与 close() 遗漏排障时按下面顺序过一遍基本能覆盖大部分 Cursor 取数问题。第一moveToFirst() 返回 false 就 return。这个写法本身不算错但它把“结果集为空”和“查询条件写错”混为一谈。更稳妥的做法是先判断 getCount()或者在列索引阶段单独处理 -1让日志能区分是没查到数据还是列名对不上。第二while(cur.moveToNext()) 跳过第一行。如果前面没有 moveToFirst()第一次 moveToNext() 会到第一行如果前面已经 moveToFirst()再进 while(moveToNext()) 就从第二行开始。想用 for 循环可以写成 for(cur.moveToFirst(); !cur.isAfterLast(); cur.moveToNext())但要注意 moveToFirst() 返回 false 时 isAfterLast() 的行为以及循环内不要再手动改变游标位置。第三getColumnIndex 返回 -1 后直接 getString。列名大小写、表别名、投影字段都会影响索引查找。循环内重复调用 getColumnIndex 也会带来不必要的开销建议在循环外把 nameColumn、phoneColumn 先取出来并判 -1。如果确定列一定存在也可以用 getColumnIndexOrThrow让异常更早暴露。第四忘记 close()。Cursor 是资源对象Activity 销毁、Fragment 视图销毁、异常分支 return 时都要关闭。可以用 try/finally 包住或者在 finally 里判断 isClosed() 再 close。第五在 Cursor 关闭后继续取值。close() 之后 getString、moveToNext 都可能抛异常排查时可以在关键位置打印 isClosed()。第六SQL 语句本身的问题被误判为 Cursor 问题。比如表名 People.TABLE 拼错、selectionArgs 数量不匹配、投影列名不在表里。这类问题会让 query() 抛异常或返回空集和游标边界问题表现相似需要分开看。第七把 TaoToken 通道问题和 Android 运行问题混在一起。Codex 连不上是 Key、Base URL、config.toml 的问题Cursor 取不到数是 SQL、列索引、循环边界的问题。先验证通道再排查代码顺序不要反。六、把排查链路固定下来API Keys 与接入文档这篇的排查链路可以固化成三步先用 TaoToken 的 Key 和 Base URL 把 Codex 通道配通再把 Cursor 判空、循环取数、isAfterLast 这几段代码交给 Codex 做静态检查最后回到本地 Android 项目跑一遍确认 People.NAME 和 People.NUMBER 能正常取出、Cursor 能正常关闭。需要创建 Key、查看 Base URL 和接入参数从这里进API Keyshttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi-keys接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdocCodex 的 config.toml 只需要保留 base_url https://taotoken.net/api不要追加 /v1不要带 UTM。Key 用控制台生成的 TaoToken Key环境变量建议单独命名。TaoToken 在这里只提供 Key 和 Base URL 通道真正执行 SQLiteDatabase.query()、moveToFirst()、moveToNext()、getColumnIndex() 和 close() 的仍然是你的 Android 项目。后续如果再遇到 Cursor 取数异常可以按“通道是否通、列索引是否 -1、循环边界是否正确、close() 是否执行”四步走。把这篇里的 config.toml 和提示词模板留在项目笔记里下次排查可以直接复用。