文章目录
- 前言
- 前言
- 两种 Picker 定位对比
- PhotoViewPicker 选图
- DocumentViewPicker 选文件
- 文件读取与保存
- 沙箱路径说明
- 写在最后
前言
选图片、选文件,App 里最基础的操作之一。但 HarmonyOS 的文件选择跟 Android 不太一样,它用了一套 Picker 体系,不是直接给你文件路径,而是给你一个 URI——这就涉及到沙箱路径、临时权限、文件拷贝等一系列问题。我第一次用的时候,文件选到了却读不出来,折腾了好一阵才搞明白。今天把 PhotoViewPicker 和 DocumentViewPicker 的用法彻底讲清楚。
前言
HarmonyOS7 的文件选择器分两种:PhotoViewPicker选图片/视频,DocumentViewPicker选文档类文件。两种 Picker 用法类似,但返回的 URI 权限不同,处理方式也有差异。这篇文章会从选图、选文件、读文件、存文件、沙箱路径,一条龙讲完。
两种 Picker 定位对比
先搞清楚什么时候用哪个:
| 对比维度 | PhotoViewPicker | DocumentViewPicker |
|---|---|---|
| 用途 | 选择图片和视频 | 选择任意文档文件 |
| 导入模块 | @kit.CoreFileKit | @kit.CoreFileKit |
| 核心方法 | select() | select() |
| 保存方法 | save() | save() |
| 返回类型 | 图片/视频 URI | 文档 URI |
| 文件过滤 | 支持 MIME 类型过滤 | 支持后缀名过滤 |
| 多选 | 支持(maxSelectNumber) | 支持(maxSelectNumber) |
| 临时权限 | 只读 | 只读 |
简单判断:选头像、选相册照片 → PhotoViewPicker;选 PDF、选 Excel → DocumentViewPicker。
PhotoViewPicker 选图
选图片是最常见的场景,代码如下:
import{picker}from'@kit.CoreFileKit';import{BusinessError}from'@kit.BasicServicesKit';@StateselectedUri:string='';asyncpickPhoto():Promise<void>{try{letphotoSelectOptions=newpicker.PhotoSelectOptions();photoSelectOptions.MIMEType=picker.PhotoViewMIMETypes.IMAGE_TYPE;photoSelectOptions.maxSelectNumber=1;letphotoViewPicker=newpicker.PhotoViewPicker();letphotoSelectResult=awaitphotoViewPicker.select(photoSelectOptions);if(photoSelectResult.photoUris.length>0){this.selectedUri=photoSelectResult.photoUris[0];console.info(`选中的图片URI:${this.selectedUri}`);}}catch(e){leterr=easBusinessError;console.error(`选图失败: code=${err.code}, msg=${err.message}`);}}逐行讲解:
MIMEType: IMAGE_TYPE— 只显示图片类型。如果也想选视频,改成IMAGE_VIDEO_TYPEmaxSelectNumber: 1— 最多选 1 张。改成 9 就是多选select()是异步方法,返回PhotoSelectResultphotoSelectResult.photoUris是选中的 URI 数组,多选时会有多个- URI 不是文件路径,不能直接拿来显示图片(需要用
@ohos.file.picker返回的 URI 配合 Image 组件使用,Image 组件可直接加载 URI)
Image 组件可以直接用 URI 显示图片:
Image(this.selectedUri).width(200).height(200).objectFit(ImageFit.Cover)DocumentViewPicker 选文件
选 PDF、Word 这类文档就得用 DocumentViewPicker:
asyncpickDocument():Promise<void>{try{letdocumentSelectOptions=newpicker.DocumentSelectOptions();documentSelectOptions.maxSelectNumber=1;documentSelectOptions.fileSuffixFilters=['.pdf','.doc','.docx','.xls'];letdocumentViewPicker=newpicker.DocumentViewPicker(this.getUIContext().getHostContext()ascommon.UIAbilityContext);letdocumentSelectResult=awaitdocumentViewPicker.select(documentSelectOptions);if(documentSelectResult.length>0){this.selectedDocUri=documentSelectResult[0];console.info(`选中的文件URI:${this.selectedDocUri}`);}}catch(e){leterr=easBusinessError;console.error(`选文件失败: code=${err.code}, msg=${err.message}`);}}关键讲解:
fileSuffixFilters— 按文件后缀过滤,只显示指定类型的文件。不设置则显示所有文件DocumentViewPicker构造时需要传入 Context,PhotoViewPicker 不需要(这是个区别,记一下)- 返回的
documentSelectResult直接就是 URI 数组,不像 PhotoSelectResult 那样包了一层
文件读取与保存
拿到 URI 后怎么读文件内容?这就要用到@kit.CoreFileKit里的fileIo:
import{fileIoasfs,fileUri}from'@kit.CoreFileKit';asyncreadFileFromUri(uri:string):Promise<string>{letfile=fs.openSync(uri,fs.OpenMode.READ_ONLY);letstat=fs.statSync(file.fd);letbuffer=newArrayBuffer(stat.size);fs.readSync(file.fd,buffer);fs.closeSync(file);// 假设是文本文件lettextDecoder=util.TextDecoder.create('utf-8');returntextDecoder.decodeToString(newUint8Array(buffer));}逐行讲解:
fs.openSync(uri, READ_ONLY)— 通过 URI 打开文件,返回文件描述符fs.statSync(file.fd)— 获取文件大小fs.readSync— 读取文件内容到 ArrayBuffer- 读完后必须 closeSync,不然文件描述符泄漏
保存文件到公共目录:
如果你要把文件保存到文件管理器可见的公共目录,用 Picker 的save()方法:
asyncsaveFileToPublic():Promise<void>{letdocumentSaveOptions=newpicker.DocumentSaveOptions();documentSaveOptions.newFileNames=['report.pdf'];letdocumentViewPicker=newpicker.DocumentViewPicker(this.getUIContext().getHostContext()ascommon.UIAbilityContext);letsaveResult=awaitdocumentViewPicker.save(documentSaveOptions);console.info(`保存路径:${saveResult[0]}`);}
save()会弹出系统文件选择器,让用户选保存位置。返回的是保存后的 URI。
沙箱路径说明
这是很多人搞不清楚的地方。HarmonyOS 的文件系统是沙箱隔离的:
| 路径 | 说明 | 其他应用可见 |
|---|---|---|
context.filesDir | 应用私有文件目录 | 否 |
context.cacheDir | 缓存目录,系统可能清理 | 否 |
context.tempDir | 临时文件目录 | 否 |
| Picker 返回的 URI | 临时授权的公共文件 URI | 只读临时权限 |
| save() 返回的 URI | 用户选定的公共目录 | 是 |
核心问题:Picker 返回的 URI 只有临时只读权限,这个权限不能转授给其他组件(比如文件预览)。如果你要预览文件,得先把文件拷贝到沙箱:
asynccopyToSandbox(pickerUri:string):Promise<string>{letfile=fs.openSync(pickerUri,fs.OpenMode.READ_ONLY);letpathDir=getContext().filesDir;letsandboxPath=pathDir+'/'+file.name;fs.copyFileSync(file.fd,sandboxPath);fs.closeSync(file);returnfileUri.getUriFromPath(sandboxPath);}逐行讲解:
file.name— 从 URI 中提取文件名copyFileSync— 把文件从公共目录拷到沙箱fileUri.getUriFromPath— 把沙箱路径转成 URI,给预览组件用
敲黑板:Picker 拿到的 URI 是临时的,App 重启后就失效了。需要持久使用必须拷贝到沙箱。
写在最后
文件选择这块最大的坑就是 URI 权限问题。Picker 给你的 URI 只有临时只读权限,想预览、想持久化都得先拷贝到沙箱。我之前就在这卡了好久,文件选到了但 Preview Kit 死活打不开,最后才发现是权限没转授的问题。
记住一句话:Picker 选完先拷贝,沙箱里才是你的地盘。