AutoCompleteTextView 与 SQLite 模糊查询绑定:TaoToken 给 Codex 配好 Key 后重写

AutoCompleteTextView 与 SQLite 模糊查询绑定:TaoToken 给 Codex 配好 Key 后重写 用 AutoCompleteTextView 做 SQLite 模糊查询时最麻烦的不是 setAdapter 那一行而是 CursorAdapter 里的绑定细节。TaoToken 配好 Key 后Codex 可以帮你把这段代码一次重写对。先去 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 拿一个 API Key再回来按照原文的 train_no like 逻辑改代码。原文是一个 Android 车次输入联想示例SQLiteDatabase 打开数据库AutoCompleteTextView 设置 setThreshold(1)然后实例化一个自定义的 CursorAdapter在 runQueryOnBackgroundThread 里拼train_no like %...%查询。这个套路能跑但有很多细节比如查询结果集必须包含_id列、bindView 里要取对列索引、后台线程里不能操作 UI。这些地方一旦写错运行时不是空指针就是列不存在。我最近让 Codex 重写这段代码发现模型通道的稳定性会直接影响它输出的完整度。把 TaoToken 的 Base URL 和 Key 配进 Codex 后它给的四个回调基本不用改就能用。下面从原文的绑定链路讲起再给你一份可以直接抄的配置和重写代码。1. 先看懂原文的 CursorAdapter 绑定链路才知道让 Codex 改什么1.1 原文的绑定链路原文的核心动作可以拆成四步。第一步SQLiteDatabase sqlite this.openOrCreateDatabase(data, 0, null)这一步把数据库打开或者创建出来不需要连接网络也不用初始化表结构。第二步拿到AutoCompleteTextView后调用setThreshold(1)意思是用户输入一个字符就开始触发联想。第三步定义查询列trainColumns原文写的是String[] trainColumns new String[] {train_no, id as _id}这里已经埋了第一个关键点CursorAdapter 会从 Cursor 里取_id列做内部索引如果你的表没有_id字段就得用别的字段起别名。第四步实例化自定义适配器trainAdpter然后actv.setAdapter(trainadpter)完成绑定。上面每一步都不难但连接起来之后真正的逻辑全在自定义适配器里。原文的trainAdpter继承自CursorAdapter必须实现newView、bindView通常还要重写convertToString和runQueryOnBackgroundThread。newView负责膨胀下拉项的布局原文用的是android.R.layout.simple_dropdown_item_1linebindView负责把 Cursor 当前行的指定列文本填到 TextView 上convertToString决定选中后往输入框里填什么runQueryOnBackgroundThread则是在后台线程执行查询返回给下拉列表展示。这套结构很经典也很容易写错。1.2 两个必须记住的坑_id 列和列索引第一个坑是_id。CursorAdapter 内部会调用Cursor.getColumnIndexOrThrow(_id)如果你返回的 Cursor 里没有_id列它会直接抛异常。原文的做法是id as _id也就是把表中不一定存在的id字段在 SELECT 里起个别名。原文说“实践证明其实随便哪个字段都可以 as _id”这句话是对的但前提是那个字段的取值在当前表里唯一否则下拉列表的选中状态可能错乱。第二个坑是列索引。bindView里通过cursor.getString(columnIndex)取值这个columnIndex必须在查询结果里有意义。原文把train_no放在trainColumns第一位所以传入的列索引如果不是 0就很容易出现Invalid column index之类的崩溃。用 Codex 重写时我会让它避免写死索引而是用getColumnIndexOrThrow(train_no)动态获取。1.3 为什么需要给 Codex 配一个稳定通道之前拿 Codex 试过几次发现它在生成这类绑定代码时经常会漏掉runQueryOnBackgroundThread或者把_id列写没。后来我意识到问题不全在 prompt而是模型通道本身。官方额度用得快、多 Key 来回切、不同 Key 对应不同模型都会让 Codex 的行为不稳定。给 Codex 配上 TaoToken 之后我把它当作统一接入的 API 通道所有请求都走同一个 Base URL模型 ID 从模型广场选一个固定的这样 Codex 生成的代码风格就比较稳定。这里不是让 Codex 直接连数据库而是让 Codex 有一个可靠的模型通道能把你描述的业务逻辑翻译成正确的 Java 代码。2. 准备重写前要拿到的两样东西2.1 在 TaoToken 官网创建 API Key重写之前先解决模型通道。打开 TaoToken 注册一个账号登录后进控制台创建一个 API Key。这个 Key 的格式是一串类似 sk- 开头的字符串创建后立刻复制因为它只在创建页面完整显示一次。创建完之后你会在控制台看到模型广场和用量页面。模型广场上列出了当前可用的模型 ID后面配置 Codex 时要从中选一个。注意官网落地页和 API Base URL 是两回事注册、建 Key、看模型、看用量都走 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 而填进 Codex 配置的是https://taotoken.net/api末尾不要加/v1。2.2 准备一个最小的 train 表测试数据为了让 Codex 输出不跑偏最好自己先有一个最小可复现的表结构。原文查的是train表列名是train_no另外有一个id字段用来起_id别名。你可以在项目初始化时执行下面的建表语句CREATE TABLE train ( id INTEGER PRIMARY KEY AUTOINCREMENT, train_no TEXT NOT NULL ); INSERT INTO train (train_no) VALUES (G1001); INSERT INTO train (train_no) VALUES (G1002); INSERT INTO train (train_no) VALUES (D2001);这个id字段在查询里写作id as _id这样 CursorAdapter 不会报找不到_id。如果表里没有id也可以把别的字段作为_id但最好保证它唯一。准备一个这样的最小表后面让 Codex 生成代码时就不会凭空编造列名。注意openOrCreateDatabase创建的是一个普通的 SQLite 数据库路径在应用私有目录下自己调试时别拿生产数据库试。3. 把 Codex 指向 TaoToken改 ~/.codex/config.toml3.1 配置 model_provider 和 Base URLCodex 读取的是~/.codex/config.toml。如果这个文件不存在先创建~/.codex目录。推荐在文件底部加一个自定义 provider然后把默认 provider 指向它。下面是一个最小可用的配置model_provider taotoken model YOUR_MODEL_ID [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY其中YOUR_MODEL_ID要去 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的模型广场复制一个真实存在的模型 ID不要拍脑袋写。TAOTOKEN_API_KEY是环境变量名值是你的 API Key。在终端里这样设置export TAOTOKEN_API_KEYYOUR_API_KEY这里的YOUR_API_KEY也是占位符需要换成从 TaoToken 控制台创建的那串 Key。执行完 export 后Codex 会在启动时读config.toml从环境变量里拿到 Key再向base_url发起请求。注意base_url末尾没有/v1也不要追加任何 UTM 参数。官网地址只用于人点机器请求不走官网。3.2 验证 Codex 已经能调用 TaoToken配置好后建议先打开终端确认环境变量已经在当前会话里生效。可以直接执行echo $TAOTOKEN_API_KEY如果输出的是你创建的 Key说明环境变量没问题。然后用一条最简单的指令测试 Codex 的通道是否通codex exec 用一句话说明你已经准备好如果 Codex 能正常回答说明 Key 和 Base URL 都配对了。如果报鉴权错误检查YOUR_API_KEY是否替换完整、环境变量是否在当前终端生效。也可以把TAOTOKEN_API_KEY写到~/.codex/.env但那种方式不如 export 直观这里不推荐。验证通过后再进入下一步的重写。这里顺便说一句Codex 的配置和 Claude Code 不一样不要往ANTHROPIC_BASE_URL里填 TaoToken 的地址Codex 认的是config.toml里的base_url。4. 让 Codex 重写 AutoCompleteTextView 模糊查询绑定代码4.1 给 Codex 的 prompt包含表结构和返回值要求验证完通道直接把需求告诉 Codex。建议把原文的业务规则说清楚包括表结构、查询方式、需要实现哪些回调。下面是一个可复制的 prompt“请用 Java 写一个 AutoCompleteTextView 的模糊查询绑定。数据库是 SQLite表名 train字段 train_no、id。输入一个字符后下拉列表显示匹配的 train_no匹配规则是 train_no like %输入值%限制返回 100 条。要求自定义适配器继承 CursorAdapter查询结果里必须包含 _id 列用 id as _id 实现。newView 使用 android.R.layout.simple_dropdown_item_1linebindView 设置 TextView 文本convertToString 返回 train_norunQueryOnBackgroundThread 中执行查询并返回 Cursor。”把这段 prompt 贴给 Codex它会生成一个跟原文结构类似但更完整的实现。你不需要把原文的每行代码都贴给它只要把关键约束写全尤其是id as _id和limit 100。这样得到的结果比让它凭空生成更像你要的版本。4.2 重写后的 CursorAdapter 骨架Java 代码我自己拿这份 prompt 跑出来的核心代码大概是下面这样你也可以直接对照检查 Codex 的产出public class TrainAdapter extends CursorAdapter { private final SQLiteDatabase db; private final String[] columns new String[]{train_no, id as _id}; public TrainAdapter(Context context, SQLiteDatabase db) { super(context, null, 0); this.db db; } Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return LayoutInflater.from(context) .inflate(android.R.layout.simple_dropdown_item_1line, parent, false); } Override public void bindView(View view, Context context, Cursor cursor) { TextView tv (TextView) view; tv.setText(cursor.getString(cursor.getColumnIndexOrThrow(train_no))); } Override public CharSequence convertToString(Cursor cursor) { return cursor.getString(cursor.getColumnIndexOrThrow(train_no)); } Override public Cursor runQueryOnBackgroundThread(CharSequence constraint) { if (constraint null || constraint.length() 0) { return null; } String selection train_no like ?; String[] selectionArgs new String[]{% constraint %}; return db.query(train, columns, selection, selectionArgs, null, null, train_no, 100); } }这段代码把原文里拼接like %...%的做法改成了like ?用selectionArgs传参数避免 SQL 注入。limit 100通过db.query的最后一个limit参数传入而不是拼进selection字符串。列索引不再写死而是用getColumnIndexOrThrow(train_no)动态取。newView和convertToString的逻辑与原文一致但代码更规整。4.3 检查 Codex 输出中的三个关键点Codex 生成完不要直接复制就运行。先检查三点。第一columns里是否有id as _id没有的话CursorAdapter 内部会炸。第二runQueryOnBackgroundThread是否在后台线程执行了查询并返回 Cursor而不是直接操作 UI。第三bindView和convertToString是否取了同一列否则下拉文本和输入框内容不一致。如果 Codex 漏了任何一点可以继续追问“请加上id as _id”或“请在 runQueryOnBackgroundThread 里使用 selectionArgs”。通常补一轮就能得到正确答案。5. 跑起来的验证步骤输入一个字符看下拉列表5.1 连接数据库并 setAdapter拿到 Codex 生成的TrainAdapter后在 Activity 里按原文的顺序初始化。先openOrCreateDatabase再 findViewById 拿到AutoCompleteTextView调用setThreshold(1)然后 new 出TrainAdapter最后setAdapter。注意SQLiteDatabase的实例要作为 Activity 的成员变量或者在 adapter 里持有引用否则runQueryOnBackgroundThread执行时数据库已经关掉。下面是绑定部分的示意代码SQLiteDatabase sqlite this.openOrCreateDatabase(data, 0, null); AutoCompleteTextView actv (AutoCompleteTextView) findViewById(R.id.actv); actv.setThreshold(1); TrainAdapter adapter new TrainAdapter(this, sqlite); actv.setAdapter(adapter);这句actv.setThreshold(1)不解释就是输入一个字符就开始匹配。之后运行应用在输入框里敲一个字母正常情况下就应该出现下拉联想列表。如果你敲了没反应先确认数据库里真的插入了测试数据再确认TrainAdapter的构造函数里传的SQLiteDatabase不是 null。下拉列表出现后点击某一条输入框会变成对应的train_no这一步能走到就说明convertToString正常。5.2 打开 Log 观察 convertToString 是否被调用如果你想确认整个绑定链路是否真的走了模糊查询可以在convertToString里加一条日志。Codex 生成的代码默认没有日志你可以手动加上Override public CharSequence convertToString(Cursor cursor) { Log.i(TrainAdapter, convertToString called); return cursor.getString(cursor.getColumnIndexOrThrow(train_no)); }选中某条下拉项时convertToString会被调用返回值会变成输入框的文本。如果这个方法没有被调用说明下拉列表根本没弹出来问题多半在runQueryOnBackgroundThread返回的 Cursor 为空或者setThreshold(1)没生效。通过 Logcat 的TrainAdaptertag可以快速判断用户交互走到了哪一步。6. 排障这次重写最容易踩的两个错6.1 no such column: _id这个错误几乎都出在查询列没有_id别名。CursorAdapter 内部需要_id来管理游标位置查出来没有就会抛SQLiteException: no such column: _id。解决办法只有一个在db.query的columns参数里加入id as _id。注意是查询列不是表里的物理字段。原文里写的是id as _id如果你的表主键叫别的名字也要在 SELECT 里把它 as 成_id。如果 Codex 生成的代码里没有这一项直接让它补上。6.2 Cannot perform this operation because the connection is closed第二个常见错误是数据库连接关闭后后台线程还在查。原因是SQLiteDatabase的生命周期没有管理好Activity 销毁时执行了sqlite.close()但runQueryOnBackgroundThread可能要等下次输入才执行。解决方法是把SQLiteDatabase实例的作用域放到 adapter 内部并在 Activity 的onDestroy里显式关闭 adapter 持有的引用。如果只是在调试可以不关进程结束会自动释放但不建议在生产环境这么干。Codex 生成的代码通常不会管这些你需要自己在TrainAdapter里加一个close()方法在 Activity 销毁时调用。6.3 如果 Codex 一直报认证错误如果你的 Codex 输出不是代码而是 401 之类的错误先检查两件事base_url是不是https://taotoken.net/api末尾有没有多写/v1环境变量TAOTOKEN_API_KEY是否等于你从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建的那串 Key。注意官网域名和 API 地址不一样不要拿官网链接去填base_url。确认无误后重启 Codex 进程再试。7. 在 TaoToken 控制台确认这次调用已经记账重写代码并跑通后回到 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的控制台找到用量页面你会看到刚才 Codex 发起的那几次请求记录。这个动作很有用它能反过来验证config.toml里的base_url和 Key 确实被 Codex 用到了。如果你发现控制台没有新的调用记录说明 Codex 可能走了别的配置或缓存。这时可以检查~/.codex/config.toml里有没有被其他 provider 覆盖或者环境变量有没有在 Codex 启动前被加载。调通之后以后再做类似 AutoCompleteTextView 绑定 SQLite 的需求可以继续用同一个通道让 Codex 基于你的表结构生成newView、bindView、convertToString、runQueryOnBackgroundThread这几个方法省掉大量手抄代码的时间。