拓冰建站拓冰建站
首页 / 资讯中心 / 正文

MediaStore 封面查询,TaoToken 的 Key 让 Codex 先跑通 album_art

这篇记录 Android MediaStore 封面查询的验证过程核心是把album_art从查询到 decode 的每一步打出来。TaoToken 的 Key 用来让 Codex 先跑通日志排查官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。这里不把 TaoToken 当成替代 Android 工程的工具getAlbumArt、ContentResolver、BitmapFactory仍然在项目里执行TaoToken 解决的是让 Codex 能持续读代码、补日志、检查分支。原场景是用MediaStore.Audio.Media.EXTERNAL_CONTENT_URI筛IS_MUSIC再拿ALBUM_ID去 albums 表查封面。痛点是album_art路径可能为nullBitmapFactory.decodeFile容易返回空 Bitmap最后不是崩在BitmapDrawable就是静默落到默认图。先确认真实返回值再改布局返工最少。一、原问题与场景MediaStore 查 MP3 时 album_art 为 nullAndroid 本地 MP3 封面查询常见链路是查询MediaStore.Audio.Media.EXTERNAL_CONTENT_URI。用IS_MUSIC过滤非音乐文件。取每行的ALBUM_ID。用ALBUM_ID去专辑表查album_art。把album_art路径交给BitmapFactory.decodeFile。返回Drawable失败时给默认封面。这个链路在模拟器、部分 ROM、Android 10 以上设备上经常出现两种结果album_art直接是null或空字符串。album_art有路径但文件不存在或者当前应用没有读取权限。BitmapFactory.decodeFile返回null代码继续new BitmapDrawable(bm)随后出现空指针或默认图覆盖。原 excerpt 的思路是可行的但它把重点放在“拿到封面”没有把中间状态打出来。实际排障时更需要知道IS_MUSIC到底取到了什么值。ALBUM_ID是否大于 0。专辑表 cursor 是否为空。ALBUM_ART列是否存在。album_art路径是否为空。decodeFile返回值是否为null。最终走的是真实封面还是默认封面。这段验证适合让 Codex 先读现有代码再把日志点补到每一步。TaoToken 的 Key 在这里的作用是让 Codex 能稳定接入模型能力来完成代码检查封面本身仍由 Android 工程代码获取。二、TaoToken 前置Codex 的 Base URL 与 Key 怎么放先打开官网创建 Keyhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content创建后拿到YOUR_API_KEY。Codex 侧需要两个东西Base URLhttps://taotoken.net/apiAPI KeyYOUR_API_KEY注意 Base URL 填https://taotoken.net/api不要自己再拼/v1到配置里。OpenAI 兼容客户端通常会自己追加路径例如/v1/models、/v1/chat/completions或/v1/responses。如果你把 Base URL 写成https://taotoken.net/api/v1部分 Codex 版本会拼成/api/v1/v1/...直接 404。Codex 的配置通常放在~/.codex/config.tomlWindows 在用户目录下的.codex/config.toml。Key 不建议写死在仓库里用环境变量export TAOTOKEN_API_KEYYOUR_API_KEYWindows PowerShell$env:TAOTOKEN_API_KEYYOUR_API_KEY这一步只用于让 Codex 跑通验证。TaoToken 的 Key 不参与 Android 运行时不会出现在AndroidManifest.xml也不会被getAlbumArt调用。它只影响 Codex 的模型请求。三、可复制配置Codex config.toml 与 getAlbumArt 日志版3.1 Codex config.toml把下面内容按你本地 Codex 版本调整。wire_api如果版本不支持可以删掉该行或按文档改为chat、responses。# ~/.codex/config.toml model MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chatMODEL_ID填你在 TaoToken 控制台看到的可用模型 ID。配置完成后Codex 请求会走https://taotoken.net/apiKey 从TAOTOKEN_API_KEY读取。3.2 Android 侧查询日志先不要改布局。把 MediaStore 查询改成下面这种更可观察的写法private void dumpMusicAndAlbumArt(Context context) { ContentResolver resolver context.getContentResolver(); String[] projection { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.ALBUM_ID, MediaStore.Audio.Media.IS_MUSIC }; String selection MediaStore.Audio.Media.IS_MUSIC ! 0; try (Cursor cursor resolver.query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER)) { if (cursor null) { Log.w(MediaStore, music cursor is null); return; } int idIndex cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID); int titleIndex cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE); int albumIdIndex cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID); while (cursor.moveToNext()) { long id cursor.getLong(idIndex); String title cursor.getString(titleIndex); long albumId cursor.getLong(albumIdIndex); Log.d(MediaStore, id id , title title , albumId albumId); Drawable cover getAlbumArt(context, albumId); Log.d(MediaStore, albumId albumId , cover (cover ! null)); } } catch (Exception e) { Log.e(MediaStore, query music failed, e); } }getAlbumArt的日志版private Drawable getAlbumArt(Context context, long albumId) { String tag AlbumArt; if (albumId 0) { Log.w(tag, invalid albumId albumId); return defaultCover(context); } Uri albumUri ContentUris.withAppendedId( MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, albumId ); String[] projection { MediaStore.Audio.Albums.ALBUM_ART }; try (Cursor cursor context.getContentResolver().query( albumUri, projection, null, null, null)) { if (cursor null) { Log.w(tag, album cursor null, albumId albumId); return defaultCover(context); } if (!cursor.moveToFirst()) { Log.w(tag, album cursor empty, albumId albumId); return defaultCover(context); } int artIndex cursor.getColumnIndex(MediaStore.Audio.Albums.ALBUM_ART); if (artIndex 0) { Log.w(tag, ALBUM_ART column missing, albumId albumId); return defaultCover(context); } String albumArtPath cursor.getString(artIndex); Log.d(tag, albumId albumId , path albumArtPath); if (albumArtPath null || albumArtPath.trim().isEmpty()) { return defaultCover(context); } File file new File(albumArtPath); if (!file.exists()) { Log.w(tag, file not exists: albumArtPath); return defaultCover(context); } Bitmap bitmap BitmapFactory.decodeFile(albumArtPath); Log.d(tag, decodeFile result (bitmap ! null)); if (bitmap null) { return defaultCover(context); } return new BitmapDrawable(context.getResources(), bitmap); } catch (Exception e) { Log.e(tag, getAlbumArt failed, albumId albumId, e); return defaultCover(context); } } private Drawable defaultCover(Context context) { return ContextCompat.getDrawable(context, R.drawable.ablum_deflaut); }这段代码的关键是BitmapFactory.decodeFile返回null时不再继续new BitmapDrawable而是直接走默认封面。ALBUM_ART列缺失、路径为空、文件不存在、cursor 为空全部记录日志。四、验证请求与成功结果album_art 真实返回再改布局配置好 Codex 后先验证 TaoToken 的 Key 是否能通。用 curl 请求模型列表curl -sS https://taotoken.net/api/v1/models \ -H Authorization: Bearer YOUR_API_KEY成功时会返回 JSON通常包含data数组。它只验证 Key、Base URL 和模型服务是否可达不涉及 Android 封面逻辑。然后让 Codex 读 Android 代码重点检查getAlbumArtexport TAOTOKEN_API_KEYYOUR_API_KEY codex 检查这段 MediaStore 封面查询重点看 album_art 为 null、ALBUM_ART 列缺失、BitmapFactory.decodeFile 返回 null 的分支帮我补全日志Codex 正常响应后把生成的检查建议落到工程里再运行 App。成功结果看 logcat能看到MediaStore: id..., title..., albumId...。能看到AlbumArt: albumId..., path/.../...。如果封面存在能看到decodeFile resulttrue。如果封面不存在能看到pathnull或file not exists并且 UI 使用默认封面不崩溃。如果decodeFile resultfalse说明路径存在但 Bitmap 解码失败需要检查文件权限、文件是否损坏、图片格式是否被支持。只有确认album_art真实返回后才进入布局修改。否则你在 RecyclerView 或自定义 View 里调封面尺寸很容易把问题误判成布局问题。五、本篇常见错排查IS_MUSIC、cursor、BitmapFactory 与权限5.1 IS_MUSIC 不能当字符串空判断原 excerpt 里有类似cursor.getString(...).equals()的判断。IS_MUSIC是整数列常见值是 0 或 1。正确做法是int isMusic cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.IS_MUSIC)); if (isMusic 0) { continue; }更推荐直接在 query 的selection里写String selection MediaStore.Audio.Media.IS_MUSIC ! 0;这样 cursor 里已经是音乐文件循环里少一次判断。5.2 album_art 为 null 不等于查询失败很多设备上专辑表能查到行但ALBUM_ART列返回null。这不是 cursor 空而是系统没有为这张专辑生成封面路径。此时直接BitmapFactory.decodeFile(null)或new File(null)都会出问题。必须先判空再决定是否走默认图。5.3 cursor 未关闭与 moveToFirstquery返回的 cursor 一定要关闭。Java 7 可以用 try-with-resources。moveToNext之前不需要moveToFirst但如果你只取一条应该先moveToFirst并判断返回值。不要假设 cursor 一定有数据。5.4 BitmapFactory.decodeFile 返回 nulldecodeFile返回null的常见原因文件路径不存在。应用没有读取该路径的权限。图片文件损坏或不是图片。图片过大解码失败。Android 10 以上分区存储限制路径不可直接读。可以先判断file.exists()和file.canRead()再解码。解码失败就返回默认封面不要继续构造BitmapDrawable。5.5 权限与 Android 版本Android 13 及以上读音频需要uses-permission android:nameandroid.permission.READ_MEDIA_AUDIO /Android 12 及以下常见uses-permission android:nameandroid.permission.READ_EXTERNAL_STORAGE /运行时还要动态请求。没有权限时MediaStore 可能返回空 cursor 或受限列。5.6 Codex 配置常见错Base URL 填成https://taotoken.net/api/v1导致路径重复。环境变量名和env_key不一致。MODEL_ID写错Codex 请求返回模型不存在。wire_api与当前 Codex 版本不匹配。Key 未 export只在当前终端生效换终端后失效。六、语义一致 CTA把这次验证接到日常 Android 编码流这次验证的重点不是“拿一个 Key”而是让 Codex 先把 MediaStore 查询的每一步日志跑通。你需要创建 Key、配置 Base URL、检查config.toml然后让 Codex 围绕album_art、ALBUM_ART、BitmapFactory.decodeFile、getAlbumArt做分支检查。接入和排障入口在这里创建与管理 Keyhttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite如果你只是想先确认模型请求是否正常可以用模型对话做一次最小验证模型对话https://taotoken.net/console/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite如果你准备把这种查询排障、日志补全、Android 媒体库检查长期放进 Codex 或 Agent 工作流可以看 Coding PlanCoding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite回到本篇顺序仍然是先让album_art真实返回再改封面布局先让 Codex 把getAlbumArt的空值分支和日志补齐再谈 UI 效果。TaoToken 的 Key 负责让 Codex 跑通这次验证Android 工程里的ContentResolver、MediaStore 查询和BitmapFactory仍然由你的代码执行。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门