ARTICLE DETAIL

资讯详情

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

Android 选择文件(调用系统文件管理器) 实战:把 endpoint 改到 TaoToken 的完整配置

Android 选择文件(调用系统文件管理器) 实战:把 endpoint 改到 TaoToken 的完整配置 1. Android 选择文件调用系统文件管理器时为什么要把 endpoint 改到 TaoToken做 Android 文件选择这个功能很多人第一反应是去搜「Android 选择文件 调用系统文件管理器」然后复制一段ACTION_GET_CONTENT的代码贴进项目跑起来发现能弹出文件管理器就以为完事了。真正上线之后才会遇到一连串问题Android 7.0 之后file://被FileUriExposedException拦掉Android 10 之后分区存储让_data列直接查不到Android 13 又细分出READ_MEDIA_IMAGES这类权限。这些坑我在真实项目里都踩过光是把 Uri 转成可读路径这一件事就够写一个工具类。但今天这篇不只讲文件选择。场景是这样的你选完文件之后往往要把文件内容或者文件元信息发给大模型做处理比如让模型识别图片里的文字、总结文档内容、或者根据用户选的文件生成一段描述。这时候就需要一个稳定的 API 通道。很多开发者习惯把 endpoint 写死在代码里或者散落在BuildConfig、local.properties、gradle.properties好几个地方换一个模型供应商就要全局搜索替换非常痛苦。TaoToken 在这里扮演的角色是一个统一的 Key 和 API 通道。你可以把它理解成「一个 endpoint 走天下」不管底层换什么模型Android 端只认一个 Base URL 和一个 Key模型 ID 通过参数传。这样文件选择链路和网络请求链路就解耦了文件选择部分该怎么做还怎么做网络部分只改配置。这篇文章适合谁适合已经会写 Android 基础代码、能跑通一个 Activity、但对「文件选择 网络请求配置」这条完整链路还不够熟的开发者。我会从 Intent 参数清单讲起给出可直接复制的 endpoint 配置片段然后完成一次可复现的请求验证。全程不涉及任何网络工具只讲代码和配置。核心检索词先明确Android 选择文件调用系统文件管理器配合 TaoToken 统一 endpoint 配置。前者解决「怎么拿到文件」后者解决「拿到文件后怎么稳定发请求」。2. TaoToken 前置准备Base URL、Key 与 Model ID 三件套在动手改 Android 代码之前先把服务端这一侧的三件套准备好。所谓三件套就是 Base URL、API Key、Model ID。这三样东西缺一不可而且必须成对出现否则请求一定失败。Base URL 用https://taotoken.net/api注意这个地址不带任何查询参数是纯粹的 API 根路径。API Key 需要你去控制台生成入口在 https://taotoken.net/api-keys 生成之后复制出来形如sk-开头的一串字符。Model ID 则是你要调用的具体模型标识比如对话类、代码类各有不同的 ID具体以文档为准文档入口在 https://taotoken.net/doc 。这里有个细节很多人会搞混Base URL 到底要不要带/v1我的建议是先看文档里给的示例请求路径。如果文档写的是POST /v1/chat/completions那你的 Base URL 就填到/api为止路径拼接交给 HTTP 客户端。如果你用的是 OpenAI 兼容的 SDK它通常会自动补/v1这时候 Base URL 就填https://taotoken.net/api不要自己再加/v1否则会变成/api/v1/v1/...直接 404。我试过在 Android 里用 OkHttp 手写请求也试过用 Retrofit 加拦截器统一注入 Key。两种方式都行但拦截器方式更适合「endpoint 统一管理」这个目标因为你可以把 Base URL 和 Key 都放在拦截器里业务代码只关心 Model ID 和消息体。关于 Key 的安全Android 端要特别注意不要把 Key 硬编码在 Java/Kotlin 源码里因为 APK 可以被反编译。比较稳妥的做法是放在local.properties里通过BuildConfig注入或者干脆让 App 走自己的后端中转。本文为了演示方便会用一个常量占位但你在真实项目里一定要替换成安全方案。还有一个容易被忽略的点Android 9.0 之后默认禁止明文 HTTP 请求。TaoToken 的 API 是 HTTPS所以不受影响。但如果你本地调试时用了 HTTP 代理记得在network_security_config.xml里放行否则会报CLEARTEXT communication not permitted。这个报错后面排障章节会详细讲。三件套准备好之后先别急着写 Android 代码用 curl 在电脑上验证一次确认 Key 和 Model ID 是通的。这一步能帮你排除掉一半的问题因为如果服务端配置错了你在 Android 里怎么调都是白费。curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的Key \ -H Content-Type: application/json \ -d { model: 你的ModelID, messages: [{role: user, content: ping}] }如果返回里有choices字段说明三件套没问题可以进入 Android 环节了。如果返回 401说明 Key 不对如果返回 404多半是路径拼错了如果返回模型不存在那就是 Model ID 写错了。这三种错误后面都会单独讲。3. 可复制配置Intent 参数清单与 endpoint 配置片段这一节是全文的核心我会给出两块可直接复制的东西一块是 Android 选择文件的 Intent 参数清单和工具类另一块是 endpoint 的配置片段。两块拼起来就是一条完整的链路。先说 Intent。调用系统文件管理器最通用的方式是ACTION_GET_CONTENT配合CATEGORY_OPENABLE。参数清单如下参数作用常用取值action动作Intent.ACTION_GET_CONTENTtypeMIME 类型*/*、image/*、video/*、application/pdfcategory类别Intent.CATEGORY_OPENABLEextra多选Intent.EXTRA_ALLOW_MULTIPLE如果你要选图片type写image/*要选视频写video/*要同时选视频和图片写video/*;image/*不限类型就写*/*。注意CATEGORY_OPENABLE一定要加否则某些文件管理器会返回不可打开的 Uri后续读取会失败。调用代码Intent intent new Intent(Intent.ACTION_GET_CONTENT); intent.setType(*/*); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); startActivityForResult(intent, REQUEST_CHOOSEFILE);拿到 Uri 之后不要直接uri.getPath()因为 Android 7.0 之后返回的可能是content://开头的 UrigetPath()拿不到真实路径。正确做法是用ContentResolver查询_data列或者更现代的做法是直接用ContentResolver.openInputStream(uri)读取流根本不需要转成绝对路径。这一点很关键因为 Android 10 之后_data列在很多情况下返回 null转路径的老方法会失效。下面是一个更稳妥的读取方式直接拿 InputStreampublic byte[] readBytes(Context context, Uri uri) throws IOException { try (InputStream is context.getContentResolver().openInputStream(uri); ByteArrayOutputStream bos new ByteArrayOutputStream()) { if (is null) return null; byte[] buf new byte[8192]; int len; while ((len is.read(buf)) ! -1) { bos.write(buf, 0, len); } return bos.toByteArray(); } }然后是 endpoint 配置片段。我用一个ApiConfig类来集中管理这样换 endpoint 只改一个地方public final class ApiConfig { public static final String BASE_URL https://taotoken.net/api; public static final String API_KEY sk-你的Key; public static final String MODEL_ID 你的ModelID; public static final String CHAT_PATH /v1/chat/completions; private ApiConfig() {} }如果你用 Retrofit可以这样建客户端OkHttpClient client new OkHttpClient.Builder() .addInterceptor(chain - { Request original chain.request(); Request req original.newBuilder() .header(Authorization, Bearer ApiConfig.API_KEY) .header(Content-Type, application/json) .build(); return chain.proceed(req); }) .build(); Retrofit retrofit new Retrofit.Builder() .baseUrl(ApiConfig.BASE_URL /) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build();注意baseUrl末尾的斜杠不能少Retrofit 要求 baseUrl 以/结尾否则会抛IllegalArgumentException。而接口方法上的路径写v1/chat/completions不要带开头的斜杠否则会覆盖 baseUrl 的路径部分。这个细节坑过很多人。如果你用 Gradle 管理配置可以把 Key 放到local.propertiesTAOTOKEN_API_KEYsk-你的Key TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_MODEL_ID你的ModelID然后在build.gradle里读取并注入BuildConfigandroid { buildTypes { debug { buildConfigField String, API_KEY, \${localProperties[TAOTOKEN_API_KEY]}\ buildConfigField String, BASE_URL, \${localProperties[TAOTOKEN_BASE_URL]}\ buildConfigField String, MODEL_ID, \${localProperties[TAOTOKEN_MODEL_ID]}\ } } }这样业务代码里用BuildConfig.BASE_URL就行源码里不出现明文 Key。三件套齐了配置片段也齐了接下来就是验证。4. 验证请求从选文件到拿到模型返回的完整链路配置写完之后必须做一次端到端验证否则你不知道是文件选择的问题还是网络请求的问题。验证的思路是先单独验证文件选择能拿到 Uri再单独验证网络请求能通最后把两者串起来。第一步验证文件选择。在onActivityResult里打印 UriOverride protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode RESULT_OK requestCode REQUEST_CHOOSEFILE) { Uri uri data.getData(); Log.d(FilePick, uri uri); Log.d(FilePick, scheme uri.getScheme()); } }跑一次看 Logcat。如果scheme是content说明是正常的如果是file说明你用的是老式调用或者某些定制 ROM。拿到 Uri 之后用上一节的readBytes读一下确认能读到字节。这一步过了文件选择链路就没问题。第二步验证网络请求。先不接文件直接发一个纯文本请求确认 endpoint 通public void pingModel() { OkHttpClient client new OkHttpClient(); String json {\model\:\ ApiConfig.MODEL_ID \, \messages\:[{\role\:\user\,\content\:\ping\}]}; RequestBody body RequestBody.create( json, MediaType.parse(application/json)); Request request new Request.Builder() .url(ApiConfig.BASE_URL ApiConfig.CHAT_PATH) .header(Authorization, Bearer ApiConfig.API_KEY) .post(body) .build(); client.newCall(request).enqueue(new Callback() { Override public void onFailure(Call call, IOException e) { Log.e(Ping, fail, e); } Override public void onResponse(Call call, Response response) throws IOException { Log.d(Ping, code response.code()); Log.d(Ping, body response.body().string()); } }); }注意这段代码必须在子线程或者用enqueue异步Android 主线程不允许网络请求否则会抛NetworkOnMainThreadException。跑一次如果 Logcat 里看到code 200并且 body 里有choices说明 endpoint 通了。第三步把两者串起来。选完文件后读取文件内容把内容作为消息发给模型。如果是文本文件直接读成字符串如果是图片可以转成 base64 或者先上传再传 URL。这里以文本为例byte[] bytes readBytes(this, uri); String content new String(bytes, StandardCharsets.UTF_8); String json {\model\:\ ApiConfig.MODEL_ID \, \messages\:[{\role\:\user\,\content\:\请总结以下内容 content.replace(\, \\\).replace(\n, \\n) \}]};这里有个坑直接把文件内容拼进 JSON 字符串如果内容里有引号、换行、反斜杠JSON 就会非法服务端返回 400。正确做法是用 Gson 或者 org.json 构造对象让它自动转义JSONObject msg new JSONObject(); msg.put(role, user); msg.put(content, 请总结以下内容 content); JSONArray arr new JSONArray(); arr.put(msg); JSONObject payload new JSONObject(); payload.put(model, ApiConfig.MODEL_ID); payload.put(messages, arr); String json payload.toString();这样无论文件内容多复杂JSON 都是合法的。实测下来这一步能省掉大量「为什么返回 400」的排查时间。验证成功的标志Logcat 里看到code 200body 里有模型返回的总结文本。到这一步整条链路就通了用户点按钮 → 系统文件管理器弹出 → 选中文件 → 读取内容 → 发到 TaoToken endpoint → 拿到模型返回。5. 本篇常见错误排查401、local proxy failed、reading choices、OAuth这一节把最常见的四类报错列出来对照着排查。这些报错我在不同项目里都遇到过有的是配置问题有的是环境问题。第一类401 Unauthorized。这个最直接就是 Key 不对。可能的原因Key 复制时多了空格、Key 已经失效、请求头里Bearer后面没加空格、或者你用了Authorization: sk-xxx而漏了Bearer。排查方法把 Key 打印出来和https://taotoken.net/api-keys页面上的对比确认完全一致。另外注意有些 HTTP 客户端会自动加Bearer你再手动加一次就变成Bearer Bearer sk-xxx也会 401。第二类local proxy failed。这个报错通常出现在你本地配了代理但代理没启动或者端口不对。Android 模拟器访问宿主机有特殊地址比如10.0.2.2如果你在模拟器里配了127.0.0.1的代理那肯定连不上。排查方法检查OkHttpClient有没有.proxy()配置检查模拟器的网络设置确认没有残留的代理配置。如果你根本没配代理却报这个错那可能是某些库默认读了系统代理显式设置.proxy(Proxy.NO_PROXY)可以绕过。第三类reading choices 相关报错比如Expected BEGIN_ARRAY but was BEGIN_OBJECT或者Cannot deserialize。这类错误是 JSON 解析问题。TaoToken 返回的结构里choices是一个数组如果你用 Gson 定义的数据类把choices写成了对象而不是数组就会报这个错。排查方法先用response.body().string()把原始 JSON 打出来看清楚结构再定义对应的数据类。不要凭记忆写字段名choices[0].message.content这个路径要一层层对。第四类OAuth 相关报错。如果你用的是某些需要 OAuth 流程的客户端可能会遇到 token 过期或者 scope 不对的问题。TaoToken 的 API Key 方式是直接 Bearer 认证不涉及 OAuth 跳转所以如果你看到 OAuth 报错多半是你用错了认证方式或者某个 SDK 默认走了 OAuth 流程。排查方法确认你用的是 API Key 而不是 OAuth token确认请求头是Authorization: Bearer sk-xxx。除了这四类还有几个高频问题。比如CLEARTEXT communication not permitted这是 Android 9.0 之后的明文限制解决办法是确保用 HTTPS或者临时在AndroidManifest.xml里加android:usesCleartextTraffictrue仅调试用。比如FileUriExposedException这是 Android 7.0 之后的 FileProvider 要求解决办法是配置FileProvider并用FileProvider.getUriForFile()生成 Uri。比如SecurityException: Permission Denial这是没申请读存储权限Android 13 之后要申请READ_MEDIA_IMAGES等细分权限。排查的通用思路是先看 HTTP 状态码再看响应体最后看 Logcat 完整堆栈。不要只看最后一行报错往往关键信息在中间几行。把原始请求 URL、请求头、请求体都打出来和文档里的示例逐字对比90% 的问题都能定位。6. 语义一致 CTA把 endpoint 固定下来长期用 Coding Plan整条链路跑通之后你会发现真正需要维护的其实只有三件套Base URL、Key、Model ID。文件选择那部分代码写完基本不用动Intent 参数和读取流的方式是稳定的。真正会变的是模型和调用量。如果你只是偶尔调一次模型按量付费就够了。但如果你在做长期编码、Agent 类应用或者需要频繁调用模型做文件处理那建议了解一下 Coding Plan入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。它的思路是把调用额度打包适合持续使用的场景。如果你只是想先验证模型效果不想写代码可以直接用模型对话页面入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 在网页上试几次确认模型返回符合预期再回到 Android 里接。Key 的管理入口再放一次方便你随时生成和轮换https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到路径或者参数不确定的时候以文档为准。最后说一个实用技巧把 Base URL 和 Model ID 做成可配置项而不是硬编码。这样你在测试环境用一个小模型生产环境换一个大模型只需要改配置不用重新编译。Android 里可以用SharedPreferences存或者用远程配置下发。文件选择链路保持不变endpoint 随时可换这才是「统一 Key/API 通道」真正的价值。
返回列表