ARTICLE DETAIL

资讯详情

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

【Android】ListView自定义Adapter接入TaoToken:settings.json骨架与验证

【Android】ListView自定义Adapter接入TaoToken:settings.json骨架与验证 1. 从商品列表到 AI 能力ListView 自定义 Adapter 的真实接入场景Android 里 ListView 配合自定义 Adapter 是很多老项目、教学项目、内部工具的主力写法。系统自带的 ArrayAdapter、SimpleAdapter 能应付纯文本列表但一旦 item 里同时有图片、标题、价格、按钮还要针对每一行做不同响应就必须自己继承 BaseAdapter把数据 bean、item 布局、控件绑定、点击事件全部接管。这个模式本身不复杂真正让人头疼的是当你想给列表里的某一行加一个AI 生成商品描述AI 推荐相似商品AI 总结评论之类的功能时Key 怎么管、请求发到哪、返回怎么塞回 Adapter很容易写成一团。我这次要做的是在一个已经跑通的商品列表自定义 Adapter 基础上接入 TaoToken 提供的统一 Key 与 API 通道。TaoToken 是一个面向开发者的 AI 能力聚合入口你可以把它理解成一个 Key 打通多种模型调用的中间层不用在 Android 工程里到处硬编码不同厂商的地址和密钥而是统一走一个兼容接口模型对话、编码辅助、Agent 场景都能覆盖。它适合谁适合正在用 ListView/RecyclerView 做业务列表、又想低成本试水 AI 能力的 Android 开发者尤其是项目里已经有自定义 Adapter、不想大改架构的人。下面我会先给出可复制的settings.json配置骨架再改造 Adapter 里的调用代码最后用本地请求验证是否真的通了。整个过程围绕商品列表 自定义 Adapter这个具体场景展开不空谈概念。2. TaoToken 前置准备Key、通道与 settings.json 骨架在动手改 Adapter 之前先把通道这件事理清楚。传统做法是每个模型厂商一个域名、一个 Key、一套鉴权头散落在代码各处。TaoToken 的思路是把这些收敛成一套你拿到一个统一 Key请求发到统一 API 地址由它去路由到具体模型。对 Android 端来说好处是网络层只需要维护一份配置换模型、加能力时不用动 Adapter 逻辑。你需要先准备好两样东西一个是 TaoToken 的 API Key另一个是确认好要调用的模型标识。Key 的获取在控制台的 API Keys 页面完成建议单独建一个给 Android 项目用的 Key方便后续按项目排查用量。拿到 Key 之后不要直接写死在 Java 代码里而是放进一个settings.json骨架文件由构建流程或运行时读取。这样做的原因是Adapter 里往往有多个调用点硬编码会导致改一次 Key 要翻遍整个工程。下面是我实测下来比较顺手的settings.json骨架放在app/src/main/assets/目录下{ taotoken: { baseUrl: https://taotoken.net/api, apiKey: sk-你的TaoTokenKey, defaultModel: claude-sonnet, timeoutMs: 30000, retry: 2 }, features: { productSummary: true, similarRecommend: false } }字段说明用表格对照一下更清楚字段作用建议值baseUrl统一 API 入口https://taotoken.net/apiapiKey统一鉴权 Key控制台生成勿提交到公开仓库defaultModel默认模型标识按你开通的能力填timeoutMs单次请求超时30000 起步列表批量调用可调大retry失败重试次数2 次足够避免列表滚动时雪崩注意settings.json里放 Key 只是本地调试的便利做法。正式项目建议把 Key 放在服务端Android 端只拿短期凭证避免反编译泄露。TaoToken 的接入文档里有更细的鉴权说明可以对照着看。配置骨架就绪后Adapter 侧要做的是读配置 发请求 回填 UI三件事。读配置可以写一个简单的单例避免每个 getView 都去解析 JSON。3. 可复制配置在自定义 Adapter 中调用 TaoToken API现在进入核心改造。假设你已经有一个类似MyAdapter extends BaseAdapter的商品列表适配器item 布局里有good_name、price、purchase这些控件。我们要加的能力是点击某一行的购买按钮时调用 TaoToken 生成一句该商品的 AI 推荐语然后弹出来或更新到 item 上。第一步写一个配置读取类把settings.json解析成对象public class TaoConfig { public String baseUrl; public String apiKey; public String defaultModel; public int timeoutMs; public static TaoConfig load(Context context) { try (InputStream is context.getAssets().open(settings.json)) { byte[] buf new byte[is.available()]; is.read(buf); JSONObject root new JSONObject(new String(buf, StandardCharsets.UTF_8)); JSONObject t root.getJSONObject(taotoken); TaoConfig c new TaoConfig(); c.baseUrl t.getString(baseUrl); c.apiKey t.getString(apiKey); c.defaultModel t.getString(defaultModel); c.timeoutMs t.optInt(timeoutMs, 30000); return c; } catch (Exception e) { throw new RuntimeException(读取 settings.json 失败, e); } } }第二步封装一个最小的请求方法。TaoToken 的接口是兼容常见对话格式的请求体里带上模型和消息即可。这里用HttpURLConnection演示避免引入额外依赖方便你直接复制public class TaoClient { private final TaoConfig config; public TaoClient(TaoConfig config) { this.config config; } public String chat(String prompt) throws IOException { URL url new URL(config.baseUrl /v1/chat/completions); HttpURLConnection conn (HttpURLConnection) url.openConnection(); conn.setRequestMethod(POST); conn.setConnectTimeout(config.timeoutMs); conn.setReadTimeout(config.timeoutMs); conn.setRequestProperty(Content-Type, application/json); conn.setRequestProperty(Authorization, Bearer config.apiKey); conn.setDoOutput(true); JSONObject body new JSONObject(); try { body.put(model, config.defaultModel); JSONArray messages new JSONArray(); JSONObject msg new JSONObject(); msg.put(role, user); msg.put(content, prompt); messages.put(msg); body.put(messages, messages); } catch (JSONException e) { throw new IOException(构造请求体失败, e); } try (OutputStream os conn.getOutputStream()) { os.write(body.toString().getBytes(StandardCharsets.UTF_8)); } int code conn.getResponseCode(); InputStream is code 200 code 300 ? conn.getInputStream() : conn.getErrorStream(); BufferedReader reader new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); String line; while ((line reader.readLine()) ! null) { sb.append(line); } reader.close(); conn.disconnect(); if (code 200 || code 300) { throw new IOException(请求失败 code code body sb); } return sb.toString(); } }第三步改造 Adapter 的getView。关键点是网络请求不能跑在主线程否则列表一滚动就 ANR。用线程池或ExecutorService把请求丢到后台回调再切回主线程更新 UI。下面是在原有getView基础上加 AI 调用的片段private final ExecutorService pool Executors.newFixedThreadPool(3); private final Handler mainHandler new Handler(Looper.getMainLooper()); private final TaoClient client; public MyAdapter(ListProduct productions, Context context) { this.productions productions; this.context context; this.client new TaoClient(TaoConfig.load(context)); } Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView null) { convertView LayoutInflater.from(context) .inflate(R.layout.activity_list_view2_item, parent, false); } ImageView tvImg convertView.findViewById(R.id.good_img); TextView tvTitle convertView.findViewById(R.id.good_name); TextView tvPrice convertView.findViewById(R.id.price); Button purchase convertView.findViewById(R.id.purchase); Product product productions.get(position); tvImg.setImageResource(product.getImgId()); tvTitle.setText(product.getTitle()); tvPrice.setText(String.valueOf(product.getPrice())); purchase.setOnClickListener(v - { String prompt 用一句话推荐商品 product.getTitle() 价格 product.getPrice() 元语气活泼。; pool.execute(() - { try { String resp client.chat(prompt); String text parseContent(resp); mainHandler.post(() - Toast.makeText(context, text, Toast.LENGTH_LONG).show()); } catch (IOException e) { mainHandler.post(() - Toast.makeText(context, AI 调用失败 e.getMessage(), Toast.LENGTH_LONG).show()); } }); }); return convertView; }其中parseContent负责从返回 JSON 里取出choices[0].message.content不同模型字段可能略有差异按实际返回结构解析即可。这里有个我踩过的坑convertView复用后如果异步回调回来时这一行已经被滚出屏幕、复用到别的商品上Toast 内容就会串。稳妥做法是在回调里用position和当前绑定的商品 id 做一次校验或者干脆把结果缓存到 bean 上由notifyDataSetChanged统一刷新。4. 验证请求是否成功本地跑通的具体步骤代码写完了怎么确认真的通了不要一上来就在真机上点按钮先用最小成本验证通道。我一般分三步走。第一步用命令行直接打 TaoToken 的接口确认 Key 和地址没问题。这一步能排除掉 90% 的配置错误curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-你的TaoTokenKey \ -d { model: claude-sonnet, messages: [{role: user, content: 说一句你好}] }如果返回里能看到choices数组和一段文本说明 Key、地址、模型标识三者都对。如果返回 401检查 Key 是否复制完整、有没有多余空格返回 404检查baseUrl后面拼的路径是否正确返回超时先确认网络环境能正常访问该域名。第二步在 Android 工程里加一个临时的验证入口比如在 Activity 的onCreate里直接调一次TaoClient.chat把结果打到 Logcat。这样能验证 assets 读取、JSON 解析、网络权限是否都配好了。别忘了在AndroidManifest.xml里加uses-permission android:nameandroid.permission.INTERNET /第三步回到 ListView 场景点某一行购买按钮观察 Toast 是否弹出 AI 推荐语。如果弹的是AI 调用失败把异常信息完整打出来对照下一节的排查表定位。提示验证阶段可以把timeoutMs调小到 10000快速暴露网络问题确认通了再调回 30000避免列表批量调用时误杀。5. 本篇常见错误排查Adapter 接入 AI 时的典型报错接入过程中遇到的报错大多集中在几个固定位置。下面这张表是我和身边开发者实际遇到过的按现象、原因、处理方式整理现象可能原因处理方式NetworkOnMainThreadException在 getView 里直接发请求用线程池 Handler 切回主线程401 UnauthorizedKey 错误或未带 Authorization 头检查 settings.json 与请求头拼接404 Not FoundbaseUrl 或路径拼错确认是 https://taotoken.net/api 加 /v1/chat/completions返回内容为空解析字段取错打印完整响应按实际结构取 contentToast 内容串行convertView 复用导致回调错位回调里校验 position 或缓存到 bean列表滚动卡顿每行都发请求改为点击触发或加请求去重与缓存assets 读取失败文件没放对目录确认在 app/src/main/assets/settings.json其中Toast 内容串行最隐蔽因为功能看起来是好的只是偶尔弹错。根因是 ListView 的回收机制一行滚出屏幕后它的 View 会被复用给新数据而你的异步回调还持有旧的 position。解决办法有两个方向一是回调时用商品唯一 id 比对当前行绑定的 id不一致就丢弃二是把 AI 结果写回 Product bean调用notifyDataSetChanged让 Adapter 重新绑定这样即使复用也不会错。另一个容易忽略的点是并发量。ListView 快速滚动时如果每行都触发请求线程池会被打满表现为部分请求超时。建议在 Adapter 里维护一个已请求过的商品 id 集合命中就跳过或者把触发时机从进入可视区域改成用户点击。6. 下一步把统一通道用顺再谈扩展走到这里你已经完成了从 settings.json 骨架、Adapter 调用代码到本地验证的完整闭环。回头看真正让这件事变简单的是把 Key 和地址收敛成一份配置Adapter 只负责什么时候调、调完怎么显示。后续如果你想加更多能力比如给列表做批量摘要、给商品生成标签思路是一样的新增一个 feature 开关复用同一个 TaoClient不用再碰鉴权逻辑。如果你在排障或接入细节上卡住可以直接去 API Keys 页面核对 Key 状态再对照接入文档确认请求格式想先验证模型返回是否符合预期用模型对话页面手动发几条 prompt 最快如果打算把 AI 能力长期用在编码或 Agent 流程里Coding Plan 会更合适省去按次计费的琐碎。把通道跑顺之后ListView 这个老控件也能长出新的交互方式。
返回列表