用户需要分享文件、保存图片、视频等用户文件时,开发者可以通过系统预置的文件选择器(FilePicker),实现该能力。通过Picker访问相关文件,将拉起对应的应用,引导用户完成界面操作,接口本身无需申请权限。Picker获取的URI只具有临时权限,获取持久化权限需要通过FilePicker设置永久授权方式获取。
根据用户文件的常见类型,选择器(FilePicker)分别提供以下选项:
- PhotoViewPicker:使用PhotoAccessHelper的PhotoViewPicker来选择图片文件。请使用安全控件保存媒体库资源。
- DocumentViewPicker:适用于文件类型文件的选择与保存。DocumentViewPicker对接的选择资源来自于FilePicker,负责文件类型的资源管理,文件类型不区分后缀,比如浏览器下载的图片、文档等,都属于文件类型。
- AudioViewPicker:适用于音频类型文件的选择与保存。AudioViewPicker目前对接的选择资源来自于FilePicker。
一、图片、视频类
1.1、选择图片、视频类文件用于上传
这个主要是把相册的图片保存到沙箱,因为要想把图片上传到服务器,必须是从沙箱文件中上传的。
typescript
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { util } from '@kit.ArkTS';
async function photoSelect() {
let phoneUris: string[] = [];
/**
从相册选择图片
*/
//图片选择配置项
let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
//选择图片以及视频
PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE;
//最大选择数
PhotoSelectOptions.maxSelectNumber = 9;
//创建图片选择器
let photoPicker = new photoAccessHelper.PhotoViewPicker();
await photoPicker.select(PhotoSelectOptions).then((PhotoSelectResult: photoAccessHelper.PhotoSelectResult) => {
//选到的资源路径数组
phoneUris = PhotoSelectResult.photoUris;
});
/**
* 把选到的资源存到应用缓存文件中,用于后续上传图片到服务器(如果只是页面显示的话不需要这一步)
*/
phoneUris.forEach(async (uri: string, index:number) => {
let originPhoneFile = await fs.open(uri, fs.OpenMode.READ_ONLY);
let newPhoneFileName = getContext().cacheDir + '/' + util.generateRandomUUID() +
uri.substring(uri.lastIndexOf('.'), uri.length);
await fs.copyFileSync(originPhoneFile.fd, newPhoneFileName);
})
return phoneUris;
}
@Entry
@ComponentV2
struct TestPhoneSelectPage {
@Local phoneUri: string[] = [];
@Local videoUri: string[] = [];
build() {
Column({space: 15}) {
Button('选择照片')
.onClick(async () => {
let uris: string[] = await photoSelect();
uris.forEach((uri: string, index: number) => {
let lowerCaseUri = uri.toLowerCase();
if (lowerCaseUri.indexOf('jpg') > 0 || lowerCaseUri.indexOf('png') > 0) {
this.phoneUri.push(uri);
}
if (lowerCaseUri.indexOf('mp4') > 0) {
this.videoUri.push(uri);
}
})
})
ForEach(this.phoneUri, (uri: string, index: number) => {
Image(uri)
.width(200)
.height(200)
})
ForEach(this.videoUri, (uri: string, index: number) => {
Video({
src: uri
})
.width(200)
.height(200)
})
}
.height('100%')
.width('100%')
}
}
1.2、保存图片、视频类到图库
当用户需要保存图片、视频等用户文件到图库时,无需在应用中申请相册管理模块权限'ohos.permission.WRITE_IMAGEVIDEO'(该权限是受控权限,一般项目取法无法申请通过的),应用可以通过安全控件或授权弹窗的方式,将用户指定的媒体资源保存到图库中。
通过弹窗授权的形式保存图片到图库
typescript
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import request from '@ohos.request';
import { util } from '@kit.ArkTS';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { fileIo as fs, fileUri } from '@kit.CoreFileKit';
@Entry
@ComponentV2
struct SavePhonePage {
@Local phoneURL: string = 'https://这里换成自己的(或者从网上随便找点资源)/java/gaoding/20240930143526.png';
build() {
Column({space: 20}) {
Text('保存图片页面');
Image(this.phoneURL)
.width(300)
.height(300)
Button('保存图片') // 创建安全控件按钮
.onClick(async () => {
try {
let context = getContext();
//截取图片文件的后缀名
let phoneSuff = this.phoneURL.substring(this.phoneURL.lastIndexOf('.') + 1, this.phoneURL.length);
/**
* 图片的沙箱路径: 大家注意这里需要把图片上传到沙箱的目录
* 该路径是从/base开始的
*/
let sandboxPhonePath = context.cacheDir + '/' + util.generateRandomUUID() + '.' + phoneSuff;
//先把网络图片保存到应用缓存沙箱中
await request.downloadFile(context, {
url: this.phoneURL,
filePath: sandboxPhonePath
})
let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
/**
* 转换成沙箱文件uri
* 该路径是从file://com.szjy.TestCourseware开始的
*/
let sandboxPhoneUri = fileUri.getUriFromPath(sandboxPhonePath);
//用于保存到用户相册的图片数组
let srcFileUris: Array<string> = [
sandboxPhoneUri
];
// 指定待保存照片的创建选项,包括文件后缀和照片类型,标题和照片子类型可选
let photoCreationConfigs: Array<photoAccessHelper.PhotoCreationConfig> = [
{
title: 'test', // 可选(可以在弹出的弹窗中更改)
fileNameExtension: phoneSuff,
photoType: photoAccessHelper.PhotoType.IMAGE,
subtype: photoAccessHelper.PhotoSubtype.DEFAULT
}
];
//通过授权弹窗用户确定以后返回可以持久化的用户相册图片的uri
let desFileUris: Array<string> = await phAccessHelper.showAssetsCreationDialog(srcFileUris, photoCreationConfigs);
let userPhoneFile: fs.File = await fs.open(desFileUris[0], fs.OpenMode.WRITE_ONLY);
let sandboxPhoneFile: fs.File = await fs.open(sandboxPhoneUri, fs.OpenMode.READ_ONLY);
//把沙箱中的图片复制到图库中的图片文件上
fs.copyFile(sandboxPhoneFile.fd, userPhoneFile.fd).then(() => {
AlertDialog.show({
message: '保存图片成功!'
})
//删除沙箱中的临时文件
fs.unlink(sandboxPhoneUri);
fs.closeSync(userPhoneFile);
fs.closeSync(sandboxPhoneFile);
});
hilog.error(0x0000, 'TestCourseware', `sandboxPhoneUri: ` + sandboxPhoneUri);
} catch (err) {
hilog.error(0x0000, 'TestCourseware', `create asset failed with error: ${err.code}, ${err.message}`);
}
})
}
.width('100%')
.height('100%')
}
}
通过安全控件保存图片
typescript
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { request } from '@kit.BasicServicesKit';
import { util } from '@kit.ArkTS';
import { fileUri } from '@kit.CoreFileKit';
@Entry
@ComponentV2
struct SavePhonePage2 {
//自定义的网络图片路径
@Local httpPhonePath: string = 'https://pics0.baidu.com/feed/aa18972bd40735fa9cd3d6868c2aa6bd0f240873.jpeg';
//保存按钮配置项
saveButtonOptions: SaveButtonOptions = {
icon: SaveIconStyle.FULL_FILLED,//图标
text: SaveDescription.SAVE_IMAGE,//文字
buttonType: ButtonType.Capsule//按钮类型
}
build() {
Row() {
Column() {
SaveButton(this.saveButtonOptions) // 创建安全控件按钮。
.onClick(async (event, result: SaveButtonOnClickResult) => {
if (result == SaveButtonOnClickResult.SUCCESS) {
try {
let context = getContext();
//从网络图片路径里面截取图片后缀名
let phoneSuff = this.httpPhonePath.substring(this.httpPhonePath.lastIndexOf('.') + 1, this.httpPhonePath.length)
//沙箱文件路径: /data/storage/el2/base/haps/entry/files/e7d45697-824d-44b4-a29c-05ab17ee4b25.jpeg
let sandboxPhonePath = context.filesDir + '/' + util.generateRandomUUID() + '.' + phoneSuff;
//把网络图片下载到沙箱文件中
let task = await request.downloadFile(context, {
url: this.httpPhonePath,
filePath: sandboxPhonePath
})
//必须监听下载完成以后在进行保存到系统相册
task.on('complete', async () => {
//转成沙箱文件的uri:file://com.szjy.TestCourseware/data/storage/el2/base/haps/entry/files/e7d45697-824d-44b4-a29c-05ab17ee4b25.jpeg
let sandboxPhoneUri = fileUri.getUriFromPath(sandboxPhonePath);
//相册管理模块
let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = photoAccessHelper.MediaAssetChangeRequest
.createImageAssetRequest(context, sandboxPhoneUri);
//把沙箱图片保存到相册
await phAccessHelper.applyChanges(assetChangeRequest)
.then(() => {
AlertDialog.show({
message: '图片保存成功'
})
});
})
} catch (err) {
console.error(`create asset failed with error: ${err.code}, ${err.message}`);
}
} else {
console.error('SaveButtonOnClickResult create asset failed');
}
})
}
.width('100%')
}
.height('100%')
}
}
二、保存文档文件到指定目录
typescript
import { picker } from '@kit.CoreFileKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
@Entry
@ComponentV2
struct SaveDocumentPage {
@Local message: string = '话说天下大势,分久必合,合久必分:周末七国分争,并入于秦;及秦灭之后,楚、汉分争又并入于汉;汉朝自高祖斩白蛇而起义,一统天下,后来光武中兴,传至献帝,遂分为三国。推其致乱之由,殆始于桓、灵二帝。桓帝禁锢善类[1],崇信宦官。及桓帝崩,灵帝即位,大将军窦武、太傅陈蕃共相辅佐,时有宦官曹节等弄权,窦武、陈蕃谋诛之,机事不密,反为所害,中[2]自此愈横。';
build() {
Column(){
Text(this.message);
Button('保存以上内容').onClick(async () =>{
// 创建文件管理器选项实例。
const documentSaveOptions = new picker.DocumentSaveOptions();
// 保存文件名,也可以在弹出的弹窗中更改
documentSaveOptions.newFileNames = ["DocumentViewPicker01.txt"];
// 保存文件类型['后缀类型描述|后缀类型'],选择所有文件:'所有文件(*.*)|.*'(可选) ,如果选择项存在多个后缀(最大限制100个过滤后缀),默认选择第一个。如果不传该参数,默认无过滤后缀。
documentSaveOptions.fileSuffixChoices = ['文档|.txt', '.pdf'];
let uris: Array<string> = [];
// 请确保 getContext(this) 返回结果为 UIAbilityContext
let context = getContext(this) as common.Context;
//创建文件选择器
const documentViewPicker = new picker.DocumentViewPicker(context);
//选择文件,并且返回文件的uri(就是用上面的newFileName加上你选择的目录,创建一个新文件)
await documentViewPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
uris = documentSaveResult;
})
//通过DocumentViewPicker选择的文件有保存的权限
const uri = uris[0];
let file = await fs.openSync(uri, fs.OpenMode.READ_WRITE);
//写数据
let writeLen: number = await fs.writeSync(file.fd, this.message);
fs.closeSync(file);
})
}
.height('100%')
.width('100%')
}
}
二、保存任何文件到指定目录
保存文件的操作和保存文档的操作基本类似,下面就写一个例子,大家需要注意的是网络上的资源要想保存到本地必须先上传到应用沙箱中,在从应用沙箱中保存数据到本地。
typescript
import { picker } from '@kit.CoreFileKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { BusinessError, request } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
@Entry
@ComponentV2
struct SaveDocumentPage {
@Local filePath: string = 'https://这里换成自己的(或者从网上随便找点资源)/java/gaoding/meituan.rar';
build() {
Column(){
Image($r('app.media.yasuobao'))
.width(100)
.height('100')
Button('下载资源').onClick(async () =>{
// 请确保 getContext(this) 返回结果为 UIAbilityContext
let context = getContext(this) as common.Context;
/**
* 先把文件上传到沙箱: 演示案例名字就写死了
*/
let sandboxFilePath = context.filesDir + '/meituan.rar';
let task = await request.downloadFile(context,{
url: this.filePath,
filePath: sandboxFilePath
})
//监控上传进度
task.on("progress", (uploadedSize: number, totalSize: number) => {
console.error(`上传进度: ${uploadedSize}, ${totalSize}`);
})
//监听上传成功
task.on("complete", async () => {
// 创建文件管理器选项实例。
const documentSaveOptions = new picker.DocumentSaveOptions();
// 保存文件名,也可以通过输入框让用户自己输入
documentSaveOptions.newFileNames = ["meituan.rar"];
// 保存文件类型['后缀类型描述|后缀类型'],选择所有文件:'所有文件(*.*)|.*'(可选) ,如果选择项存在多个后缀(最大限制100个过滤后缀),默认选择第一个。如果不传该参数,默认无过滤后缀。
documentSaveOptions.fileSuffixChoices = ['.rar'];
let locationUris: Array<string> = [];
//创建文件选择器
const documentViewPicker = new picker.DocumentViewPicker(context);
//选择文件,并且返回文件的uri(就是用上面的newFileName加上你选择的目录,创建一个新文件)
await documentViewPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
locationUris = documentSaveResult;
})
//通过DocumentViewPicker选择的文件有保存的权限
const locationUri= locationUris[0];
//保存到本地的文件
let locationFile = await fs.openSync(locationUri, fs.OpenMode.READ_WRITE);
//沙箱里面的文件
let sandboxFile = await fs.open(sandboxFilePath, fs.OpenMode.READ_WRITE);
//写数据
await fs.copyFile(sandboxFile.fd, locationFile.fd)
fs.closeSync(locationFile);
fs.closeSync(sandboxFile);
//删除沙箱中文件
fs.unlink(sandboxFilePath);
})
})
}
.height('100%')
.width('100%')
}
}