ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CursorWindow 报错排查:从 Failed to read row 0, column -1 到 TaoToken 配置校验

CursorWindow 报错排查:从 Failed to read row 0, column -1 到 TaoToken 配置校验 1. 先别急着改 SQLCursorWindow 报错到底在说什么Failed to read row 0, column -1 from a CursorWindow which has 0 rows, 0 columns这个报错几乎每个做 Android 数据层的同学都撞过。它的字面意思是你让 Cursor 去读第 0 行的第 -1 列而当前 CursorWindow 里既没有行也没有列。注意column -1这个细节——正常的列索引从 0 开始出现 -1 说明代码里拿到的列索引本身就是个非法值通常是getColumnIndex()或getColumnIndexOrThrow()没找到目标列名后返回了 -1然后你又直接把它喂给了getString()、getInt()这类读取方法。它适合谁看适合正在写 ContentProvider、Room 原生查询、SQLite 手写 Cursor 遍历或者用第三方 SDK 回调 Cursor 的 Android 开发者。能做什么帮你把「游标越界」这一类问题从「玄学报错」拆成可定位、可复现、可验证的排查路径。我试过在同一个项目里连续踩三次这个坑每次根因都不一样所以这篇不打算只给你一句「检查列名」而是把定位动作、日志验证、以及顺手用 TaoToken 统一 Key 通道做配置校验的骨架一起讲清楚。核心检索词先摆出来CursorWindow、column -1、getColumnIndex 返回 -1、moveToFirst 为 false、settings.json 配置校验。下面按「问题场景 → 前置准备 → 可复制配置 → 验证请求 → 错排查 → 工具入口」的顺序展开你可以直接跳到对应小节跟做。2. 场景还原column -1 是怎么被喂进 Cursor 的2.1 三个高频触发点第一个触发点是列名拼写和实际 schema 不一致。比如建表时写的是user_name查询投影里写SELECT name FROM ...getColumnIndex(user_name)自然返回 -1。第二个是rawQuery的 projection 参数传了空数组或 nullCursor 拿到的窗口列数为 0。第三个最隐蔽moveToFirst()返回 false结果集为空但代码没判断返回值就直接getString(columnIndex)此时窗口 0 行 0 列任何读取都炸。// 反面示例没有判断 moveToFirst也没有校验列索引 Cursor cursor db.rawQuery(SELECT id, user_name FROM users WHERE age ?, new String[]{18}); int nameIdx cursor.getColumnIndex(username); // 拼错返回 -1 if (cursor.moveToFirst()) { String name cursor.getString(nameIdx); // 一旦执行到这里就抛 CursorWindow 异常 }2.2 为什么报错信息里是 column -1 而不是别的因为getColumnIndex系列方法在找不到列时约定返回 -1而 Cursor 内部读取时会用这个索引去 CursorWindow 定位窗口校验发现索引非法且窗口为空就把column -1原样带进了异常消息。所以看到 -1第一反应应该是「列名没匹配上」而不是「数据库坏了」。2.3 定位思路先分清是空结果还是错列名一个快速区分方法在读取前打印cursor.getCount()和cursor.getColumnCount()。如果 count 为 0是结果集为空如果 count 大于 0 但 columnCount 为 0是 projection 有问题如果两者都正常但getColumnIndex返回 -1就是列名不匹配。这三条分支对应三种修法别混着改。3. TaoToken 前置把 Key 和通道收敛成一份 settings.json排查数据库问题本身不需要网络但很多团队会把「AI 辅助排查日志」「统一模型调用」接进开发流程这时候 Key 散落在各个 IDE 插件、脚本里就会很乱。TaoToken 在这里的角色是统一 Key / API 通道你申请一次 Key通过一个兼容接口去调用不同模型省得每个工具单独配一遍。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 这个不加 UTM。需要先说明TaoToken 是合规的 API 聚合通道不是什么灰色中转也不涉及任何网络访问工具。它的用途就是把模型调用凭证集中管理。下面这份settings.json骨架是我在 Android 项目里用来做「配置校验」的模板字段含义逐条注释。{ taotoken: { api_base: https://taotoken.net/api, api_key: sk-你的Key粘贴在这里, default_model: claude-sonnet, timeout_ms: 30000, retry: { max_attempts: 3, backoff_ms: 800 } }, cursor_debug: { log_sql: true, log_column_index: true, throw_on_empty_window: true } }api_base固定指向 API 域名api_key从控制台生成default_model按你实际用的模型填。cursor_debug这一段是我自己加的排查开关log_column_index打开后每次getColumnIndex的结果都会打到 Logcatthrow_on_empty_window让空窗口提前抛异常而不是静默返回。这样 CursorWindow 问题和配置问题能在同一份日志里对照看。Key 的生成入口在控制台的 API Keys 页面https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_campaignrewriteutm_contentapi_keys 。生成后别硬编码进仓库用local.properties或环境变量注入settings.json里只留占位符。4. 可复制配置Cursor 读取的安全封装 配置校验脚本4.1 安全读取封装从源头堵住 column -1与其每次手动判断不如封装一个工具方法把「列索引校验 空结果判断」一次性做掉。public final class SafeCursor { public static int requireColumn(Cursor c, String name) { int idx c.getColumnIndex(name); if (idx 0) { throw new IllegalStateException( 列名不存在: name , 实际列: Arrays.toString(c.getColumnNames())); } return idx; } public static String getStringOrNull(Cursor c, String name) { if (!c.moveToFirst()) return null; // 空结果直接返回 int idx requireColumn(c, name); // 列名不存在直接抛清晰异常 return c.isNull(idx) ? null : c.getString(idx); } }关键点requireColumn在列名不存在时抛出带「实际列名数组」的异常这样日志里能直接看到 schema 和查询投影的差异比原生column -1有用得多。getStringOrNull先moveToFirst再取列顺序不能反。4.2 配置校验脚本确认 settings.json 能被正确解析光有配置不够得验证它真的被读进来了。下面这段 Kotlin 脚本放在 debug 构建里跑一次检查 Key 和 base 是否就位。fun validateTaotokenConfig(json: JSONObject): Boolean { val cfg json.optJSONObject(taotoken) ?: return false.also { Log.e(TaoToken, 缺少 taotoken 节点) } val base cfg.optString(api_base) val key cfg.optString(api_key) if (!base.startsWith(https://taotoken.net/api)) { Log.e(TaoToken, api_base 异常: $base); return false } if (key.isBlank() || key sk-你的Key粘贴在这里) { Log.e(TaoToken, api_key 未替换占位符); return false } Log.i(TaoToken, 配置校验通过, model${cfg.optString(default_model)}) return true }跑通后 Logcat 会输出「配置校验通过」说明通道配置没问题接下来排查 Cursor 时就不会被「是不是 Key 没配好」这种无关变量干扰。4.3 参数对照表字段作用常见错误值正确示例api_base接口基址带 UTM 的完整 URLhttps://taotoken.net/apiapi_key调用凭证空串 / 占位符sk-开头的真实 Keydefault_model默认模型拼写错误claude-sonnetlog_column_index列索引日志falsetrue排查期5. 验证请求用日志确认列索引和配置都正确5.1 打开列索引日志后跑一次查询把log_column_index设为 true执行一次会触发 CursorWindow 的查询观察 Logcatadb logcat -s CursorDebug TaoToken | grep -E columnIndex|配置校验预期输出类似CursorDebug: querySELECT id, user_name FROM users, columns[id, user_name] CursorDebug: getColumnIndex(user_name)1 CursorDebug: moveToFirsttrue, count3, columnCount2 TaoToken: 配置校验通过, modelclaude-sonnet看到getColumnIndex(user_name)1且columnCount2说明列名匹配、窗口非空之前的column -1已经被消除。如果这里仍然打印getColumnIndex(username)-1那就是查询投影里根本没有username这一列回到 SQL 改投影即可。5.2 用模型对话做一次日志解读如果你把排查日志贴给模型让它帮忙分析可以走模型对话入口https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_campaignrewriteutm_contentmodels 。把上面那段 Logcat 输出粘进去问「columnCount 为 0 的可能原因」它会结合日志给出分支判断比人肉翻文档快。注意这是辅助手段最终结论仍以你本地的 schema 为准。5.3 成功结果的判定标准一次成功的验证要同时满足三条moveToFirst返回 true、getColumnIndex返回非负、getColumnCount大于 0。三条里任意一条不满足就回到第 2 节的三分支定位法。实测下来把这三条做成断言写进单元测试回归时能挡住大部分 CursorWindow 复发。6. 本篇常见错排查6.1 改了列名但没改 projection最常见的复发原因你在getColumnIndex里把列名改对了但rawQuery的 SELECT 语句还是旧的投影窗口里压根没这列。修法是让 projection 和读取列名用同一个常量数组别两处手写。6.2 moveToFirst 和 moveToNext 混用遍历时用while (cursor.moveToNext())单条读取用if (cursor.moveToFirst())。如果先moveToFirst再进while(moveToNext)第一条记录会被跳过如果只调moveToNext不调moveToFirst第一条永远读不到某些实现下窗口状态异常也会报 CursorWindow 错。6.3 Cursor 没关导致窗口复用异常Cursor用完必须close()推荐 try-with-resources。忘记关闭会让 CursorWindow 的内存被占住后续查询可能拿到脏窗口报错信息里行列为 0 的情况就包含这一种。try (Cursor c db.rawQuery(sql, args)) { if (c.moveToFirst()) { int idx SafeCursor.requireColumn(c, user_name); Log.i(CursorDebug, name c.getString(idx)); } }6.4 settings.json 占位符没替换配置校验脚本报「api_key 未替换占位符」时检查是不是把模板直接提交了。用local.properties注入CI 里用环境变量覆盖别把真实 Key 写进版本库。6.5 把 CursorWindow 报错误判成数据库损坏column -1和数据库文件损坏没有必然关系。先按列名、投影、空结果三条排查实在怀疑数据问题再单独做完整性检查别一上来就重建数据库。7. 工具入口与长期编码建议排查完这一轮如果你打算把「AI 辅助日志分析」长期接进 Android 开发流程建议用 Coding Plan 把模型调用额度固定下来入口是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_campaignrewriteutm_contentcoding_plan 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_campaignrewriteutm_contentdoc 里面有 settings.json 各字段的完整说明和不同语言的调用示例。如果你用的是 Claude Code 这类编码工具对应的配置参考 https://taotoken.net/claudecode?utm_sourcetaotoken_aicg_blog_endutm_campaignrewriteutm_contentclaudecode 。最后留一个我自己的习惯每次改完 Cursor 相关代码先跑一遍带log_column_index的 debug 构建确认日志里列索引全是非负再提交。这个动作花不到一分钟但能挡住绝大多数column -1回归。
返回列表