更新记录

1.0.0(2026-09-22)

首次发布,支持 Android 平台(最低 SDK 21,需设备支持 USB Host / OTG)。


平台兼容性

uni-app(4.72)

Vue2 Vue2插件版本 Vue3 Vue3插件版本 Chrome Safari app-vue app-vue插件版本 app-nvue app-nvue插件版本 Android Android插件版本 iOS 鸿蒙
1.0.0 1.0.0 × × 1.0.0 1.0.0 5.0 1.0.0 × ×
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
× × × × × × × × × - × ×

uni-app x(4.72)

Chrome Safari Android Android插件版本 iOS 鸿蒙 微信小程序
× × 5.0 1.0.0 × × ×

zy-usb-drive

U 盘读写 UTS 插件(Android)。在支持 USB Host(OTG)的 Android 设备上连接 U 盘、读卡器、移动硬盘, 完成设备枚举、USB 授权、目录浏览、文本 / 二进制文件读写,以及本地文件与 U 盘文件的双向复制。

目录

特性

  • 设备管理:枚举 USB 存储设备;自动申请系统 USB 授权,授权通过后自动继续初始化,无需二次点击
  • 插拔监听:持续监听 U 盘插入 / 拔出事件
  • 容量信息:获取总容量、簇大小、卷标;可选统计已用 / 剩余空间
  • 目录浏览:按目录逐级浏览,自动过滤 System Volume InformationLOST.DIR 等系统目录
  • 文件读写:文本文件(UTF-8)与二进制文件(Base64)读写,文件不存在时自动创建
  • 双向复制:本地文件 → U 盘、U 盘 → 本地,支持大文件分块传输与 onProgress 进度回调
  • 目录与删除:创建多级目录、删除文件或目录
  • 权限辅助:Android 11+ 的「所有文件访问权限」检查,无权限时自动跳转系统设置页
  • 统一回调:全部 API 均支持 success / fail / complete,失败为 UniError

平台支持

平台 支持
Android
iOS
Harmony
H5
小程序

兼容性

项目 要求
最低系统版本 Android 5.0(API 21)
硬件 设备须支持 USB Host / OTG,并能给外设供电
支持的指令集 armeabi-v7aarm64-v8a
支持的 U 盘格式 FAT32
支持的宿主 uni-app(vue2 / vue3)、uni-app x

不支持 iOS:系统未开放直接访问 U 盘文件系统的能力。

安装与引入

插件目录名 / 插件 id 为 zy-usb-drive,通过 uni_modules 引入:

import {
    checkAllFilesAccessPermission,
    getUsbDriveDeviceList,
    initUsbStorage,
    registerUsbStateReceiver,
    getUsbFileList,
    readTextFile,
    readBinaryFile,
    writeTextFile,
    writeBinaryFile,
    copyLocalFileToUsb,
    copyUsbFileToLocal,
    createUsbDirectory,
    deleteUsbFile,
    releaseResources
} from '@/uni_modules/zy-usb-drive'

快速开始

完整流程:检查权限 → 监听插拔 → 枚举设备 → 初始化 → 浏览文件 → 读写 / 复制 → 释放资源。

import {
    checkAllFilesAccessPermission,
    getUsbDriveDeviceList,
    initUsbStorage,
    registerUsbStateReceiver,
    getUsbFileList,
    readTextFile,
    writeTextFile,
    releaseResources
} from '@/uni_modules/zy-usb-drive'

export default {
    data() {
        return {
            currentPath: '/',
            files: [],
            initialized: false
        }
    },
    onLoad() {
        // 1. 注册插拔监听(onEvent 可多次回调,注册结果走 success / fail)
        registerUsbStateReceiver({
            onEvent: (e) => {
                if (e.event === 'deviceAttached') {
                    console.log('U 盘已插入:', e.deviceName)
                    this.loadDevices()
                } else if (e.event === 'deviceDetached') {
                    console.log('U 盘已拔出:', e.deviceName)
                    this.initialized = false
                }
            },
            success: (res) => console.log(res.errMsg),
            fail: (err) => console.error('监听注册失败', err.errCode, err.errMsg)
        })

        // 2. Android 11+ 推荐先确认「所有文件访问权限」(复制本地文件时需要)
        checkAllFilesAccessPermission({
            success: (res) => console.log('所有文件访问权限:', res.hasPermission)
        })

        this.loadDevices()
    },
    onUnload() {
        // 3. 页面卸载时释放资源
        releaseResources()
    },
    methods: {
        loadDevices() {
            getUsbDriveDeviceList({
                success: (res) => {
                    if (res.devices.length === 0) {
                        console.log('未检测到 USB 存储设备')
                        return
                    }
                    console.log('设备列表:', res.devices)
                    this.initStorage(res.devices[0].index)
                },
                fail: (err) => console.error(err.errCode, err.errMsg)
            })
        },
        initStorage(deviceIndex) {
            // 无权限时会自动弹出系统 USB 授权框,用户允许后自动继续初始化
            initUsbStorage({
                deviceIndex: deviceIndex,
                statCapacity: true, // 需要剩余 / 已用空间时开启,默认 false 不统计
                success: (info) => {
                    this.initialized = true
                    console.log('容量:', info.capacity, '剩余:', info.freeSpace, '是否已统计:', info.capacityStat)
                    this.loadFiles()
                },
                fail: (err) => console.error(`初始化失败 [${err.errCode}] ${err.errMsg}`)
            })
        },
        loadFiles() {
            getUsbFileList({
                path: this.currentPath,
                success: (res) => {
                    this.files = res.files
                    console.log(`共 ${res.files.length} 项`)
                },
                fail: (err) => console.error(err.errCode, err.errMsg)
            })
        },
        readFile(file) {
            readTextFile({
                filePath: file.path,
                success: (res) => console.log('文件内容:', res.content),
                fail: (err) => console.error(err.errCode, err.errMsg)
            })
        },
        writeDemoFile() {
            writeTextFile({
                filePath: this.currentPath === '/' ? '/test.txt' : `${this.currentPath}/test.txt`,
                content: 'hello zy-usb-drive',
                success: (res) => {
                    console.log(res.errMsg)
                    this.loadFiles()
                },
                fail: (err) => console.error(err.errCode, err.errMsg)
            })
        }
    }
}

API 一览

API 说明 是否持续回调
checkAllFilesAccessPermission 检查「所有文件访问权限」,无权限时跳转设置页
getUsbDriveDeviceList 枚举 USB 存储设备
initUsbStorage 初始化设备(含自动 USB 授权)
registerUsbStateReceiver 注册 U 盘插拔监听 是(onEvent
getUsbFileList 获取目录下的文件列表
readTextFile 读取文本文件
readBinaryFile 读取二进制文件(Base64)
writeTextFile 写入文本文件
writeBinaryFile 写入二进制文件
copyLocalFileToUsb 本地文件复制到 U 盘 是(onProgress
copyUsbFileToLocal U 盘文件复制到本地 是(onProgress
createUsbDirectory 创建目录(支持多级)
deleteUsbFile 删除文件或目录
releaseResources 释放资源

通用约定:

  • 所有 options 均为可选对象(除标注必填字段的 API);
  • 回调:success(res) 成功、fail(err) 失败(errUniError,含 errCode / errMsg)、complete(res) 无论成败都会执行;
  • 所有路径均以 U 盘根目录 / 为基准,例如 /test.txt/photos/1.jpg
  • 路径可不写前导 /,插件会自动补全,重复的 / 与结尾 / 会被规范化。

API 详解

checkAllFilesAccessPermission(options?)

检查 Android 11+ 的「所有文件访问权限」(复制 / 读取公共目录下的本地文件时需要)。 无权限时插件会自动跳转到系统授权页,用户授权后返回应用再次调用本接口即可确认。

参数:无(options 可省略)

success 回调

字段 类型 说明
hasPermission boolean 是否已拥有「所有文件访问权限」
isAndroidROrHigher boolean 是否运行在 Android 11(API 30)及以上
jumpedToSettings boolean 是否已自动跳转到系统授权页
checkAllFilesAccessPermission({
    success: (res) => {
        if (!res.hasPermission && res.jumpedToSettings) {
            uni.showToast({ title: '请在系统设置中开启「所有文件访问权限」', icon: 'none' })
        }
    }
})

getUsbDriveDeviceList(options?)

枚举当前已连接的 USB 存储设备。返回的 index 是后续 initUsbStorage 需要的下标。

参数:无(options 可省略)

success 回调{ devices: UsbDriveDeviceInfo[] },字段见数据类型

getUsbDriveDeviceList({
    success: (res) => {
        res.devices.forEach((d, i) => {
            console.log(i, d.deviceName, d.productName, `VID:${d.vendorId} PID:${d.productId}`)
        })
    },
    fail: (err) => console.error(err.errCode, err.errMsg)
})

需要在调用 initUsbStorage 之前执行。若设备列表在插拔后发生变化,重新调用本接口刷新即可。

initUsbStorage(options)

初始化指定设备,成功后即可进行文件浏览与读写。

参数

参数 类型 必填 默认值 说明
deviceIndex number - 设备在 getUsbDriveDeviceList 返回列表中的 index
statCapacity boolean false 是否统计剩余 / 已用空间

success 回调UsbDriveStorageInfo):

字段 类型 说明
capacity number 总容量(字节),任何情况下都准确
chunkSize number 簇大小(字节),任何情况下都准确
volumeLabel string 卷标,读取不到时为空字符串
freeSpace number 剩余空间(字节);capacityStatfalse 时为 0
occupiedSpace number 已用空间(字节);capacityStatfalse 时为 0
capacityStat boolean 本次是否已统计剩余 / 已用空间

关于 statCapacity

统计剩余 / 已用空间需要额外读取磁盘信息,容量越大耗时越明显,因此默认不统计

取值 行为
false 初始化更快;freeSpace / occupiedSpace 恒为 0capacityStatfalse。目录浏览与文件读写不受影响
true 返回准确的 freeSpace / occupiedSpacecapacityStattrue
// 只做文件读写,不需要容量信息 —— 用默认值即可
initUsbStorage({
    deviceIndex: 0,
    success: (info) => console.log('容量:', info.capacity, '簇大小:', info.chunkSize)
})

// 需要展示容量占用 —— 开启统计
initUsbStorage({
    deviceIndex: 0,
    statCapacity: true,
    success: (info) => {
        if (info.capacityStat) {
            console.log(`已用 ${info.occupiedSpace} / 剩余 ${info.freeSpace}`)
        }
    }
})

关于数值含义

  • occupiedSpace = capacity - freeSpace,其中包含文件系统自身的元数据占用, 因此「已用空间」会略大于 U 盘内文件大小之和,属正常现象;
  • 部分 U 盘上报的可用空间信息不完整,开启 statCapacity 时插件会自动校正, 保证 freeSpace 不为负数。

关于授权

设备未授权时,插件会自动弹出系统 USB 授权弹窗,用户点「允许」后自动继续初始化并回调 success, 不需要再次点击初始化。用户拒绝或 60 秒内未响应会走 fail(错误码 9030004 / 9030016)。

registerUsbStateReceiver(options?)

注册 U 盘插入 / 拔出监听。

参数

参数 类型 必填 说明
onEvent Function 插拔事件回调,可多次触发(UsbDriveEvent

onEvent 回调字段:

字段 类型 说明
event string deviceAttached(插入) / deviceDetached(拔出)
deviceId number 系统分配的设备 ID
deviceName string 系统设备节点路径
registerUsbStateReceiver({
    onEvent: (e) => {
        if (e.event === 'deviceAttached') {
            // 插入后建议刷新设备列表并重新初始化
            getUsbDriveDeviceList()
        } else {
            // 拔出后需要重新初始化才能继续操作
            this.initialized = false
        }
    },
    success: (res) => console.log(res.errMsg),       // 注册成功
    fail: (err) => console.error(err.errCode, err.errMsg)
})

getUsbFileList(options?)

获取指定目录下的文件列表,自动过滤 System Volume InformationLOST.DIR 等系统目录。

参数

参数 类型 必填 默认值 说明
path string / 要浏览的目录路径

success 回调{ files: UsbDriveFileInfo[] },字段见数据类型

getUsbFileList({
    path: '/',
    success: (res) => {
        res.files.forEach(f => {
            console.log(f.isDirectory ? '[目录]' : '[文件]', f.name, f.size)
        })
    }
})

readTextFile(options)

读取文本文件,按 UTF-8 解码。

参数

参数 类型 必填 说明
filePath string 文件路径,如 /a/b.txt

success 回调{ content: string }

readTextFile({
    filePath: '/test.txt',
    success: (res) => console.log(res.content),
    fail: (err) => console.error(err.errCode, err.errMsg)
})

readBinaryFile(options)

读取二进制文件,返回 Base64 字符串(不含换行)。

参数

参数 类型 必填 说明
filePath string 文件路径

success 回调{ base64: string }

readBinaryFile({
    filePath: '/logo.png',
    success: (res) => console.log('Base64 长度:', res.base64.length)
})

writeTextFile(options)

写入文本文件(UTF-8)。文件不存在时自动创建,已存在时覆盖

参数

参数 类型 必填 说明
filePath string 目标文件路径
content string 要写入的内容

success 回调{ errMsg: string }

writeTextFile({
    filePath: '/notes/readme.txt',
    content: 'hello',
    success: (res) => console.log(res.errMsg),
    fail: (err) => console.error(err.errCode, err.errMsg)
})

目标文件的父目录必须已存在,可先用 createUsbDirectory 创建。 覆盖写入时会先截断原文件,不会残留旧内容。

writeBinaryFile(options)

写入二进制文件。文件不存在时自动创建,已存在时覆盖

参数

参数 类型 必填 说明
filePath string 目标文件路径
base64Data string 文件内容的 Base64 字符串

success 回调{ errMsg: string }

writeBinaryFile({
    filePath: '/logo.png',
    base64Data: 'iVBORw0KGgoAAAANSUhEUg...',
    success: (res) => console.log(res.errMsg)
})

copyLocalFileToUsb(options)

把手机本地文件复制到 U 盘。大文件会分块传输,可配合 onProgress 展示进度。

参数

参数 类型 必填 说明
localFilePath string 本地文件路径,如 /sdcard/Download/a.mp4
usbFilePath string U 盘目标路径,如 /video/a.mp4
onProgress Function 复制进度回调(UsbDriveCopyProgress

success 回调{ errMsg: string }

copyLocalFileToUsb({
    localFilePath: '/sdcard/Download/video.mp4',
    usbFilePath: '/video.mp4',
    : (p) => {
        console.log(`进度 ${p.progress}%(${p.loaded}/${p.total} 字节)`)
    },
    success: (res) => console.log(res.errMsg),
    fail: (err) => console.error(err.errCode, err.errMsg)
})

U 盘目标文件的父目录必须已存在;本地源文件不存在或为目录时会走 fail

copyUsbFileToLocal(options)

把 U 盘文件复制到手机本地。本地父目录不存在时会自动创建。

参数

参数 类型 必填 说明
usbFilePath string U 盘源文件路径
localFilePath string 本地保存路径
onProgress Function 复制进度回调(UsbDriveCopyProgress

success 回调{ errMsg: string }

copyUsbFileToLocal({
    usbFilePath: '/video.mp4',
    localFilePath: '/sdcard/Download/video.mp4',
    : (p) => this.percent = p.progress,
    success: (res) => console.log(res.errMsg)
})

复制进度 onProgress

copyLocalFileToUsbcopyUsbFileToLocal 支持 onProgress 回调, 在复制过程中按整数百分比变化多次触发0 ~ 100),可用于驱动进度条:

字段 类型 说明
loaded number 已传输字节数
total number 待传输总字节数
progress number 进度百分比(0 ~ 100 的整数)

createUsbDirectory(options)

创建 U 盘目录,支持多级(如 /a/b/c),已存在的层级会自动跳过。

参数

参数 类型 必填 说明
dirPath string 目录路径,如 /a/b/c

success 回调{ errMsg: string }

createUsbDirectory({
    dirPath: '/logs/2026',
    success: (res) => console.log(res.errMsg)
})

若路径中某一级已存在同名文件,会走 fail(错误码 9030011)。

deleteUsbFile(options)

删除 U 盘上的文件或目录。

参数

参数 类型 必填 说明
filePath string 要删除的文件 / 目录路径

success 回调{ errMsg: string }

deleteUsbFile({
    filePath: '/logs/2026',
    success: (res) => console.log(res.errMsg),
    fail: (err) => console.error(err.errCode, err.errMsg)
})
  • 不允许删除根目录 /(错误码 9030001);
  • 删除目录要求该目录为空,非空目录会走 fail

releaseResources(options?)

释放插件占用的设备与监听资源。

参数:无(options 可省略)

success 回调{ errMsg: string }

onUnload() {
    releaseResources({
        success: (res) => console.log(res.errMsg)
    })
}

建议在页面 onUnload 或退出相关业务时调用。释放后如需继续操作,请重新执行 getUsbDriveDeviceListinitUsbStorage

数据类型

UsbDriveDeviceInfo

字段 类型 说明
deviceId number 系统分配的设备 ID
deviceName string 系统设备节点路径,如 /dev/bus/usb/001/002
productName string \| null 产品名
manufacturerName string \| null 厂商名
vendorId number 厂商 ID
productId number 产品 ID
index number 列表下标,传给 initUsbStorage

UsbDriveStorageInfo

initUsbStorage

UsbDriveFileInfo

字段 类型 说明
name string 文件 / 目录名
path string 相对 U 盘根目录的路径,可直接传给各文件接口
absolutePath string U 盘内的绝对路径
isDirectory boolean 是否为目录
size number 文件大小(字节),目录恒为 0

UsbDriveEvent

registerUsbStateReceiver

UsbDriveCopyProgress

复制进度

UsbDrivePermissionResult

checkAllFilesAccessPermission

错误码

失败回调 fail(err) 中的 errUniError 对象:

字段 说明
errCode 错误码,取值见下表
errMsg 错误描述
errSubject 固定为 zy-usb-drive
错误码 说明
9030001 参数错误(含非法路径、删除根目录、非法 Base64 等)
9030002 USB 存储未初始化,请先调用 initUsbStorage
9030003 未检测到 USB 存储设备
9030004 USB 权限被拒绝
9030005 初始化 USB 存储失败(含设备无可用分区)
9030006 文件或目录不存在
9030007 获取文件列表失败
9030008 读取文件失败
9030009 写入文件失败
9030010 删除失败
9030011 创建目录失败(如路径中存在同名文件)
9030012 复制文件失败
9030013 打开权限设置页失败
9030014 应用上下文获取失败(建议重启应用)
9030015 释放资源失败
9030016 USB 权限授权超时(60 秒内未响应授权弹窗)
9030017 指定路径是目录,不能按文件读写

权限与要求

插件已声明以下权限与硬件特性:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

<uses-feature android:name="android.hardware.usb.host" android:required="true" />
权限 / 特性 用途
READ_EXTERNAL_STORAGE 读取本地文件(复制到 U 盘、读取本地源文件)
WRITE_EXTERNAL_STORAGE 写入本地文件(从 U 盘复制到本地)
MANAGE_EXTERNAL_STORAGE Android 11+ 访问 /sdcard 等公共目录
android.hardware.usb.host USB Host 能力,使用 U 盘的前提

另外,访问具体某个 U 盘时还需要系统的 USB 设备授权,该授权由插件在 initUsbStorage 中自动发起。

MANAGE_EXTERNAL_STORAGE(「所有文件访问权限」)无法通过运行时弹窗申请, 必须由用户前往系统设置手动开启。可先用 checkAllFilesAccessPermission 检查并自动跳转设置页。

使用须知

  1. 调用顺序getUsbDriveDeviceListinitUsbStorage → 文件相关接口。 deviceIndex 来自设备列表,不能自行臆造。
  2. 必须先初始化:除 checkAllFilesAccessPermissiongetUsbDriveDeviceListregisterUsbStateReceiverreleaseResources 外,其余接口都需要先成功初始化, 否则返回 9030002
  3. 父目录需自行创建:写入文件(writeTextFile / writeBinaryFile)与 复制到 U 盘(copyLocalFileToUsb)都不会自动创建父目录,请先用 createUsbDirectorycopyUsbFileToLocal 会自动创建本地的父目录。
  4. 拔出 U 盘后需重新初始化:拔出事件触发后,原有初始化状态失效, 需要重新 getUsbDriveDeviceList + initUsbStorage
  5. 大文件不会阻塞界面:文件读写与复制不会占用界面线程, 复制过程可通过 onProgress 展示进度。
  6. 同一时刻只支持一个已初始化的设备:插件内维护单一存储上下文, 切换设备请重新初始化,必要时先 releaseResources
  7. 及时释放资源:不再需要时请调用 releaseResources,避免残留的插拔监听。

常见问题

Q:successfreeSpace / occupiedSpace 一直是 0? A:这是默认行为(不统计容量信息)。需要时在 initUsbStorage 传入 statCapacity: true, 并用 capacityStat 判断本次是否已统计。

Q:开启 statCapacity 后初始化变慢了? A:统计剩余 / 已用空间需要额外读取磁盘信息,U 盘容量越大耗时越明显。 如果只做文件读写,保持默认(不统计)即可。

Q:occupiedSpace 比 U 盘里文件的总大小还大? A:正常现象。该值按 capacity - freeSpace 计算,包含文件系统自身的元数据占用。

Q:部分 U 盘上 freeSpace 显示为 0? A:说明该 U 盘上报的可用空间信息不可用且无法校正,插件会退化为 0 而不是返回异常值。 不影响文件浏览与读写,建议用 capacityoccupiedSpace 谨慎展示。

Q:调用 initUsbStorage 后没等到回调? A:若设备未授权,插件会弹出系统 USB 授权弹窗,此时不会立即回调。 请在弹窗中点「允许」。60 秒未响应会走 fail9030016),拒绝授权会走 fail9030004)。

Q:写入文件报「父目录不存在」? A:先调用 createUsbDirectory 创建目录,再写入文件。

Q:删除目录失败? A:只能删除空目录,请先删除目录内的文件;根目录 / 不允许删除。

Q:iOS 能用吗? A:不能。系统未开放直接访问 U 盘文件系统的能力,本插件仅支持 Android。

隐私、权限声明

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

android.permission.READ_EXTERNAL_STORAGE,android.permission.WRITE_EXTERNAL_STORAGE,android.permission.MANAGE_EXTERNAL_STORAGE,android.hardware.usb.host

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

U盘内文件与设备信息仅在本机读写,不会上传至任何服务器

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