课程demo参考:访问和操作文件demo

应用文件介绍

在设备上应用所使用及存储的数据,以文件、键值对、数据库等形式保存在一个专属目录类,该目录称为应用文件目录。应用文件目录是应用数据的核心存储区域,其内部数据以多种文件格式存放,这些文件统称为应用文件应用文件是指归属于应用的文件集合,包括应用安装文件、应用资源文件、应用缓存文件等

在应用开发中,以下数据通常需要通过文件的形式保存到App中:

  • 用户生成内容:用户撰写的笔记或评论、创作的图片或视频、分享的文档等。
  • 地图数据:地图导航类App的离线地图,供用户离线查看和导航。
  • 语言包数据:支持多语言的App的语言包数据。
  • 学习资料数据:教育类App中,一些离线学习资料。
  • 游戏资源:包括游戏中的图片、音频、视频、模型等素材资源。
  • ...

应用沙箱介绍

概述

对于每个应用,系统会在内部存储空间映射出一个专属的“应用沙箱目录”,它是“应用文件目录”与一部分系统文件所在的目录组成的集合。应用沙箱限制了应用可见的数据范围,在“应用沙箱目录”中,应用仅能看到自己的应用文件以及少量的系统文件(少量的系统文件是指应用运行必需的少量系统文件)。

因此,本应用的文件也不为其他应用可见,从而保护了应用文件的安全。

应用沙箱是一种以安全防护为目的的隔离机制,避免数据受到恶意路径穿越访问。在这种沙箱的保护机制下,应用可见的目录范围即为“应用沙箱目录"。

  • 在应用沙箱保护机制下,应用无法获知除自身应用文件目录之外的其他应用或用户的数据目录位置及存在。
  • 所有应用的目录可见范围均经过权限隔离与文件路径挂载隔离,形成了独立的路径视图,屏蔽了实际物理路径。实际物理路径与沙箱路径并非1:1的映射关系,沙箱路径总是少于系统进程视角可见的物理路径,部分调试进程视角下的物理路径在对应的应用沙箱目录下没有对应路径。

应用沙箱目录

应用文件目录结构图

三级目录代表不同文件加密类型。详细介绍如下:

EL1:设备级加密区。EL1加密类型提供了保护设备上的所有文件的基础安全能力,在设备开机后,不需要用户先完成身份认证即可访间EL1保护的文件。对于私有文件,如闹铃、壁纸等,应用可以将这些文件放到设备级加密分区 (EL1) 中,以保证在用户输入密码前就可以被访问。

EL2:用户级加密区。EL2在EL1的基础上,增加首次认证后的文件保护能力,设备开机后,用户在首次通过认证后,通过EL2能力保护的文件才能被访问。对于更敏感的文件,如个人隐私信息等,应用可以将这些文件放到更高级别的加密分区 (EL2) 中,以保证更高的安全性。

EL3:EL3在EL2的基础上,增加设备在锁屏时的文件保护能力,锁屏下可创建新的文件,但无法读取。需要在锁屏时访问锁屏前打开的文件和创建新文件,放在EL3加密区比较合适。对于应用中的记录步数、文件下载、音乐播放,放在(EL3)的加密分区比较合适,

EL4:EL4与EL3整体能力类似,在用户锁屏时无法读取文件,也不能创建文件,建议存放对于用户安全信息相关的文件。对于用户安全信息相关的文件,锁屏时不需要读写文件、也不能创建文件,放在(EL4)的加密分区更合适。

EL5:EL5主要存储用户隐私等敏感数据文件,锁屏后默认不可读写。对于用户隐私敏感数据文件,锁屏后默认不可读写,如果锁屏后需要读写文件,则锁屏前可以调用Access接口申请继续读写文件。

如果锁屏后也需要创建新文件且可读可写的应用,如无特殊需要,应将数据存放在el2加密目录下,以尽可能保证数据安全。

四级、五级目录:

通过ApplicationContext可以获取distributedfiles目录或base下的files、cache、 preferences、temp等目录的应用文件路径,应用全局信息可以存放在这些目录下。

四级目录说明如下:

五级目录说明如下:

七级目录

七级目录含义和五级目录类似,但其代表HAP级别应用文件路径

通过UIAbilityContext、AbilityStageContext、ExtensionContext可以获取HAP级别应用文件路径。HAP信息可以存放在这些目录下,存放在此目录的文件会跟随HAP的卸载而删除,不会影响App级别目录下的文件。

应用沙箱路径和物理路径

在应用沙箱路径下读写文件,经过映射转换,实际读写的是真实物理路径中的应用文件,应用沙箱路径与真实物理路径对应关系如下表所示。其中<USERID>为当前用户ID,从100开始递增。

应用文件访问和操作

操作文件常用API

获取沙箱路径

打开文件

open(path: string, mode?: number): Promise<File> 用于打开文件,使用Promise异步返回。支持使用URl打开文件。

读文件

read(fd: number, buffer: ArrayBuffer, options?: ReadOptions): Promisecnumber>从文件读取数据,使用Promise异步返回。

写文件

write(fd: number, buffer: ArrayBuffer | string, options?: WriteOptions): Promise<number>将数据写入文件,使用Promise异步返回。

文件压缩

compressFile(inFile: string, outFile: string, options: Options): Promise<void>

压缩文件,压缩的结果,使用Promise异步返回。成功时返回null,失败时返回错误码。

文件解压缩

decompressFile(inFile: string, outFile: string, options?: Options): Promise<void>

解压文件,解压的结果,使用Promise异步返回,成功时返回null,失败时返回错误码。

文件预览

openPreview(context: Context, files: Array<PreviewInfo>, index?: number): Promise<void>

通过传入多个文件预览信息以及选择展示的文件信息下标,打开预览窗口。1秒内重复调用无效。使用Promise方式异步返回结果。

文件预览信息需要包括文件标题名、文件url和文件资源类型。

应用空间统计

getCurrentBundleStats(): Promise<BundleStats>

异步获取当前应用存储空间大小(单位为Byte),以Promise方式返回。

系统空间统计

get TotalSize(path: string): Promise<number>

异步方法获取指定文件系统总字节数,以Promise形式返回结果。

接口说明

应用需要对应用文件目录下的应用文件进行查看、创建、读写、删除、移动、复制、获取属性等访问操作,下文介绍具体方法。

开发者通过基础文件操作接口(ohos.file.fs)实现应用文件访问能力,主要功能如下表所示。

表1 基础文件操作接口功能

接口名 功能 接口类型 支持同步 支持异步
access 检查文件是否存在 方法
close 关闭文件 方法
copyFile 复制文件 方法
createStream 基于文件路径打开文件流 方法
listFile 列出文件夹下所有文件名 方法
mkdir 创建目录 方法
moveFile 移动文件 方法
open 打开文件 方法
read 从文件读取数据 方法
rename 重命名文件或文件夹 方法
rmdir 删除整个目录 方法
stat 获取文件详细属性信息 方法
unlink 删除单个文件 方法
write 将数据写入文件 方法
Stream.close 关闭文件流 方法
Stream.flush 刷新文件流 方法
Stream.write 将数据写入流文件 方法
Stream.read 从流文件读取数据 方法
File.fd 获取文件描述符 属性 - -
OpenMode 设置文件打开标签 属性 - -
Filter 设置文件过滤配置项 类型 - -

注意

使用基础文件操作接口时,耗时较长的操作,例如:read、write等,建议使用异步接口,避免应用崩溃。

文件流读写和普通读写区别

在鸿蒙开发中,“文件流读写”是面向大规模数据、内存敏感场景的推荐方法,而“普通读写”则适合小文件或一次性操作。它们的主要区别在于数据加载方式和适用的文件大小。

下面的表格清晰地对比了这两种方式的核心区别:

对比维度 文件流读写 (Stream I/O) 普通读写 (常规 I/O)
数据加载方式 分块读取/写入,按需加载数据块至内存。 整体读取/写入,一次性将整个文件内容加载到内存。
内存占用 低且可控,与文件大小无关。 ,直接取决于文件大小。
主要适用场景 大文件(如视频、大型日志)、需要实时处理或网络传输的数据流。 配置文件、用户偏好设置、小型文本或JSON文件。
性能特点 适用于大文件,避免内存溢出,I/O操作更平滑。 适用于小文件,操作简单直接。
关键接口/模块 createReadStream()createWriteStream() (来自 @ohos.file.fs)。 readSync()writeSync()readText() (来自 @ohos.file.fs)。

开发示例

在对应用文件开始访问前,开发者需要获取应用文件路径。以从UIAbilityContext获取HAP级别的文件路径为例进行说明,UIAbilityContext的获取方式请参见获取UIAbility的上下文信息

下面介绍几种常用操作示例。

新建并读写一个文件

// pages/xxx.ets
import { fileIo as fs, ReadOptions } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { buffer } from '@kit.ArkTS';

// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;

function createFile(): void {
  // 文件不存在时创建并打开文件,文件存在时打开文件
  let file = fs.openSync(filesDir + '/test.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
  // 写入一段内容至文件
  let writeLen = fs.writeSync(file.fd, "Try to write str.");
  console.info("The length of str is: " + writeLen);
  // 创建一个大小为1024字节的ArrayBuffer对象,用于存储从文件中读取的数据
  let arrayBuffer = new ArrayBuffer(1024);
  // 设置读取的偏移量和长度
  let readOptions: ReadOptions = {
    offset: 0,
    length: arrayBuffer.byteLength
  };
  // 读取文件内容到ArrayBuffer对象中,并返回实际读取的字节数
  let readLen = fs.readSync(file.fd, arrayBuffer, readOptions);
  // 将ArrayBuffer对象转换为Buffer对象,并转换为字符串输出
  let buf = buffer.from(arrayBuffer, 0, readLen);
  console.info("the content of file: " + buf.toString());
  // 关闭文件
  fs.closeSync(file);
}

读取文件内容并写入到另一个文件

// pages/xxx.ets
import { fileIo as fs, ReadOptions, WriteOptions } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';

// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;

function readWriteFile(): void {
  // 打开文件
  let srcFile = fs.openSync(filesDir + '/test.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
  let destFile = fs.openSync(filesDir + '/destFile.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
  // 读取源文件内容并写入至目的文件
  let bufSize = 4096;
  let readSize = 0;
  let buf = new ArrayBuffer(bufSize);
  let readOptions: ReadOptions = {
    offset: readSize,
    length: bufSize
  };
  let readLen = fs.readSync(srcFile.fd, buf, readOptions);
  while (readLen > 0) {
    readSize += readLen;
    let writeOptions: WriteOptions = {
      length: readLen
    };
    fs.writeSync(destFile.fd, buf, writeOptions);
    readOptions.offset = readSize;
    readLen = fs.readSync(srcFile.fd, buf, readOptions);
  }
  // 关闭文件
  fs.closeSync(srcFile);
  fs.closeSync(destFile);
}

说明

使用读写接口时,需注意可选项参数offset的设置。对于已存在且读写过的文件,文件偏移指针默认在上次读写操作的终止位置

以流的形式读写文件

// pages/xxx.ets
import { fileIo as fs, ReadOptions } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';

// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;

async function readWriteFileWithStream(): Promise<void> {
  // 创建并打开输入文件流
  let inputStream = fs.createStreamSync(filesDir + '/test.txt', 'r+');
  // 创建并打开输出文件流
  let outputStream = fs.createStreamSync(filesDir + '/destFile.txt', "w+");

  let bufSize = 4096;
  let readSize = 0;
  let buf = new ArrayBuffer(bufSize);
  let readOptions: ReadOptions = {
    offset: readSize,
    length: bufSize
  };
  // 以流的形式读取源文件内容并写入到目标文件
  let readLen = await inputStream.read(buf, readOptions);
  readSize += readLen;
  while (readLen > 0) {
    const writeBuf = readLen < bufSize ? buf.slice(0, readLen) : buf;
    await outputStream.write(writeBuf);
    readOptions.offset = readSize;
    readLen = await inputStream.read(buf, readOptions);
    readSize += readLen;
  }
  // 关闭文件流
  inputStream.closeSync();
  outputStream.closeSync();
}

说明

使用流接口时,需注意流的及时关闭。同时流的异步接口应严格遵循异步接口使用规范,避免同步、异步接口混用。流接口不支持并发读写。

查看文件列表

import { fileIo as fs, Filter, ListFileOptions } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';

// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;

// 查看文件列表
function getListFile(): void {
  let listFileOption: ListFileOptions = {
    recursion: false,
    listNum: 0,
    filter: {
      suffix: [".png", ".jpg", ".txt"],
      displayName: ["test*"],
      fileSizeOver: 0,
      lastModifiedAfter: new Date(0).getTime()
    }
  };
  let files = fs.listFileSync(filesDir, listFileOption);
  for (let i = 0; i < files.length; i++) {
    console.info(`The name of file: ${files[i]}`);
  }
}

使用文件流

// pages/xxx.ets
import { fileIo as fs } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';

// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;

function copyFileWithReadable(): void {
  // 创建文件可读流
  const rs = fs.createReadStream(`${filesDir}/read.txt`);
  // 创建文件可写流
  const ws = fs.createWriteStream(`${filesDir}/write.txt`);
  // 暂停模式拷贝文件。在拷贝数据时,将原始数据暂停,然后将数据复制到另一个位置,适用于对数据完整性和一致性要求较高的场景
  rs.on('readable', () => {
    const data = rs.read();
    if (!data) {
      return;
    }
    ws.write(data);
  });
}

function copyFileWithData(): void {
  // 创建文件可读流
  const rs = fs.createReadStream(`${filesDir}/read.txt`);
  // 创建文件可写流
  const ws = fs.createWriteStream(`${filesDir}/write.txt`);
  // 流动模式拷贝文件。数据的读取和写入是同时进行的,不需要暂停原始数据的访问,适用于对数据实时性要求较高的场景
  rs.on('data', (emitData) => {
    const data = emitData?.data;
    if (!data) {
      return;
    }
    ws.write(data as Uint8Array);
  });
}

使用文件哈希流

哈希流是一种数据传输和存储技术,可以将任意长度的数据转换为固定长度的哈希值来验证数据的完整性和一致性。以下代码演示了如何使用文件哈希处理接口(ohos.file.hash)来处理文件哈希流。

// pages/xxx.ets
import { fileIo as fs } from '@kit.CoreFileKit';
import { hash } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';

// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;

function hashFileWithStream() {
  const filePath = `${filesDir}/test.txt`;
  // 创建文件可读流
  const rs = fs.createReadStream(filePath);
  // 创建哈希流
  const hs = hash.createHash('sha256');
  rs.on('data', (emitData) => {
    const data = emitData?.data;
    hs.update(new Uint8Array(data?.split('').map((x: string) => x.charCodeAt(0))).buffer);
  });
  rs.on('close', async () => {
    const hashResult = hs.digest();
    const fileHash = await hash.hash(filePath, 'sha256');
    console.info(`hashResult: ${hashResult}, fileHash: ${fileHash}`);
  });
}

用户文件概述

用户文件:登录到该终端设备的用户所拥有的文件,包括用户私有的图片、视频、音频、文档等。应用进程在访问时需要进行授权访问。

用户文件特性

1.用户文件存放在用户目录下,归属于该设备上登录的用户。

2.用户文件存储位置主要分为内置存储、外置存储。

3.应用对用户文件的创建、访问、删除等行为,需要提前获取用户授权,或由用户操作完成。

用户文件分类

按照文件的特征/属性,以及用户/应用的使用习惯,可分为:

1、图片/视频类媒体文件

所具有的特征包括拍摄时间、地点、旋转角度、文件宽高等信息,以媒体文件的形式存储在系统中,通常是以所有文件、相册的形式对外呈现,不会展示其在系统中存储的具体位置

2、音频类媒体文件

所具有的特征包括所属专辑、音频创作者、持续时间等信息,以媒体文件的形式存储在系统中,通常会以所有文件、专辑、作家等形式对外部呈现,不会展示其在系统中存储的具体位置。

3、其他文件(统称为文档类文件)

以普通文件的形式存储在系统中,该类文件既包括普通的文本文件、压缩文件等,又包括以普通文件形式存储的图片/视频、音频文件,该类文件通常是以目录树的形式对外展示。

uri介绍

在对用户文件进行访问与修改等操作时往往都会使用到uri,用户文件uri是文件的唯一标识。uri的生成和使用如下图所示。

文档类uri获取方式

  • 通过DocumentViewPicker接口选择或保存文件,返回选择或保存的文件uri。
  • 通过AudioViewPicker接口选择或保存文件,返回选择或保存的文件uri。

媒体文件uri获取方式

  • 通过PhotoAccessHelper的PhotoViewPicker选择媒体文件,返回选择的媒体文件文件的uri。
  • 通过photoAccessHelper模块中的getAssets或createAsset接口获取媒体文件对应文件的uri。

安全控件与Picker

安全控件与Picker用于帮助开发者获取文件uri,并且不需要进行权限的配置和获取

安全控件是系统提供的一组系统实现的ArkUI组件,应用集成这类组件就可以实现在用户点击后自动授权,而无需弹窗授权。它们可以作为一种“特殊的按钮"融入应用页面,实现用户点击即许可的设计思路。

Picker

相机、文件管理、联系人等系统应用提供了系统Picker组件,支持开发者无需申请权限,即可使用系统应用的一些常用功能,比如访问用户的资源文件

以下为开发者经常用到的一些场景:

  • 拉起位置权限设置弹窗
  • 拉起相机权限设置弹窗
  • 拉起图片与视频权限设置弹窗
  • 拉起音乐和音频权限设置弹窗
  • 拉起通讯录权限设置弹窗
  • 拉起日历权限设置弹窗

说明:

由于系统Picker已经获取了对应权限的预授权,开发使用系统Picker时,无需再次申请权限也可临时受限访问对应的资源。例如,当应用需要读取用户图片时,可通过使用照片Picker,在用户选择所需要的图片资源后,直接返回该图片资源,而不需要授予应用读取图片文件的权限,系统Picker由系统独立进程实现

用户文件访问和操作

选择用户文件

用户需要分享文件、保存图片、视频等用户文件时,开发者可以通过系统预置的文件选择器(FilePicker),实现该能力。通过Picker访问相关文件,将拉起对应的应用,引导用户完成界面操作,接口本身无需申请权限。picker获取的uri只具有临时权限,获取持久化权限需要通过FilePicker设置永久授权方式获取。

根据用户文件的常见类型,选择器(FilePicker)分别提供以下选项:

  • PhotoViewPicker适用于图片或视频类型文件的选择与保存(该接口在后续版本不再演进)。请使用PhotoAccessHelper的PhotoViewPicker来选择图片文件。请使用安全控件保存媒体库资源

  • DocumentViewPicker适用于文件类型文件的选择与保存。DocumentViewPicker对接的选择资源来自于FilePicker, 负责文件类型的资源管理,文件类型不区分后缀,比如浏览器下载的图片、文档等,都属于文件类型。

  • AudioViewPicker适用于音频类型文件的选择与保存。AudioViewPicker目前对接的选择资源来自于FilePicker。

选择图片或视频类文件

PhotoViewPicker在后续版本不再演进,请PhotoAccessHelper的PhotoViewPicker来选择图片文件。

select(option?:PhotoSelectOptions):Promise<Array<string>>通过选择模式拉起photoPicker界面,用户可以选择一个或多个文件。接口采用Promise异步返回形式,传入可选参数PhotoSelectOptions对象,返回用户选择的文件的uri数组。如果不传入option参数,则默认选择媒体文件类型为图片和视频类型,默认选择媒体文件数量的最大值为50。

说明:

select返回的uri权限是只读权限,可以根据结果集中ur进行读取文件数据操作。注意不能在picker的回调里直接使用此uri进行打开文件操作,需要定义一个全局变量保存uri,类似使用一个按钮去触发打开文件。可参考指定uri读取文件数据。也可以通过返回的uri获取图片或视频资源如有获取元数据需求,可以通过文件管理接口和文件ur根据uri获取部分文件属性信息,比如文件大小、访问时间、修改时间、文件名、文件路径等。

选择文档类文件

select(option?:DocumentSelectOptions):Promise<Array<string>>通过选择模式拉起documentPicker界面,用户可以选择一个或多个文件。接口采用Promise异步返回形式,传入可选参数DocumentSelectOptions对象,返回用户选择的文件的uri数组。如果不传入option参数,则拉起documentPicker主界面。

  • 导入选择器模块和基础文件API模块。

    import  { picker } from '@kit.CoreFileKit';
    import { fileIo as fs } from '@kit.CoreFileKit';
    import { common } from '@kit.AbilityKit';
    import { BusinessError } from '@kit.BasicServicesKit';
  • 创建文件类型、文件选择选项实例。
const documentSelectOptions = new picker.DocumentSelectOptions();
// 选择文档的最大数目(可选)。
documentSelectOptions.maxSelectNumber = 5;
// 指定选择的文件或者目录路径(可选)。
documentSelectOptions.defaultFilePathUri = "file://docs/storage/Users/currentUser/test";
// 选择文件的后缀类型['后缀类型描述|后缀类型'](可选) 若选择项存在多个后缀名,则每一个后缀名之间用英文逗号进行分隔(可选),后缀类型名不能超过100,选择所有文件:'所有文件(*.*)|.*'。
 documentSelectOptions.fileSuffixFilters = ['图片(.png, .jpg)|.png,.jpg', '文档|.txt', '视频|.mp4', '.pdf']; 
//选择是否对指定文件或目录授权,true为授权,当为true时,defaultFilePathUri为必选参数,拉起文管授权界面;false为非授权(默认为false),拉起常规文管界面(可选),仅支持2in1设备。
documentSelectOptions.authMode = false;
  • 创建文件选择器DocumentViewPicker实例。调用select()接口拉起FilePicker应用界面进行文件选择。
    let uris: Array<string> = [];
    let context = getContext(this) as common.Context; // 请确保 getContext(this) 返回结果为 UIAbilityContext
    // 创建文件选择器实例
    const documentViewPicker = new picker.DocumentViewPicker(context);
    documentViewPicker.select(documentSelectOptions).then((documentSelectResult: Array<string>) => {
      //文件选择成功后,返回被选中文档的uri结果集。
      uris = documentSelectResult;
      console.info('documentViewPicker.select to file succeed and uris are:' + uris);
    }).catch((err: BusinessError) => {
      console.error(`Invoke documentViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
    })

    注意

    1、使用picker获取的select()返回的uri权限是临时只读权限,待退出应用后台后,获取的临时权限就会失效。

    2、如果想要获取持久化权限(仅在2in1设备上生效),请参考文件持久化授权访问

    3、开发者可以根据结果集中uri做进一步的处理。建议定义一个全局变量保存uri。

    4、如有获取元数据需求,可以通过基础文件API文件URI根据uri获取部分文件属性信息,比如文件大小、访问时间、修改时间、文件名、文件路径等。

  • 待界面从FilePicker返回后,使用基础文件API的fs.openSync接口通过uri打开这个文件得到文件描述符(fd)。
let uri: string = '';
//这里需要注意接口权限参数是fs.OpenMode.READ_ONLY。
let file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
console.info('file fd: ' + file.fd);
  • 通过fd使用fs.readSync接口读取这个文件内的数据。
let buffer = new ArrayBuffer(4096);
let readLen = fs.readSync(file.fd, buffer);
console.info('readSync data to file succeed and buffer size is:' + readLen);
//读取完成后关闭fd。
fs.closeSync(file);

    选择音频类文件

    select(option?:AudioSelectOptions):Promise<Array<string>>通过选择模式拉起audioPicker界面,用户可以选择一个或多个音频文件。接口采用Promise异步返回形式,传入可选参数AudioSelectOptions对象,返回用户选择的音频文件的uri数组。如果不传入option参数,则拉起audioPicker主界面。

    • 导入选择器模块和基础文件API模块。
    import  { picker } from '@kit.CoreFileKit';
    import { fileIo as fs } from '@kit.CoreFileKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { common } from '@kit.AbilityKit';
    • 创建音频类型文件选择选项实例。说明:目前AudioSelectOptions不支持参数配置,默认可以选择所有类型的用户文件。
    const audioSelectOptions = new picker.AudioSelectOptions();
    • 创建音频选择器AudioViewPicker实例。调用select()接口拉起FilePicker应用界面进行文件选择。
      let uris: string = '';
      // 请确保 getContext(this) 返回结果为 UIAbilityContext
      let context = getContext(this) as common.Context; 
      const audioViewPicker = new picker.AudioViewPicker(context);
      audioViewPicker.select(audioSelectOptions).then((audioSelectResult: Array<string>) => {
        //文件选择成功后,返回被选中音频的uri结果集。
        uris = audioSelectResult[0];
        console.info('audioViewPicker.select to file succeed and uri is:' + uris);
      }).catch((err: BusinessError) => {
        console.error(`Invoke audioViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
      })

    注意

    1、使用picker获取的select()返回的uri权限是临时只读权限,待退出应用后台后,获取的临时权限就会失效。

    2、如果想要获取持久化权限(仅在2in1设备上生效),请参考文件持久化授权访问

    3、开发者可以根据结果集中的uri做读取文件数据操作。建议定义一个全局变量保存uri。例如通过基础文件API根据uri拿到音频资源的文件描述符(fd),再配合媒体服务实现音频播放的开发,具体请参考音频播放开发指导

    let uri: string = '';
    //这里需要注意接口权限参数是fs.OpenMode.READ_ONLY。
    let file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
    console.info('file fd: ' + file.fd);
    let buffer = new ArrayBuffer(4096);
    let readLen = fs.readSync(file.fd, buffer);
    console.info('readSync data to file succeed and buffer size is:' + readLen);
    //读取完成后关闭fd。
    fs.closeSync(file);

    保存用户文件

    在从网络下载文件到本地或将已有用户文件另存为新的文件路径等场景下,需要使用FilePicker提供的保存用户文件的能力。需关注以下关键点:

    权限说明

    • 通过Picker获取的URI默认只具备临时读写权限,临时授权在应用退出后台自动失效。
    • 获取持久化权限需要通过FilePicker设置永久授权方式获取。(仅限2in1设备)。
    • 使用picker对音频、图片、视频、文档类文件的保存操作无需申请权限

    系统隔离说明

    • 通过Picker保存的文件存储在用户指定的目录。此类文件与图库管理的资源隔离,无法在图库中看到
    • 若开发者需要保存图片、视频资源到图库,可使用用户无感的安全控件进行保存

    保存图片或视频类文件

    PhotoViewPicker在后续版本不再演进,建议使用Media Library Kit(媒体文件管理服务)中能力来保存媒体库资源

    如果开发场景无法调用安全控件进行图片、视频保存,可使用相册管理模块PhotoAccessHelper.showAssetsCreationDialog接口进行保存操作。

    PhotoAccessHelper的showAssetsCreationDialog(srcFileUris: Array<string>, photoCreationConfigs: Array<PhotoCreationConfig>): Promise<Array<string>>调用接口拉起保存确认弹窗。用户同意保存后,返回已创建并授予保存权限的uri列表,该列表永久生效,应用可使用该uri写入图片/视频。如果用户拒绝保存,将返回空列表。

    • srcFileUris:需保存到媒体库中的图片/视频文件对应的媒体库uri。仅支持处理图片、视频uri。不支持手动拼接的uri,需调用接口获取。
    • photoCreationConfigs:保存图片/视频到媒体库的配置,包括保存的文件名等,与srcFileUris保持一一对应。

    保存文档类文件

    save(option?:DocumentSaveOptions):Promise<Array<string>>:通过保存模式拉起documentPicker界面,用户可以保存一个或多个文件。接口采用Promise异步返回形式,传入可选参数DocumentSaveOptions对象,返回保存文件的uri数组。如果不传入option参数,则拉起documentPicker界面需要用户自行输入保存的文件名

    • 模块导入。

    import { picker } from '@kit.CoreFileKit';
    import { fileIo as fs } from '@kit.CoreFileKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { common } from '@kit.AbilityKit';
    • 配置保存选项。
    // 创建文件管理器选项实例。
    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);
    documentViewPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
      uris = documentSaveResult;
      console.info('documentViewPicker.save to file succeed and uris are:' + uris);
    }).catch((err: BusinessError) => {
      console.error(`Invoke documentViewPicker.save failed, code is ${err.code}, message is ${err.message}`);
    })

    注意

    1、URI存储建议:

    • 建议不在Picker的回调里直接使用此uri进行打开文件操作,需要定义一个全局变量保存uri。

    • 建议使用全局变量保存URI以供后续使用。​​​​

    2、使用Picker的save()接口获取到uri的权限是临时读写权限,待退出应用后台后,获取的临时权限就会失效。

    3、如果想要获取持久化权限(仅在2in1设备上生效),请参考文件持久化授权访问。

    4、可以通过便捷方式,直接将文件保存到Download目录下。可以通过DOWNLOAD模式直达下载目录。

    const uri = '';
    //这里需要注意接口权限参数是fs.OpenMode.READ_WRITE。
    let file = fs.openSync(uri, fs.OpenMode.READ_WRITE);
    console.info('file fd: ' + file.fd);
    let writeLen: number = fs.writeSync(file.fd, 'hello, world');
    console.info('write data to file succeed and size is:' + writeLen);
    fs.closeSync(file);

    保存音频类文件

    • 模块导入。

    import { picker } from '@kit.CoreFileKit';
    import { fileIo as fs } from '@kit.CoreFileKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { common } from '@kit.AbilityKit';
    • 配置保存选项。
    const audioSaveOptions = new picker.AudioSaveOptions();
    // 保存文件名(可选) 
    audioSaveOptions.newFileNames = ['AudioViewPicker01.mp3']; 
    let uri: string = '';
    // 请确保 getContext(this) 返回结果为 UIAbilityContext
    let context = getContext(this) as common.Context; 
    const audioViewPicker = new picker.AudioViewPicker(context);
    audioViewPicker.save(audioSaveOptions).then((audioSelectResult: Array<string>) => {
      uri = audioSelectResult[0];
      console.info('audioViewPicker.save to file succeed and uri is:' + uri);
    }).catch((err: BusinessError) => {
      console.error(`Invoke audioViewPicker.save failed, code is ${err.code}, message is ${err.message}`);
    })

    注意

    1、URI存储建议:

    • 避免在Picker回调中直接操作URI。

    • 建议使用全局变量保存URI以供后续使用

    2、快捷保存:

    //这里需要注意接口权限参数是fileIo.OpenMode.READ_WRITE。
    let file = fs.openSync(uri, fs.OpenMode.READ_WRITE);
    console.info('file fd: ' + file.fd);
    let writeLen = fs.writeSync(file.fd, 'hello, world');
    console.info('write data to file succeed and size is:' + writeLen);
    fs.closeSync(file);

    DOWNLOAD模式保存文件

    用户在使用save接口时,可以将pickerMode配置为DoWNLOAD模式,该模式下会在公共路径download目录下创建用户当前hap包名的文件夹,并通过save接口返回值回传相应的uri,后续用户可以直接将文件保存在该uri下返回的uri已具备持久化权限,用户可以在该uri下创建文件。

    模式特点

    • 自动创建在Download/包名/目录。
    • 跳过文件选择界面直接保存。
    • 返回的URI已具备持久化权限, 用户可在该URI下创建文件
    1. 模块导入。

      import { fileUri, picker } from '@kit.CoreFileKit';
      import { fileIo as fs } from '@kit.CoreFileKit';
      import { BusinessError } from '@kit.BasicServicesKit';
      import { common } from '@kit.AbilityKit';
      1. 配置下载模式。
      const documentSaveOptions = new picker.DocumentSaveOptions();
      // 配置保存的模式为DOWNLOAD,若配置了DOWNLOAD模式,此时配置的其他documentSaveOptions参数将不会生效。
      documentSaveOptions.pickerMode = picker.DocumentPickerMode.DOWNLOAD; 
      1. 保存到下载目录。
      let uri: string = '';
      // 请确保 getContext(this) 返回结果为 UIAbilityContext
      let context = getContext(this) as common.Context; 
      const documentViewPicker = new picker.DocumentViewPicker(context);
      const documentSaveOptions = new picker.DocumentSaveOptions();
      documentSaveOptions.pickerMode = picker.DocumentPickerMode.DOWNLOAD;
      documentViewPicker.save(documentSaveOptions ).then((documentSaveResult: Array<string>) => {
        uri = documentSaveResult[0];
        console.info('documentViewPicker.save succeed and uri is:' + uri);
        const testFilePath = new fileUri.FileUri(uri + '/test.txt').path;
        const file = fs.openSync(testFilePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
        fs.writeSync(file.fd, 'Hello HarmonyOS');
        fs.closeSync(file.fd);
      }).catch((err: BusinessError) => {
        console.error(`Invoke documentViewPicker.save failed, code is ${err.code}, message is ${err.message}`);
      })

      安全控件方式保存媒体资源

      SaveButton:用户通过点击该保存按钮,可以临时获取存储权限,而不需要权限弹框授权确认。

      说明:

      安全控件因其自动授权的特性,为了保障用户的隐私不被恶意应用获取,针对安全控件作了很多的限制。应用开发者需保证安全控件在应用界面上清晰可见、用户能明确识别,防止因覆盖、混淆等因素导致授权失败。

      当因控件样式不合法导致授权失败的情况发生时,请开发者检查设备错误日志,过滤关键字"SecurityComponentCheckFail"可以获取具体原因。

      授权持久化

      场景介绍

      应用通过Picker获取临时授权,临时授权在应用退出后或者设备重启后会清除,如果应用重启或者设备重启后需要直接访问之前已访问过的文件,则需对文件进行持久化授权

      通过Picker获取临时授权并进行授权持久化

      通过Picker选择文件或文件夹进行临时授权,然后应用可以按需通过文件分享接口(ohos.fileshare)进行持久化授权。

      1.应用仅临时需要访问公共目录的数据,例如:通讯类应用需要发送用户的文件或者图片。应用调用Picker的(select)接口选择需要发送的文件或者图片,此时应用获取到是该文件的临时访问权限,应用重启或者设备重启后,再次访问该文件则仍需使用Picker进行文件选择。

      申请权限

      2.应用如果需要长期访问某个文件或目录时,可以通过Picker选择文件或文件夹进行临时授权,然后利用persistPermission接口(ohos.fileshare.persistPermission)对授权进行持久化(在授权方同意被持久化的情况下),例如:文档编辑类应用本次编辑完一个用户文件,期望在历史记录中可以直接选中打开,无需再拉起Picker进行选择授权。

      可使用canIUse接口,确认设备是否具有以下系统能力:SystemCapability.FileManagement.AppFileService.FolderAuthorization。

      if (!canIUse('SystemCapability.FileManagement.AppFileService.FolderAuthorization')) {
          console.error('this api is not supported on this device');
          return;
      }

      需要权限

      ohos.permission.FILE_ACCESS_PERSIST,具体参考访问控制-申请应用权限

      示例:

      import { BusinessError } from '@kit.BasicServicesKit';
      import { picker } from '@kit.CoreFileKit';
      import { fileShare } from '@kit.CoreFileKit';
      
      async function persistPermissionExample() {
          try {
              let DocumentSelectOptions = new picker.DocumentSelectOptions();
              let documentPicker = new picker.DocumentViewPicker();
              let uris = await documentPicker.select(DocumentSelectOptions);
              let policyInfo: fileShare.PolicyInfo = {
                  uri: uris[0],
                  operationMode: fileShare.OperationMode.READ_MODE,
              };
              let policies: Array<fileShare.PolicyInfo> = [policyInfo];
              fileShare.persistPermission(policies).then(() => {
                  console.info("persistPermission successfully");
              }).catch((err: BusinessError<Array<fileShare.PolicyErrorResult>>) => {
                  console.error("persistPermission failed with error message: " + err.message + ", error code: " + err.code);
                  if (err.code == 13900001 && err.data) {
                      for (let i = 0; i < err.data.length; i++) {
                          console.error("error code : " + JSON.stringify(err.data[i].code));
                          console.error("error uri : " + JSON.stringify(err.data[i].uri));
                          console.error("error reason : " + JSON.stringify(err.data[i].message));
                      }
                  }
              });
          } catch (error) {
              let err: BusinessError = error as BusinessError;
              console.error('persistPermission failed with err: ' + JSON.stringify(err));
          }
      }

      注意

      1、持久化授权文件信息建议应用在本地存储数据,供后续按需激活持久化文件。

      2、持久化授权的数据存储在系统的数据库中,应用或者设备重启后需要激活已持久化的授权才可以正常使用激活持久化授权

      3、持久化权限接口(仅在2in1上生效可以使用canIUse接口进行校验能力是否可用),且需要申请对应的权限。

      4、应用在卸载时会将之前的授权数据全部清除,重新安装后需要重新授权。

      备注

      C/C++持久化授权接口说明及开发指南具体参考:OH_FileShare_PersistPermission持久化授权接口

      取消授权

      3.可以通过revokePermission接口(ohos.fileshare.revokePermission)对已持久化的文件取消授权,同时更新应用存储的数据以删除最近访问数据。

      需要权限

      ohos.permission.FILE_ACCESS_PERSIST,具体参考访问控制-申请应用权限

      示例:

      import { BusinessError } from '@kit.BasicServicesKit';
      import { picker } from '@kit.CoreFileKit';
      import { fileShare } from '@kit.CoreFileKit';
      
      async function revokePermissionExample() {
          try {
              let uri = "file://docs/storage/Users/username/tmp.txt";
              let policyInfo: fileShare.PolicyInfo = {
                  uri: uri,
                  operationMode: fileShare.OperationMode.READ_MODE,
              };
              let policies: Array<fileShare.PolicyInfo> = [policyInfo];
              fileShare.revokePermission(policies).then(() => {
                  console.info("revokePermission successfully");
              }).catch((err: BusinessError<Array<fileShare.PolicyErrorResult>>) => {
                  console.error("revokePermission failed with error message: " + err.message + ", error code: " + err.code);
                  if (err.code == 13900001 && err.data) {
                      for (let i = 0; i < err.data.length; i++) {
                          console.error("error code : " + JSON.stringify(err.data[i].code));
                          console.error("error uri : " + JSON.stringify(err.data[i].uri));
                          console.error("error reason : " + JSON.stringify(err.data[i].message));
                      }
                  }
              });
          } catch (error) {
              let err: BusinessError = error as BusinessError;
              console.error('revokePermission failed with err: ' + JSON.stringify(err));
          }
      }

        注意

        1、示例中的uri来源自应用存储的持久化数据中。

        2、建议按照使用需求去激活对应的持久化权限,不要盲目的全量激活。

        3、持久化权限接口(仅在2in1上生效可以使用canIUse接口进行校验能力是否可用),且需要申请对应的权限。

        备注

        C/C++去持久化授权接口说明及开发指南具体参考:OH_FileShare_RevokePermission去持久化授权接口

        激活已经持久化的权限访问文件或目录

        对于应用已经持久化的授权,应用每次启动时实际未加载到内存中,需要应用按需进行手动激活已持久化授权的权限,通过activatePermission接口(ohos.fileshare.activatePermission)对已经持久化授权的权限进行使能操作,否则已经持久化授权的权限仍存在不能使用的情况

        需要权限

        ohos.permission.FILE_ACCESS_PERSIST,具体参考访问控制-申请应用权限

        示例:

        import { BusinessError } from '@kit.BasicServicesKit';
        import { picker } from '@kit.CoreFileKit';
        import { fileShare } from '@kit.CoreFileKit';
        
        async function activatePermissionExample() {
            try {
                let uri = "file://docs/storage/Users/username/tmp.txt";
                let policyInfo: fileShare.PolicyInfo = {
                    uri: uri,
                    operationMode: fileShare.OperationMode.READ_MODE,
                };
                let policies: Array<fileShare.PolicyInfo> = [policyInfo];
                fileShare.activatePermission(policies).then(() => {
                    console.info("activatePermission successfully");
                }).catch((err: BusinessError<Array<fileShare.PolicyErrorResult>>) => {
                    console.error("activatePermission failed with error message: " + err.message + ", error code: " + err.code);
                    if (err.code == 13900001 && err.data) {
                        for (let i = 0; i < err.data.length; i++) {
                            console.error("error code : " + JSON.stringify(err.data[i].code));
                            console.error("error uri : " + JSON.stringify(err.data[i].uri));
                            console.error("error reason : " + JSON.stringify(err.data[i].message));
                            if (err.data[i].code == fileShare.PolicyErrorCode.PERMISSION_NOT_PERSISTED) {
                                //可以选择进行持久化后再激活。
                            }
                        }
                    }
                });
            } catch (error) {
                let err: BusinessError = error as BusinessError;
                console.error('activatePermission failed with err: ' + JSON.stringify(err));
            }
        }

        注意

        1、示例中的uri来源自应用存储的持久化数据中。

        2、建议按照使用需求去激活对应的持久化权限,不要盲目的全量激活。

        3、如果激活失败显示未持久化的权限可以按照示例进行持久化。

        4、持久化权限接口(仅在2in1上生效可以使用canIUse接口进行校验能力是否可用),且需要申请对应的权限。

        C/C++持久化授权激活接口说明及开发指南具体参考:OH_FileShare_ActivatePermission持久化授权激活接口

        Logo

        讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

        更多推荐