更新记录

1.1.2(2026-08-31)

修复

  • 修复部分 Android 设备忽略 MediaStore 查询排序,导致媒体列表未按最新到最旧显示的问题

1.1.1(2026-08-31)

修复

  • 修复 Android 缩略图线程池任务中错误使用 return@Thread 导致的 Kotlin 编译失败
  • 移除未使用的 AppCompat Activity 和主题依赖,改用 Android 系统 Activity 与 Material 主题
  • 修复原生布局仍引用 AppCompat 点击反馈属性,导致媒体选择器启动时 InflateException 崩溃的问题
  • 修正 Android config.json 中 minSdkVersion 的数据类型

1.1.0(2026-08-28)

重写

  • 公共 API、结果结构、导入路径和 9201001–9201008 错误码保持兼容
  • Android/iOS 桥接层增加线程安全的一次性结算,避免生命周期竞态导致重复回调
  • 视频时长改为按系统提供的小数秒精度校验和返回,不再向下截断
  • 移除用户可感知的 60 条分页:Android 一次读取轻量 MediaStore 索引,iOS 直接以完整 PHFetchResult 作为随机访问数据源
  • RecyclerView/UICollectionView 仅创建可视区域及少量预取 Cell,列表和全屏预览共用惰性随机访问数据源,避免复制完整相册
  • Android 缩略图改用固定 3 线程执行器,并按 Bitmap 实际字节限制 LRU 缓存,降低快速滚动和大相册下的 OOM 风险
  • iOS 媒体项只为可视、预取和相邻预览位置按需构造,离屏请求继续及时取消
  • iOS 预览返回不再重新查询相册;仅在 PHFetchResultChangeDetails 确认结果变化时无加载态应用新结果,并保留当前滚动位置
  • 媒体网格由固定四列改为 4–8 列响应式布局,补齐首屏加载、空状态和深色模式占位色
  • 双端选择控件使用至少 44dp/pt 点击范围,并补充可访问名称和选中状态
  • Android 图片预览增加双击缩放、平移和视频封面占位;iOS 图片预览增加双击缩放
  • 导出采用原文件流式复制、分段进度、空间安全余量、临时文件原子提交和失败会话回滚
  • 缓存会话与双端桥接状态增加并发保护,权限范围变化时清除已不可访问的选择项
查看更多

平台兼容性

uni-app x(4.76)

Chrome Safari Android Android插件版本 iOS iOS插件版本 鸿蒙 微信小程序
- - 13.0 1.1.2 12 1.1.2 - -

其他

多语言 暗黑模式 宽屏模式 蒸汽模式
× × × √

dyl-media-picker

面向 uni-app x 的 Android / iOS 原生全屏图片、视频选择器。

通过一次 API 调用打开原生媒体选择界面,支持图片、视频或混合选择,并提供分类筛选、虚拟列表、全屏预览、数量与文件限制、原文件导出和缓存清理能力。

功能特性

  • 图片、视频单选或混合多选,最多可选 99 个文件
  • 全部、图片、视频三个分类 Tab,支持指定初始 Tab
  • 手机竖屏默认四列,横屏和平板按宽度扩展到最多八列;列表一次呈现完整逻辑结果,原生 Cell 仅渲染可视区域及少量预取区域
  • Android 一次查询轻量 MediaStore 索引,缩略图使用固定并发线程池按需加载;iOS 保留 PHFetchResult 并按索引访问,不构造完整媒体数组
  • 按选择顺序编号,确认后按选择顺序返回结果
  • 支持视频时长、文件大小和选择数量限制
  • 图片缩放预览、视频播放和暂停控制
  • iOS 预览采用相邻页面缓存、缩略图到高清图渐进加载和 PhotoKit 预热,降低快速滑动时的白屏、黑屏概率
  • 视频预览使用封面占位,媒体内容在可用区域内居中;Android 暂停时保留控制栏,iOS 使用系统播放器控制栏
  • 支持 iCloud 媒体下载和原文件导出进度提示
  • 结果统一导出为本地绝对路径,不向业务层暴露 content:// 或 ph:// 地址
  • 自动清理过期缓存,也支持业务层主动清理

平台与环境

项目 支持范围
应用框架 仅 uni-app x App
HBuilderX 4.76 及以上
Android Android 5.0 / API 21 及以上
iOS iOS 12.0 及以上

插件直接弹出原生页面,不需要在 pages.json 中注册插件页面。

如果同一个业务页面还需要编译到 Web 或小程序,请使用条件编译,只在 App 端导入和调用本插件。

接入与自定义基座

本插件包含 Kotlin、Swift、原生依赖、系统 Framework 和权限配置。以下情况需要重新制作自定义调试基座:

  • 首次将 dyl-media-picker 加入项目
  • 修改了 utssdk/app-android 或 utssdk/app-ios 下的代码或配置
  • 升级插件后,新版本包含原生实现或原生依赖变更
  • 调整了 Android 权限、目标 SDK,或 iOS Info.plist、Framework 配置

只修改业务页面中的调用参数、回调处理或界面逻辑,不需要因为这些改动重新制作基座。

Android 和 iOS 基座需要分别构建。调试时请确认运行的是包含当前插件版本的最新基座;旧基座不会动态获得新加入的原生代码。

如果项目曾使用旧目录 jx-media-picker,请直接替换为 dyl-media-picker,不要同时保留两份插件;同时更新所有 import 后重新制作自定义基座。新版本使用独立的 dyl-media-picker 缓存目录,旧缓存由系统缓存策略回收,也可以在升级前主动清理。

快速开始

import {
  chooseMedia,
  type ChooseMediaResult,
  type ChooseMediaFail
} from '@/uni_modules/dyl-media-picker'

const openMediaPicker = () : void => {
  chooseMedia({
    count: 9,
    mediaType: ['image', 'video'],
    initialTab: 'all',
    success: (result : ChooseMediaResult) : void => {
      console.log('已选择:', result.tempFiles.length)

      if (result.tempFiles.length > 0) {
        const firstFile = result.tempFiles[0]
        console.log(firstFile.tempFilePath)
      }
    },
    fail: (error : ChooseMediaFail) : void => {
      console.log(error.errCode, error.errMsg)
    },
    complete: () : void => {
      console.log('选择流程结束')
    }
  })
}

chooseMedia 使用回调 API,不返回 Promise:

  • 选择并成功导出全部文件时调用 success
  • 用户返回或主动取消时调用 fail,错误码为 9201006
  • 其他权限、参数、初始化或导出错误也调用 fail
  • 无论成功或失败,最后都只调用一次 complete
  • success 与 fail 互斥,不会在同一次调用中同时触发

常用配置

只选择图片

chooseMedia({
  count: 9,
  mediaType: ['image'],
  initialTab: 'image',
  success: (result) : void => {
    console.log(result.tempFiles)
  }
})

只选择视频

chooseMedia({
  count: 1,
  mediaType: ['video'],
  initialTab: 'video',
  maxVideoDuration: 60,
  maxFileSize: 200 * 1024 * 1024,
  success: (result) : void => {
    console.log(result.tempFiles)
  }
})

图片和视频混选

chooseMedia({
  count: 9,
  mediaType: ['image', 'video'],
  initialTab: 'all',
  title: '选择素材',
  accentColor: '#00C853',
  success: (result) : void => {
    console.log(result.tempFiles)
  }
})

API

chooseMedia(options)

打开原生媒体选择器。同一时间只允许存在一个选择器实例,重复调用会返回 9201002。

参数 类型 默认值 说明
count number 9 最大选择数量,必须是 1–99 的整数
mediaType MediaPickerMediaType[] ['image', 'video'] 可选择的媒体类型,数组不得为空
initialTab MediaPickerTab 'all' 初始 Tab:'all'、'image' 或 'video'
maxVideoDuration number 0 视频最大时长,单位为秒;0 表示不限制,必须是非负整数
maxFileSize number 0 单个文件最大大小,单位为字节;0 表示不限制,必须是非负整数
title string '相册' 原生选择页标题
accentColor string '#00C853' 主题色,支持 #RRGGBB 或 #AARRGGBB
success (result: ChooseMediaResult) => void - 文件全部导出成功回调
fail (error: ChooseMediaFail) => void - 取消或失败回调
complete () => void - 流程结束回调

MediaPickerMediaType 可取:

type MediaPickerMediaType = 'image' | 'video'

MediaPickerTab 可取:

type MediaPickerTab = 'all' | 'image' | 'video'

如果 initialTab 与 mediaType 冲突,插件会回退到当前允许的媒体分类。例如只允许视频时,初始页最终会使用视频分类。

ChooseMediaResult

type ChooseMediaResult = {
  tempFiles: MediaPickerFile[]
}

结果数组保持用户的选择顺序。确认后插件先将选中的系统资源导出到插件缓存,全部成功后再一次性返回。

MediaPickerFile

字段 类型 说明
identifier string 系统媒体资源标识,用于插件内部定位和去重;受平台权限与资源生命周期约束,不应作为文件地址使用
type 'image' \| 'video' 媒体类型
tempFilePath string 导出的原文件本地绝对路径
thumbnailPath string 导出的缩略图本地绝对路径
name string 原始文件名
mimeType string MIME 类型
size number 原文件大小,单位为字节
width number 媒体宽度,单位为像素
height number 媒体高度,单位为像素
duration number 视频时长,单位为秒并保留系统可用的小数精度;图片固定为 0
createTime number 媒体创建时间,毫秒时间戳

tempFilePath 和 thumbnailPath 是普通本地绝对路径,可用于文件读取、上传和后续原生处理。它们属于插件缓存文件,不是永久文件。

ChooseMediaFail

interface ChooseMediaFail extends IUniError {
  errCode: MediaPickerErrorCode
  identifier: string | null
}

当某个具体媒体导出失败时,identifier 可能携带对应资源标识;没有明确资源时为 null。

clearMediaPickerCache(options)

清理插件缓存目录中超过 24 小时的旧会话。

import {
  clearMediaPickerCache,
  type ClearMediaPickerCacheResult,
  type ChooseMediaFail
} from '@/uni_modules/dyl-media-picker'

const clearPickerCache = () : void => {
  clearMediaPickerCache({
    success: (result : ClearMediaPickerCacheResult) : void => {
      console.log('删除文件数:', result.removedFileCount)
      console.log('释放字节数:', result.freedSize)
    },
    fail: (error : ChooseMediaFail) : void => {
      console.log(error.errCode, error.errMsg)
    }
  })
}
返回字段 类型 说明
removedFileCount number 本次删除的文件数量
freedSize number 本次释放的空间,单位为字节

选择器打开期间不要清理缓存;此时清理请求会失败并返回 9201008。

预览与导出行为

图片预览

  • 支持全屏查看和缩放
  • iOS 优先显示可用缩略图,再渐进加载高清图
  • 当前页和相邻页会被缓存与预热,快速左右滑动时不必每次重新创建全部媒体视图
  • 系统内存紧张时可能主动回收非当前页缓存,插件会在再次进入该页时重新加载

视频预览

  • 使用视频封面作为播放器准备完成前的占位内容
  • 媒体按比例在预览区域内居中显示
  • Android 暂停时控制栏保持可见,便于继续播放或拖动进度
  • iOS 使用 AVPlayerViewController 的系统播放控制栏
  • 离开当前预览页时会暂停视频,避免多个视频同时播放

iCloud 媒体

iOS 素材只存在于 iCloud 时,高清预览或确认导出需要联网下载。下载期间可能先显示缩略图或视频封面;网络速度会直接影响高清内容和播放器的就绪时间。

maxFileSize 在系统能提前获取文件大小时会在选择阶段校验。对于 iCloud 等大小暂不可知的资源,最终以导出阶段的校验结果为准。

权限说明

Android

  • API 21–22:无需运行时授权
  • API 23–32:运行时申请 READ_EXTERNAL_STORAGE
  • API 33+:根据 mediaType 申请 READ_MEDIA_IMAGES、READ_MEDIA_VIDEO
  • API 34+:支持 READ_MEDIA_VISUAL_USER_SELECTED 部分媒体访问
  • 要完整使用 Android 14 部分媒体访问,宿主 targetSdkVersion 应不低于 34
  • 插件不会覆盖宿主工程的目标 SDK 配置

用户拒绝权限或当前授权状态不可用时,插件返回 9201003。

iOS

  • 使用 PhotoKit 读取系统相册
  • iOS 14+ 支持 limited Photos 权限,并只展示当前被授权的素材
  • 插件声明 NSPhotoLibraryUsageDescription,宿主可以根据产品文案覆盖该字段
  • 原生配置依赖 Photos、PhotosUI、UIKit、AVFoundation 和 AVKit 等系统 Framework

权限或 Framework 配置发生变化后,需要重新制作 iOS 自定义基座。

缓存生命周期

  • 导出目录:<application-cache>/dyl-media-picker/<session-id>/
  • 每次打开选择器时自动删除超过 24 小时的旧会话
  • 调用 clearMediaPickerCache 时同样只清理超过 24 小时的旧会话
  • 操作系统仍可能因空间压力提前回收缓存
  • 需要长期保留的文件,应在成功回调中尽快上传或复制到业务持久目录
  • 不要把缓存路径长期写入数据库后直接复用

错误码

错误码 含义 常见原因
9201001 参数不合法 数量越界、媒体类型为空、颜色格式错误或限制值不是非负整数
9201002 选择器已打开 上一次选择流程尚未结束,又调用了 chooseMedia
9201003 相册权限不足 用户拒绝权限,或当前授权状态不可用
9201004 选择器初始化失败 原生页面、数据源或宿主环境初始化异常
9201005 原文件导出失败 系统资源已删除、iCloud 下载失败或文件复制失败
9201006 用户取消选择 用户从选择器主界面返回或关闭选择器
9201007 缓存空间不足 导出目录无法创建、写入或设备可用空间不足
9201008 缓存清理失败 选择器正在使用缓存,或目录删除失败

业务层通常应将 9201006 作为正常取消处理,不必展示错误提示。

常见问题

加入插件后运行没有效果,或提示找不到原生类

当前运行的自定义基座没有包含插件。重新制作对应平台的自定义基座,并确认设备安装的是新基座。

iOS 云打包能通过,但旧基座仍然表现为修改前的版本

UTS 原生插件代码会编译进基座。仅替换业务资源不会更新 Swift 实现,需要重新制作并安装 iOS 基座。

Android 14 只能看到部分照片

这是系统的部分媒体授权行为。确认宿主目标 SDK 不低于 34,并在授权流程中处理 READ_MEDIA_VISUAL_USER_SELECTED。

iOS 预览 iCloud 视频时短暂显示封面

播放器需要等待系统返回可播放的 AVAsset。插件会保留视频封面,资源下载完成后再切换到播放器;请确认设备网络可用。

成功回调中的路径过一段时间失效

返回的是插件缓存路径。需要持久保存时,请在成功回调后立即复制到业务目录或完成上传。

如何避免数组越界

UTS 在 Android 和 iOS 原生端访问越界数组会抛出异常。读取 tempFiles[index] 前应显式检查:

if (index >= 0 && index < result.tempFiles.length) {
  const file = result.tempFiles[index]
  console.log(file.tempFilePath)
}

调试与验收

当前项目提供了调用示例页面:媒体选择器调试页。

完整的自动检查、真机测试矩阵和发布前清单见:TESTING.md。

建议至少在以下场景进行真机回归:

  • 首次授权、拒绝后重试、iOS limited Photos、Android 14 部分媒体权限
  • 图片、视频、混合选择,以及数量、时长、大小限制
  • 连续快速左右滑动图片和视频,并多次滑回同一素材
  • 从预览页返回列表,确认列表位置、选中状态和缩略图不闪烁
  • iCloud 原图和视频的弱网下载、取消与失败
  • 导出大文件时的进度、空间不足和缓存清理

从 uni.chooseMedia 迁移

// 旧代码
uni.chooseMedia({
  count: 9,
  mediaType: ['image', 'video'],
  success: (result) : void => {
    console.log(result.tempFiles)
  }
})

// 新代码
import { chooseMedia } from '@/uni_modules/dyl-media-picker'

chooseMedia({
  count: 9,
  mediaType: ['image', 'video'],
  success: (result) : void => {
    // 返回原文件、缩略图、尺寸、时长、MIME 和创建时间等完整信息
    console.log(result.tempFiles)
  }
})

迁移后需要注意:

  • 本插件只支持 uni-app x App
  • 首次接入必须重新制作 Android / iOS 自定义基座
  • 用户取消会进入 fail,错误码为 9201006
  • 返回路径位于插件缓存,长期使用前需要持久化
  • iCloud 素材可能在确认后继续下载和导出

隐私、权限声明

1. 本插件需要申请的系统权限列表:

相册已内置

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

无

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无

暂无用户评论。