圖庫最新視頻縮略圖(含TaoToken配置))
1. 短視頻 App 搭建里 MediaStore 取最新視頻縮略圖到底難在哪做短視頻 App 搭建時(shí)首頁頂部通常要放一個(gè)「最近拍攝」的入口展示手機(jī)圖庫里最新一條視頻的縮略圖。這個(gè)需求聽起來簡單真機(jī)跑起來卻容易翻車Android 13 之后 READ_MEDIA_VIDEO 和 READ_MEDIA_IMAGES 分家老代碼直接崩MediaStore 查詢默認(rèn)按 _ID 升序拿到的往往是第一條而不是最新一條縮略圖用 MICRO_KIND 還是 MINI_KIND 分不清加載出來糊成馬賽克再疊加接口聯(lián)調(diào)時(shí) Key 散落在各個(gè)模塊調(diào)試成本翻倍。我試過在一臺(tái) Android 14 的機(jī)器上直接跑網(wǎng)上的老代碼結(jié)果 cursor 返回 null日志里全是 SecurityException。后來把權(quán)限、排序、縮略圖尺寸、接口通道四件事拆開處理才把「穩(wěn)定展示最新視頻縮略圖」這個(gè)目標(biāo)落地。這篇就按可跟做的順序把 MediaStore 查詢、縮略圖加載配置以及用 TaoToken 統(tǒng)一 Key/API 通道做接口聯(lián)調(diào)的驗(yàn)證步驟講清楚。適合誰看正在做 Android 短視頻 App、需要讀取本地圖庫最新視頻并展示縮略圖的開發(fā)者對(duì) MediaStore 權(quán)限適配、縮略圖性能、接口聯(lián)調(diào)通道有困惑的同學(xué)。核心檢索詞就是「Android MediaStore 獲取最新視頻縮略圖」下面所有代碼和配置都圍繞它展開。先說清楚 MediaStore 能做什么它是 Android 系統(tǒng)提供的媒體庫訪問入口視頻、圖片、音頻都通過 ContentResolver 查詢。縮略圖有兩種模式KIND 字段區(qū)分 MICRO_KIND 和 MINI_KIND前者分辨率更低、內(nèi)存占用小適合列表后者清晰一些適合大圖預(yù)覽。查詢照片時(shí)把 Video 換成 Images 即可Uri 結(jié)構(gòu)一致。理解這一點(diǎn)后面的排序和權(quán)限適配才有落腳點(diǎn)。2. TaoToken 前置準(zhǔn)備統(tǒng)一 Key 與 API 通道短視頻 App 搭建到聯(lián)調(diào)階段通常會(huì)遇到一個(gè)現(xiàn)實(shí)問題縮略圖本地能顯示了但「最新視頻」還要上報(bào)給后端、拉取封面審核結(jié)果、或者調(diào)用模型做內(nèi)容理解這時(shí)候接口的 Base URL、Key、Model ID 散落在各個(gè) Activity 里改一次環(huán)境要翻半天。TaoToken 在這里的作用是把這些通道統(tǒng)一起來一個(gè) Key 走完對(duì)話、編碼、Agent 類接口減少聯(lián)調(diào)時(shí)的變量。你需要先拿到 Key。打開官網(wǎng) https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注冊(cè)后在控制臺(tái)創(chuàng)建 API Key??刂婆_(tái)地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content Key 管理頁在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。API 基礎(chǔ)地址統(tǒng)一用 https://taotoken.net/api 注意這個(gè)地址不加 UTM 參數(shù)直接作為 Base URL 填進(jìn)配置。如果你只是想在聯(lián)調(diào)前驗(yàn)證模型通道是否通可以用模型對(duì)話頁 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 先發(fā)一條測(cè)試消息。長期做編碼和 Agent 任務(wù)的話Coding Plan 頁面在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 接入文檔在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。Claude Code 相關(guān)的接入說明在 https://taotoken.net/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。這里要強(qiáng)調(diào)一個(gè)原則TaoToken 是接口通道不是編輯器替代品也不要用它直連生產(chǎn)數(shù)據(jù)庫。它的定位是讓你在短視頻 App 聯(lián)調(diào)時(shí)把「本地縮略圖展示」和「遠(yuǎn)端接口調(diào)用」兩條線分開本地用 MediaStore遠(yuǎn)端用統(tǒng)一 Key 走 API互不干擾。拿到 Key 后先別急著寫進(jìn)代碼下一步我們用配置文件把它管起來。3. 可復(fù)制配置MediaStore 查詢 縮略圖加載 通道配置這一節(jié)是全文的核心分三塊MediaStore 查詢最新視頻、縮略圖加載、以及 TaoToken 的配置文件片段。路徑和原文保持一致直接復(fù)制即可。3.1 MediaStore 查詢最新一條視頻關(guān)鍵點(diǎn)是排序。MediaStore 默認(rèn)按 _ID 升序要拿最新一條用 DATE_ADDED 或 DATE_TAKEN 降序并且 limit 1。Android 10 之后推薦用 Bundle 傳 queryArgs兼容性更好。// VideoRepository.kt fun queryLatestVideo(context: Context): Uri? { val collection MediaStore.Video.Media.EXTERNAL_CONTENT_URI val projection arrayOf( MediaStore.Video.Media._ID, MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DATE_ADDED ) val selection ${MediaStore.Video.Media.DATE_ADDED} ? val selectionArgs arrayOf(0) val sortOrder ${MediaStore.Video.Media.DATE_ADDED} DESC val queryArgs Bundle().apply { putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, arrayOf(MediaStore.Video.Media.DATE_ADDED)) putInt(ContentResolver.QUERY_ARG_SORT_DIRECTION, ContentResolver.QUERY_SORT_DIRECTION_DESCENDING) putInt(ContentResolver.QUERY_ARG_LIMIT, 1) } context.contentResolver.query( collection, projection, queryArgs, null )?.use { cursor - if (cursor.moveToFirst()) { val id cursor.getLong( cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)) return ContentUris.withAppendedId(collection, id) } } return null }如果你要兼容 Android 9 及以下把 queryArgs 換成傳統(tǒng)的 selection/sortOrder 重載即可邏輯一致。查詢照片時(shí)把MediaStore.Video.Media全部替換為MediaStore.Images.MediaUri 換成EXTERNAL_CONTENT_URI的 Images 版本。3.2 縮略圖加載配置拿到 Uri 后用 loadThumbnail 生成縮略圖Android 10 之后推薦這個(gè)方法內(nèi)部會(huì)處理尺寸。老設(shè)備用 Thumbnails.getThumbnailKIND 選 MICRO_KIND 省內(nèi)存。// ThumbnailLoader.kt fun loadThumbnail(context: Context, uri: Uri, size: Size): Bitmap? { return if (Build.VERSION.SDK_INT Build.VERSION_CODES.Q) { context.contentResolver.loadThumbnail(uri, size, null) } else { val id ContentUris.parseId(uri) MediaStore.Video.Thumbnails.getThumbnail( context.contentResolver, id, MediaStore.Video.Thumbnails.MICRO_KIND, null ) } }尺寸建議列表用 200x200大圖預(yù)覽用 512x512。MICRO_KIND 分辨率更低適合 RecyclerView 復(fù)用MINI_KIND 清晰但內(nèi)存占用高別在長列表里用。3.3 TaoToken 通道配置片段把 Key 和 Base URL 放進(jìn)local.properties或gradle.properties不要硬編碼進(jìn) Kotlin。下面是一個(gè) settings 風(fēng)格的 JSON 片段路徑按你項(xiàng)目實(shí)際調(diào)整。{ taotoken: { baseUrl: https://taotoken.net/api, apiKey: sk-你的Key, modelId: 你的模型ID, timeoutMs: 30000 } }如果你用 Codex 的 auth.json結(jié)構(gòu)類似{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model: 你的模型ID }三件套記牢Base URL 用https://taotoken.net/apiKey 從 api-keys 頁面拿Model ID 按你控制臺(tái)里開通的填。Cline MCP 或 CC Switch 場景下同樣把這三項(xiàng)填進(jìn)對(duì)應(yīng)配置不要只填 Key 漏掉 Base URL。4. 驗(yàn)證請(qǐng)求與成功結(jié)果配置寫完先驗(yàn)證本地縮略圖再驗(yàn)證遠(yuǎn)端通道兩條線都通了才算穩(wěn)。本地驗(yàn)證在 Activity 里調(diào)用 queryLatestVideo把返回的 Uri 傳給 loadThumbnail設(shè)置到 ImageView。真機(jī)上打開 App如果圖庫里有視頻應(yīng)該能看到最新一條的縮略圖。日志里打印 Uri 和 Bitmap 尺寸確認(rèn)不是 null。lifecycleScope.launch(Dispatchers.IO) { val uri queryLatestVideo(thisMainActivity) val bitmap uri?.let { loadThumbnail(thisMainActivity, it, Size(200, 200)) } withContext(Dispatchers.Main) { findViewByIdImageView(R.id.ivLatest).setImageBitmap(bitmap) } }遠(yuǎn)端驗(yàn)證用 curl 或 Postman 打一次 TaoToken 的接口確認(rèn) Key 和 Base URL 生效。curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的Key \ -H Content-Type: application/json \ -d { model: 你的模型ID, messages: [{role: user, content: ping}] }成功的話返回 JSON 里會(huì)有 choices 字段內(nèi)容正常。如果返回 401說明 Key 不對(duì)如果報(bào) local proxy failed檢查 Base URL 是不是寫成了帶 UTM 的地址正確寫法是https://taotoken.net/api不帶參數(shù)。如果報(bào) reading choices 相關(guān)錯(cuò)誤多半是響應(yīng)結(jié)構(gòu)解析問題確認(rèn)你用的是標(biāo)準(zhǔn) chat completions 格式。實(shí)測(cè)下來本地縮略圖和遠(yuǎn)端通道分開驗(yàn)證排障效率高很多。先保證 MediaStore 查詢返回非空再保證接口返回 200最后把兩者串起來做業(yè)務(wù)邏輯。5. 本篇常見錯(cuò)排查這一節(jié)按真實(shí)報(bào)錯(cuò)來對(duì)照著查。401 UnauthorizedKey 錯(cuò)誤或沒帶 Authorization 頭。檢查 api-keys 頁面復(fù)制的 Key 是否完整Bearer 后面有沒有空格。TaoToken 的 Key 只在創(chuàng)建時(shí)顯示一次丟了就重新建一個(gè)。local proxy failedBase URL 寫錯(cuò)。常見錯(cuò)誤是復(fù)制了帶 UTM 的官網(wǎng)地址當(dāng) API 地址。API 地址固定為https://taotoken.net/api不加任何查詢參數(shù)。如果你在 CC Switch 或 Cline MCP 里配置同樣只填這個(gè)。reading choices 報(bào)錯(cuò)響應(yīng)解析失敗。確認(rèn)請(qǐng)求體是標(biāo)準(zhǔn) chat completions 格式model 字段填的是你控制臺(tái)開通的 Model ID不是隨便寫的名字。如果用的是 Codex auth.json檢查字段名是 base_url 還是 baseUrl不同工具大小寫敏感。OAuth 相關(guān)報(bào)錯(cuò)多見于 Claude Code 接入場景。如果你走的是 ClaudeCodeAnthropic 通道確認(rèn)接入方式選對(duì)不要混用 OAuth 和 API Key 兩種模式。文檔在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 有對(duì)應(yīng)說明。MediaStore 返回 null權(quán)限沒給。Android 13 及以上要?jiǎng)討B(tài)申請(qǐng) READ_MEDIA_VIDEOAndroid 12 及以下用 READ_EXTERNAL_STORAGE。別只在 Manifest 里聲明運(yùn)行時(shí)也要請(qǐng)求。另外查詢時(shí)如果圖庫里確實(shí)沒有視頻返回 null 是正常的做好空態(tài)處理??s略圖模糊KIND 選錯(cuò)。列表用 MICRO_KIND預(yù)覽用 MINI_KIND。如果還是糊檢查傳入的 Size 是不是太小200x200 在低密度屏上會(huì)拉伸。cursor 泄漏query 返回的 cursor 必須 close用 use 塊包起來。上面代碼已經(jīng)處理別自己改成手動(dòng) close 漏掉異常分支。6. 接入與排障通道把上面幾步跑通后短視頻 App 的「最新視頻縮略圖」基本就穩(wěn)了。本地這條線靠 MediaStore 查詢加縮略圖加載遠(yuǎn)端這條線靠 TaoToken 統(tǒng)一 Key 和 Base URL。兩條線分開調(diào)試出問題好定位。如果你在接入過程中卡在 Key 或通道配置直接去 API Keys 頁面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 重新拿一個(gè)再對(duì)照接入文檔 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 檢查字段。想先驗(yàn)證模型通道是否通用模型對(duì)話頁 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 發(fā)一條消息最快。長期做編碼和 Agent 任務(wù)的話Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 更適合。最后留一個(gè)實(shí)用技巧把 queryLatestVideo 和 loadThumbnail 封裝成 Repository加一層內(nèi)存緩存避免每次 onResume 都重新查詢。真機(jī)上頻繁查詢 MediaStore 會(huì)有明顯卡頓緩存后體驗(yàn)順很多??s略圖展示這塊穩(wěn)定比花哨重要。