news 2026/10/18 10:01:44

安卓 rawQuery 查询结果绑定 ListView:Cursor 到 Adapter 的完整实现与 TaoToken 配置验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
安卓 rawQuery 查询结果绑定 ListView:Cursor 到 Adapter 的完整实现与 TaoToken 配置验证

1. 安卓 rawQuery 查询结果绑定 ListView 的完整场景拆解

db.rawQuery()是 Android 里最直接的 SQL 查询入口,它返回一个Cursor,你可以把它理解成一张“游标卡在结果集上的临时表”。问题在于,Cursor本身不会自己显示到界面上,ListView 也不认识 Cursor,中间必须有一个 Adapter 做翻译。很多新手卡住的地方不是 SQL 写错,而是 Cursor 拿到了、ListView 也写好了,但列表就是空白,或者一滑动就崩。

这篇内容适合三类人:刚学 Android 数据库、用 SQLite 做本地缓存、需要在列表里展示查询结果的开发者。核心检索词就是“安卓 rawQuery 查询结果绑定 ListView”,我会把SimpleCursorAdapter和自定义CursorAdapter两种写法都跑一遍,再把列名映射、_id主键、Cursor 生命周期关闭这些坑讲透。最后补一段用 TaoToken 统一 Key/API 通道做接口调试的配置验证,方便你在本地数据库之外,把网络请求也纳入同一套调试流程。

先说结论:rawQuery返回的 Cursor 要绑定到 ListView,必须满足三个条件——Cursor 里有名为_id的列、Adapter 的列名数组和布局控件 ID 数组一一对应、Cursor 在 Activity 销毁时正确关闭。少一个,轻则空白,重则IllegalArgumentException: column '_id' does not exist。

我试过最典型的翻车场景:SQL 写的是SELECT name, age FROM user,SimpleCursorAdapter 构造时直接传这个 Cursor,结果一运行就崩。原因就是 SimpleCursorAdapter 内部依赖_id做稳定 ID,查询里没带它。改成SELECT _id, name, age FROM user立刻正常。这个细节在官方文档里一笔带过,但实际开发中命中率极高。

下面从建库建表开始,一步步把可复制的代码给全。

2. TaoToken 前置配置:统一 Key 与 API 通道

在写 Adapter 之前,先把调试通道搭好。TaoToken 的作用是把模型调用、接口调试的 Key 和 Base URL 统一到一处,避免你在多个平台之间来回切换配置。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api ,注意 API 地址不带 UTM 参数。

你需要先拿到 API Key。进入控制台创建密钥,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,在 API Keys 页面生成一个 Key,形如sk-xxxx。这个 Key 后面会同时用于模型对话和 Coding Plan 场景。

如果你用的是 Claude Code 这类命令行编码工具,配置方式是在项目根目录或用户目录下写 settings 文件。以 Claude Code 的 Anthropic 兼容配置为例,Base URL 填https://taotoken.net/api,Key 填你刚生成的,Model ID 按你实际要用的模型填。三件套缺一不可:Base URL、Key、Model ID。很多人只填了 Key 就报 401,就是因为 Base URL 还指向默认地址。

对于 Android 项目里的接口调试,你可以在local.properties或BuildConfig里注入这个 Key,但注意不要提交到 Git。更稳妥的做法是用环境变量或 Gradle 的gradle.properties本地覆盖。下面给一段可复制的settings.json片段,路径按你的工具实际位置放:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的实际Key", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" } }

如果你用的是 Codex 的auth.json,结构类似,把 Base URL 和 Key 填进对应字段即可。Cline MCP 场景下,则是在 MCP 配置里指定 command 和 env,把ANTHROPIC_BASE_URL指向 TaoToken 的 API 地址。这三种配置的共同点就是三件套必须齐全,少一个都会在请求阶段报错。

配置完成后,你可以先用模型对话页面验证 Key 是否生效,地址是 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite ,发一条简单消息,能正常返回就说明通道通了。这一步做完,再回到 Android 的 Cursor 绑定,心里就有底了。

3. 可复制配置:SimpleCursorAdapter 与自定义 CursorAdapter 两种写法

先建库建表。假设我们有一个user.db,表结构如下:

CREATE TABLE user ( _id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER NOT NULL );

插入几条测试数据:

SQLiteDatabase db = helper.getWritableDatabase(); db.execSQL("INSERT INTO user (name, age) VALUES ('张三', 28)"); db.execSQL("INSERT INTO user (name, age) VALUES ('李四', 32)"); db.execSQL("INSERT INTO user (name, age) VALUES ('王五', 25)");

3.1 SimpleCursorAdapter 写法

布局文件item_user.xml:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="12dp"> <TextView android:id="@+id/tv_name" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textSize="16sp" /> <TextView android:id="@+id/tv_age" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" /> </LinearLayout>

Activity 里的绑定代码:

public class MainActivity extends AppCompatActivity { private SQLiteDatabase db; private Cursor cursor; private SimpleCursorAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DBHelper helper = new DBHelper(this); db = helper.getWritableDatabase(); cursor = db.rawQuery("SELECT _id, name, age FROM user", null); ListView listView = findViewById(R.id.list_view); adapter = new SimpleCursorAdapter( this, R.layout.item_user, cursor, new String[]{"name", "age"}, new int[]{R.id.tv_name, R.id.tv_age}, 0 ); listView.setAdapter(adapter); } @Override protected void onDestroy() { super.onDestroy(); if (cursor != null && !cursor.isClosed()) { cursor.close(); } if (db != null && db.isOpen()) { db.close(); } } }

关键点:new String[]{"name", "age"}是 Cursor 里的列名,new int[]{R.id.tv_name, R.id.tv_age}是布局里的控件 ID,两者顺序必须严格对应。_id列虽然不显示,但必须查出来,否则 SimpleCursorAdapter 会抛异常。

3.2 自定义 CursorAdapter 写法

当列表项需要更复杂的逻辑,比如根据年龄显示不同颜色,就继承 CursorAdapter:

public class UserCursorAdapter extends CursorAdapter { public UserCursorAdapter(Context context, Cursor c, int flags) { super(context, c, flags); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return LayoutInflater.from(context) .inflate(R.layout.item_user, parent, false); } @Override public void bindView(View view, Context context, Cursor cursor) { TextView tvName = view.findViewById(R.id.tv_name); TextView tvAge = view.findViewById(R.id.tv_age); String name = cursor.getString(cursor.getColumnIndexOrThrow("name")); int age = cursor.getInt(cursor.getColumnIndexOrThrow("age")); tvName.setText(name); tvAge.setText(String.valueOf(age)); if (age > 30) { tvAge.setTextColor(Color.RED); } else { tvAge.setTextColor(Color.BLACK); } } }

绑定到 ListView:

cursor = db.rawQuery("SELECT _id, name, age FROM user", null); UserCursorAdapter adapter = new UserCursorAdapter(this, cursor, 0); listView.setAdapter(adapter);

getColumnIndexOrThrow比getColumnIndex更安全,列名写错会立刻抛异常,而不是返回 -1 导致后续取值错乱。newView只负责创建视图,bindView负责填数据,CursorAdapter 会自动复用视图,不要在这两个方法里做耗时操作。

3.3 列名映射对照表

Cursor 列名布局控件 ID类型说明
_id不映射INTEGER必须存在,Adapter 内部使用
nameR.id.tv_nameTEXT对应第一个 TextView
ageR.id.tv_ageINTEGER对应第二个 TextView

如果查询里用了别名,比如SELECT name AS user_name,那new String[]里就要写user_name,不是name。这个映射关系是字符串匹配,大小写敏感。

4. 验证请求与成功结果:从 Cursor 到列表渲染

代码写完后,运行验证分三步。

第一步,确认 Cursor 有数据。在rawQuery之后加一行日志:

Log.d("CursorTest", "count = " + cursor.getCount());

如果输出count = 0,说明查询没查到数据,先检查插入语句是否执行、表名是否正确。如果输出count = 3,说明 Cursor 正常。

第二步,确认 Adapter 绑定成功。在setAdapter之后加:

Log.d("CursorTest", "adapter count = " + adapter.getCount());

adapter.getCount()应该等于 Cursor 的行数。如果不等,检查new String[]里的列名是否都在 Cursor 中存在。

第三步,观察界面。正常情况下,ListView 会显示三行,分别是张三 28、李四 32、王五 25,其中李四和王五的年龄文字是红色(因为年龄大于 30 的只有李四,王五 25 应该是黑色,这里按代码逻辑 age > 30 才变红,所以只有李四变红)。

如果你在调试网络接口时用了 TaoToken,可以在模型对话页面发一条请求,确认返回结构。比如让模型返回一段 JSON,然后你在 Android 里用同样的 Key 和 Base URL 去请求,对比两边结果是否一致。这一步能帮你排除是 Key 配置问题还是代码解析问题。

成功结果的特征:列表能正常滚动、没有CursorWindow相关警告、退出 Activity 后 Logcat 没有Cursor finalized without prior close()的提示。最后这条特别重要,它意味着你的 Cursor 泄漏了。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

5.1 报错column '_id' does not exist

这是 SimpleCursorAdapter 最经典的报错。原因就是rawQuery的 SELECT 语句里没带_id。解决方式:把 SQL 改成SELECT _id, name, age FROM user。注意_id必须是下划线开头,不能写成id。

5.2 报错java.lang.IllegalStateException: Couldn't read row 0, col -1 from CursorWindow

这个通常是getColumnIndex返回 -1 后继续取值导致的。改用getColumnIndexOrThrow,让错误在列名阶段就暴露。同时检查new String[]里的列名和 SQL 里的别名是否一致。

5.3 报错401 Unauthorized或local proxy failed

如果你在 Android 里请求 TaoToken 的 API 时遇到 401,先检查三件套:Base URL 是否为https://taotoken.net/api、Key 是否以sk-开头且没有多余空格、Model ID 是否拼写正确。local proxy failed一般是本地代理配置冲突,检查settings.json或auth.json里是否有多余的 proxy 字段,删掉即可。

5.4 报错reading choices或 OAuth 相关错误

reading choices通常出现在解析模型返回的 JSON 时,说明返回结构和你预期的字段不一致。先用模型对话页面确认返回格式,再对照调整解析代码。OAuth 错误则多出现在 Claude Code 或 Codex 的登录态配置里,检查auth.json是否过期,重新生成 Key 后覆盖。

5.5 Cursor 泄漏:Cursor finalized without prior close()

这个不是崩溃,但会在 Logcat 里刷警告。解决方式就是在onDestroy里关闭 Cursor,或者用try-finally包裹查询逻辑。如果你用了startManagingCursor(已废弃),换成手动关闭。

5.6 列表不刷新

rawQuery返回的 Cursor 是一次性快照,数据库变了它不会自动更新。需要重新查询并调用adapter.changeCursor(newCursor),然后关闭旧 Cursor。SimpleCursorAdapter 和 CursorAdapter 都支持changeCursor方法。

6. 语义一致 CTA:把调试通道固定下来

Cursor 绑定 ListView 这件事,核心就是三件事:SQL 带_id、列名和控件 ID 对齐、Cursor 记得关。把这三件事做成模板,以后每次写列表都能直接套。

接口调试部分,建议你把 TaoToken 的配置固定到项目模板里。API Key 管理入口在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,需要长期做编码和 Agent 任务的可以看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。Claude Code 的 Anthropic 配置入口是 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude-code-anthropic&utm_campaign=rewrite ,里面有三件套的完整填写示例。

最后留一个实用技巧:在bindView里不要做cursor.getColumnIndex的重复调用,把它提到newView或者 Adapter 构造时缓存起来,列表滑动会明显更顺。这个优化在数据量超过 500 条时体感很强。

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

零知识证明如何为链上AI交易代理提供可信执行与可验证推理

1. 先聊一个现实问题&#xff1a;链上的交易代理&#xff0c;凭什么让人信任做DeFi的人都清楚&#xff0c;链上环境是一个典型的零信任世界——代码公开、账本公开、人人可校验。但要在这个环境里跑一个AI交易代理&#xff0c;事情就变得拧巴了。假设你在某个协议里部署了一个自…

作者头像 李华
网站建设 2026/10/18 9:59:49

Spring AI与LangChain4j深度对比:Java AI框架选型与迁移实战

1. 为什么我要做这个对比测试做Java后端的人最近一两年应该都有同感&#xff1a;AI能力集成这件事&#xff0c;从“要不要做”变成了“怎么做”。尤其是大模型调用、RAG检索增强、工具函数编排这几块&#xff0c;摆在面前的选择其实不多——要么用Spring AI&#xff0c;要么用L…

作者头像 李华
网站建设 2026/10/18 9:59:16

从TCP握手到HTTP/3:网络协议核心原理与面试避坑指南

简介&#xff1a;计算机基础.pdf 是一份面向程序员、前端/后端开发者及面试求职者的计算机基础知识点整理文档&#xff0c;内容聚焦计算机网络与浏览器工作流程两大板块&#xff0c;能帮助读者高效备战技术面试、跳槽复习与日常查漏补缺。文档系统梳理了 TCP 三次握手与四次挥手…

作者头像 李华
网站建设 2026/10/18 9:58:26

灯开关检测实战:不找现成模型,自己训练YOLO并部署

如果你搜过“YOLO 灯开关 模型”&#xff0c;大概率会翻到两种东西&#xff1a;一种是卖模型文件的帖子&#xff0c;另一种就是“收一个训练好的pt文件”这种求购。我想先泼一盆冷水&#xff1a;现成的、能稳定识别灯的开关的 .pt 文件&#xff0c;在公开渠道基本找不到&#x…

作者头像 李华
网站建设 2026/10/18 9:57:50

开题报告AI工具怎么选?我把TaoToken接进Cline MCP后说点实在的

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

作者头像 李华
网站建设 2026/10/18 9:57:42

PHPCMS V9.6.6修改版php7部署实战:phpsso移除与html5适配

简介&#xff1a;这是一份面向PHP开发者的PHPCMS V9.6.6深度修改版&#xff0c;针对老版本在PHP8、MySQL8及HTTPS环境下兼容性差、Flash组件被淘汰等问题做了系统重构。资源包为rar压缩格式&#xff0c;整体约20.04MB&#xff0c;包含源码、类库与安装文件等核心内容&#xff0…

作者头像 李华