尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Android开发之路——遍历相册:把 MediaStore 查询改到 TaoToken 统一 Key 通道
1. 从 MediaStore 遍历相册到统一 Key 通道Android 图片列表与模型调用怎么接做 Android 相册遍历这件事绕不开一个现实系统相册、第三方相册 UI 往往满足不了产品需求尤其是要自己控制多选、分组、缩略图加载策略的时候。我最近在做一个图片整理类的小工具核心诉求有两个一是用 MediaStore 把本地图片稳定地查出来并展示缩略图二是把里面涉及的模型调用比如图片描述生成、OCR 结果润色统一走一个 Key 通道避免在客户端里散落多个厂商的 endpoint 和密钥。这篇就围绕「Android 遍历相册 TaoToken 统一 Key/API 通道」来写适合正在做相册模块、又需要接入模型能力的 Android 开发者。你会看到可复制的 MediaStore 查询配置、权限声明、TaoToken endpoint 配置片段以及遍历结果和调用返回的验证步骤。核心检索词就是 Android 遍历相册、MediaStore 查询、TaoToken 统一 Key 通道。先说清楚 TaoToken 在这里的角色它是一个统一的模型 API 入口官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end API 地址是 https://taotoken.net/api 。你不需要在 App 里为每个模型厂商维护一套鉴权逻辑只要把 Base URL 指向它用同一个 Key 就能调用不同模型。对相册类应用来说这意味着「遍历本地图片」和「调用模型处理图片」这两件事可以解耦前者是 Android 系统能力后者是网络请求互不干扰。我试过把相册遍历和模型调用混在一个工具类里结果权限、Cursor、网络回调全缠在一起排查问题时非常痛苦。后来拆成两层MediaStore 层只负责拿到图片路径和缩略图模型层只负责发请求。这样即使模型通道换了相册逻辑一行都不用动。下面按「原问题与场景 → TaoToken 前置 → 可复制配置 → 验证请求 → 常见错排查 → CTA」的顺序展开。你可以直接跳到需要的章节但建议至少把权限声明和 MediaStore 查询那段完整看一遍因为 Android 13 之后的权限模型变化挺大很多老代码会直接查不到图。2. Android 遍历相册的权限与 MediaStore 查询READ_MEDIA_IMAGES 适配与缩略图加载先讲原问题和场景。Android 遍历相册的典型做法是用 ContentResolver 查询 MediaStore.Images.Media.EXTERNAL_CONTENT_URI拿到 DATA 路径或 content URI再加载缩略图。但这里有几个坑第一权限。Android 13API 33之后READ_EXTERNAL_STORAGE 对图片不再生效要改用 READ_MEDIA_IMAGES。如果你只声明旧权限在 Android 13 设备上查询会返回空 Cursor或者直接抛 SecurityException。很多教程还在用老写法复制过去就踩坑。第二DATA 字段。MediaStore.Images.Media.DATA 在 Android 10 之后属于受限字段虽然部分场景还能读到但官方推荐用 content URI 配合 ContentResolver.openInputStream 来读取。如果你只是展示缩略图用 ContentResolver.loadThumbnail 更稳。第三Cursor 分页。相册图片可能上千张一次性查完既慢又占内存。excerpt 里用了 moveToPosition 每次 300 条的方式这个思路是对的但要注意 Cursor 的关闭和位置管理否则容易内存泄漏或重复加载。我实测下来比较稳的组合是Android 13 用 READ_MEDIA_IMAGESAndroid 12 及以下用 READ_EXTERNAL_STORAGE查询时按 DATE_MODIFIED 倒序分页加载缩略图用 ContentResolver.loadThumbnailAPI 29或 Glide 的 content URI 加载。权限声明部分AndroidManifest.xml 里这样写!-- Android 13 读取图片 -- uses-permission android:nameandroid.permission.READ_MEDIA_IMAGES / !-- Android 12 及以下读取外部存储 -- uses-permission android:nameandroid.permission.READ_EXTERNAL_STORAGE android:maxSdkVersion32 / !-- 如果涉及拍照 -- uses-permission android:nameandroid.permission.CAMERA /注意 READ_EXTERNAL_STORAGE 加了 maxSdkVersion32这样在 Android 13 上不会请求这个已经无效的权限。WRITE_EXTERNAL_STORAGE 在 Android 10 之后基本不需要了除非你要写公共目录这里先不声明避免权限列表过长。动态申请权限时按系统版本分支private fun requiredImagePermissions(): ArrayString { return if (Build.VERSION.SDK_INT Build.VERSION_CODES.TIRAMISU) { arrayOf(Manifest.permission.READ_MEDIA_IMAGES) } else { arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) } } private fun requestImagePermission() { val permissions requiredImagePermissions() val notGranted permissions.filter { ContextCompat.checkSelfPermission(this, it) ! PackageManager.PERMISSION_GRANTED } if (notGranted.isEmpty()) { scanAllImages() } else { ActivityCompat.requestPermissions(this, notGranted.toTypedArray(), REQ_CODE_IMAGE) } }这里用 Kotlin 写比 excerpt 里的 Java 更简洁逻辑一致先检查未授权的才申请全部授权后直接进入遍历。MediaStore 查询部分我建议用 content URI 而不是 DATA 路径。查询语句可以这样组织private fun scanAllImages(offset: Int, limit: Int): ListImageItem { val result mutableListOfImageItem() val projection arrayOf( MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.MIME_TYPE, MediaStore.Images.Media.DATE_MODIFIED ) val selection ${MediaStore.Images.Media.MIME_TYPE} IN (?, ?) val selectionArgs arrayOf(image/jpeg, image/png) val sortOrder ${MediaStore.Images.Media.DATE_MODIFIED} DESC LIMIT $limit OFFSET $offset val cursor contentResolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, sortOrder ) cursor?.use { c - val idCol c.getColumnIndexOrThrow(MediaStore.Images.Media._ID) val nameCol c.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME) while (c.moveToNext()) { val id c.getLong(idCol) val name c.getString(nameCol) val uri ContentUris.withAppendedId( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id ) result.add(ImageItem(id, name, uri)) } } return result }这里用 LIMIT/OFFSET 做分页比 moveToPosition 更直观。注意 sortOrder 里拼 LIMIT 在部分设备上可能不生效更稳的做法是用 Bundle 传 queryArgsAPI 26 支持 SQLite LIMIT或者查完后在内存里截取。如果目标设备版本较低还是用 excerpt 里的 moveToPosition 方式更保险。缩略图加载API 29 可以直接用val thumbnail: Bitmap? contentResolver.loadThumbnail(uri, Size(200, 200), null)低版本用 GlideGlide.with(imageView) .load(uri) .override(200, 200) .into(imageView)这样遍历出来的图片列表就能稳定展示了。整个过程不涉及任何网络请求纯本地操作和后面的 TaoToken 通道完全解耦。3. TaoToken 统一 Key 通道配置Base URL、API Key 与 Model ID 三件套相册遍历完成后下一步是把模型调用接进来。比如你想对选中的图片做描述生成或者把 OCR 结果交给模型润色这时候就需要一个稳定的 API 通道。TaoToken 的作用就是把这些调用统一到一个 Base URL 和一个 Key 上。前置准备很简单去官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册后在控制台创建 API Key。控制台地址是 https://taotoken.net/console API Keys 管理页是 https://taotoken.net/api-keys 。拿到 Key 之后你需要的三件套是Base URLhttps://taotoken.net/apiAPI Key你在控制台生成的那串Model ID按需选择比如对话模型、视觉模型等在 Android 项目里我建议把这三个值放在 BuildConfig 或 local.properties 里不要硬编码在代码中。用 Gradle 配置// app/build.gradle android { defaultConfig { buildConfigField String, TAOTOKEN_BASE_URL, \https://taotoken.net/api\ buildConfigField String, TAOTOKEN_API_KEY, \${project.findProperty(TAOTOKEN_API_KEY) ?: }\ buildConfigField String, TAOTOKEN_MODEL_ID, \your-model-id\ } }然后在 local.properties 里写TAOTOKEN_API_KEYsk-你的key这样 Key 不会进版本库。如果你用 Kotlin DSL写法类似buildConfigField(String, TAOTOKEN_BASE_URL, \https://taotoken.net/api\)接下来是网络请求层。用 OkHttp Retrofit 或者直接 OkHttp 都行。这里给一个 OkHttp 的最小配置方便你直接复制val client OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .addInterceptor { chain - val request chain.request().newBuilder() .addHeader(Authorization, Bearer ${BuildConfig.TAOTOKEN_API_KEY}) .addHeader(Content-Type, application/json) .build() chain.proceed(request) } .build()请求体按 OpenAI 兼容格式组织因为 TaoToken 的 API 是兼容这种格式的{ model: your-model-id, messages: [ { role: user, content: 请描述这张图片的内容 } ], max_tokens: 512 }如果你要传图片用视觉模型时 content 可以改成数组形式包含 image_url。具体支持的模型和参数可以看接入文档 https://taotoken.net/doc 。这里要强调一点TaoToken 是统一 Key 通道不是让你在客户端里做灰色中转。它的定位是帮你把多个模型的鉴权收敛到一个入口减少客户端维护成本。你在代码里只需要维护一个 Base URL 和一个 Key换模型时只改 Model ID。对于长期做编码或 Agent 的场景可以考虑 Coding Plan地址是 https://taotoken.net/coding-plan 。如果只是验证模型效果用模型对话页 https://taotoken.net/models 更快。配置完成后你的 Android 项目里就有了一个统一的模型调用出口。相册遍历拿到的图片 URI可以转成 Base64 或上传后拿 URL再交给这个通道处理。两层之间通过一个 Repository 或 UseCase 连接职责清晰。4. 验证遍历结果与模型调用返回从 Cursor 到 HTTP 响应的完整链路配置写完了怎么确认真的通了分两步验证先验证 MediaStore 遍历结果再验证 TaoToken 调用返回。第一步遍历结果验证。在 scanAllImages 返回后打日志val images scanAllImages(offset 0, limit 300) Log.d(AlbumScan, count${images.size}) images.take(5).forEach { Log.d(AlbumScan, id${it.id}, name${it.name}, uri${it.uri}) }预期结果是 count 大于 0且 uri 形如 content://media/external/images/media/12345。如果 count 为 0先检查权限是否真的授予了再检查 MIME_TYPE 过滤条件是否太窄。有些设备上 PNG 的 MIME 可能是 image/png但截图可能是 image/webp你可以先把 selection 去掉查全部图片看看有没有结果。第二步模型调用验证。用一个最简单的请求测试通道val json { model: ${BuildConfig.TAOTOKEN_MODEL_ID}, messages: [{role: user, content: 回复 OK}], max_tokens: 16 } .trimIndent() val request Request.Builder() .url(${BuildConfig.TAOTOKEN_BASE_URL}/v1/chat/completions) .post(json.toRequestBody(application/json.toMediaType())) .build() client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { Log.e(TaoToken, failed: ${e.message}) } override fun onResponse(call: Call, response: Response) { Log.d(TaoToken, code${response.code}, body${response.body?.string()}) } })预期返回是 HTTP 200body 里有 choices 数组choices[0].message.content 是模型回复。如果返回 401说明 Key 不对或没带上 Authorization 头如果返回 404检查 Base URL 是否多了或少了 /v1如果返回 400多半是 model 字段写错了。把这两步串起来完整链路就是请求权限 → 查询 MediaStore → 拿到图片 URI 列表 → 展示缩略图 → 用户选中图片 → 构造请求体 → 通过 TaoToken 通道调用模型 → 解析返回。每一步都有明确的输入输出出问题时能快速定位是哪一层。我建议在开发阶段加一个调试面板把遍历到的图片数量和最近一次模型调用的状态码显示出来。这样不用连 Logcat 也能看到链路是否正常。5. 常见报错排查401、local proxy failed、reading choices 与 OAuth 问题这一节对照真实报错来写都是我在接相册 模型通道时遇到过的。401 Unauthorized。最常见的原因是 Authorization 头没带上或者 Key 前后有空格。检查 BuildConfig 里的 Key 是否被 local.properties 正确注入有时候 Gradle 同步没生效BuildConfig 里是空字符串。另一个原因是 Key 被撤销或过期去控制台 https://taotoken.net/api-keys 重新生成一个。local proxy failed。这个报错通常出现在你本地配了代理工具但代理没启动或端口不对。Android 模拟器访问宿主机网络时如果走了系统代理可能连不上。解决办法是检查模拟器的代理设置或者在 OkHttp 里显式设置不使用代理。注意这里说的是本地开发环境的网络配置问题不是让你去用什么特殊工具保持网络环境干净即可。reading choices 相关报错。如果你在解析返回时抛了 NullPointerException 或 JSONException提示读不到 choices先打印完整 body。常见原因是返回的不是预期格式比如模型返回了错误信息而不是正常响应或者你请求的 endpoint 路径不对。确认 URL 是 https://taotoken.net/api/v1/chat/completions并且请求体里 model 字段是有效的 Model ID。OAuth 相关报错。如果你用的是某些需要 OAuth 流程的工具比如 Claude Code 或 Codex 的认证文件可能会遇到 token 过期。这类工具通常有自己的配置文件比如 Codex 的 auth.json里面包含 Base URL、Key 和 Model ID 三件套。如果你在 Android 项目里模拟类似流程确保这三项都写对。CC Switch 或 Cline MCP 这类工具也是同样的三件套逻辑Base URL 指向 https://taotoken.net/api Key 用控制台生成的Model ID 按需选。Cursor 查询返回空。除了权限问题还要注意 Android 10 的分区存储。如果你查询的是外部存储但图片在应用私有目录MediaStore 是查不到的。另外某些设备上媒体库还没扫描完刚拍的照片可能查不到可以手动触发 MediaScanner。缩略图加载失败。loadThumbnail 在 API 29 以下不可用低版本要用 Glide 或手动解码。另外content URI 的权限是临时的如果你在异步任务里延迟加载可能权限已经失效建议在拿到 URI 后尽快加载。排查时按「权限 → 查询 → 解析 → 网络 → 返回」的顺序逐层检查基本能覆盖大部分问题。每层都打上清晰的日志比盲目改代码高效得多。6. 把相册遍历和模型调用拆成两层Android 项目的落地建议最后说几个落地建议都是实际项目里踩过坑总结的。第一相册遍历层不要持有 Context 的强引用。用 ApplicationContext 或者 WeakReference避免内存泄漏。Cursor 一定要用 use 或 try-finally 关闭。第二分页加载的 offset 要持久化。用户滑到一半退出再进来时从上次位置继续体验会好很多。可以用 Paging 3 库也可以自己维护 offset。第三模型调用层做成接口。定义一个 ModelRepository内部用 TaoToken 通道实现。这样以后换通道或加缓存上层不用改。第四Key 不要放在客户端明文里。虽然 Android 没有绝对安全但至少用 BuildConfig local.properties再配合混淆提高一点门槛。如果涉及敏感操作考虑把模型调用放到自己的后端客户端只调后端。第五验证模型效果时先用模型对话页 https://taotoken.net/models 快速试确认 Model ID 和参数没问题再写进代码。接入文档 https://taotoken.net/doc 里有各模型的参数说明值得先看一遍。如果你要做的是长期编码或 Agent 类功能Coding Plan https://taotoken.net/coding-plan 会更合适额度和调用方式都针对这类场景优化过。整个链路的核心思路就是本地能力用 Android 原生 API网络能力用统一 Key 通道两层通过清晰的数据结构连接。相册遍历拿到 URI模型调用拿到文本结果中间用 Repository 做转换。这样无论相册 UI 怎么改或者模型通道怎么换都不会互相牵连。
RELATED

相关推荐

从RNN到Transformer:144页PPT补全序列建模认知缺口

从RNN到Transformer:144页PPT补全序列建模认知缺口

简介:这份资源是面向NLP初学者、算法工程师与高校学生的Transformer系统学习资料,由卡内基梅隆大学讲师Jean de Dieu Nyandwi主讲,以144页PPT配合视频课程的形式,帮助读者从零建立对Transformer架构的完整认知。内容从Transformer…

📅 2026/10/18 0:16:33
AI接入Kafka:数据管道可靠性、消费语义与Lag排查实战

AI接入Kafka:数据管道可靠性、消费语义与Lag排查实战

1. 先说个反直觉的结论:AI模型的胜负手在数据管道最近圈子里都在聊“Kafka已正式接入AI”,这个词条烧得很快,我翻了翻大家的讨论,发现一个奇怪的现象:大多数人盯着的是模型选型、算力调度、Agent框架,但真正…

📅 2026/10/18 0:16:33
手把手教你写 Skill:从需求定义到可复用工具流程的完整实践(TaoToken 统一 Key 接入版)

手把手教你写 Skill:从需求定义到可复用工具流程的完整实践(TaoToken 统一 Key 接入版)

/* 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 0:16:33
MORE NEWS

更多资讯

📰

LayoutLM v3深度解析:多模态文档智能模型微调实战

每次看到开源社区放出的文档智能模型有实质更新,我都要停下来仔细研究一番,LayoutLM系列就是这中间绕不开的名字。这个系列从最初把文本、布局、图像三种模态塞进同一个预训练框架开始,一步步变成了表格解析、票据识别、合同信息抽取这些场景…

📰

Event2Mind:让机器从事件文本中推理意图与反应

每次读到关于事件理解的研究,我都会习惯性地先想一个问题:机器读一句话,真的“读懂了”吗?拿“这个人被公司开除了”来说,字面上是一个解雇事件,但稍微有点常识的人都会接着想:他慌不慌&#xf…

📰

向量与矩阵范数详解:从L1/L2到谱范数的机器学习应用指南

1. 为什么我们要关心“范数”这个数学工具第一次接触“范数”这个词,很多人会觉得它离实际开发很远,像是数学系课本里才会出现的抽象概念。但如果你做过机器学习、数值计算、信号处理,甚至只是用过正则化去调一个过拟合的模型,那你…

📰

多层网络关键节点识别:超邻接矩阵与GNN实战指南

简介:这份文档面向人工智能、大模型与复杂网络分析方向的学习者和研究者,系统梳理多层网络建模与关键节点智能识别技术。内容从多层网络的定义、模型分类与应用领域切入,逐步展开关键节点的定义、重要性及常见识别方法,并深入讲解…

📰

集团企业指标体系落地指南:从指标字典到数据治理的完整路径

简介:《大型集团企业指标体系建设方案》PPT面向集团企业管理者、战略规划与运营管理岗位,是一套可直接套用的指标体系构建模板。内容从建设背景与意义切入,提出SMART指标原则,重点讲解战略目标、业务板块、运营管理、支持保障四个…

📰

Codex 自定义模型操作指南:CLI 与桌面客户端双端实操(TaoToken 统一 Key 接入)

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

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬