HarmonyOS 应用开发进阶案例(十五):实现文件管理案例实战

51 阅读7分钟

一、案例实现

(一) 案例效果截图

(二) 案例运用到的知识点

  1. 核心知识点
  • 文件管理能力 :该模块为基础文件操作API,提供基础文件操作能力,包括文件基本管理、文件目录管理、文件信息统计、文件流式读写等常用功能。(@ohos.file.fs)
  • 选择器 : 选择器(Picker)模块封装了PhotoViewPicker、DocumentViewPicker、AudioViewPicker等API模块,提供文件选择与保存能力。(@ohos.file.picker)
  • 相册管理模块 :该模块提供相册管理模块能力,包括创建相册以及访问、修改相册中的媒体数据信息等。(@ohos.file.photoAccessHelper)
  • PhotoViewPicker :图库选择器对象,用来支撑选择图片/视频和保存图片/视频等用户场景。
  • DocumentViewPicker :文件选择器对象,用来支撑选择和保存各种格式文档。
  1. 其他知识点
  • ArkTS 语言基础
  • V2版状态管理:@ComponentV2/@Local
  • 自定义组件
  • 自定义构建函数@Builder
  • 内置组件:SaveButton/Tabs/Scroll/Column/Text/TextArea/Button
  • 常量与资源分类的访问
  • MVVM模式

(三) 代码结构

├──entry/src/main/ets                      // 代码区
│  ├──common
│  │  └──utils
│  │     ├──Logger.ets                     // 日志打印类
│  │     ├──PictureSaving.ets              // 图片保存方法
│  │     ├──ReadFile.ets                   // 文件读取方法
│  │     ├──SavingAndSelectUserFile.ets    // 用户文件保存与选择方法
│  │     └──WriteFile.ets                  // 文件写入方法
│  ├──entryability
│  │  └──EntryAbility.ets                  // 程序入口类
│  ├──pages
│  │  └──HomePage.ets                      // 主界面  
│  └──view
│     ├──ApplicationFileTab.ets            // 应用文件功能展示
│     └──PublicFilesTab.ets                // 公共文件功能展示
└──entry/src/main/resources                // 资源文件目录

(四) 公共文件与资源

本案例涉及到的常量类和工具类代码如下:

  1. 通用日志类
// entry/src/main/ets/common/utils/Logger.ets
import { hilog } from '@kit.PerformanceAnalysisKit'

const LOGGER_PREFIX: string = 'File_Management'
class Logger {
  private domain: number
  private prefix: string
  private format: string = '%{public}s, %{public}s'

  constructor(prefix: string = '', domain: number = 0xFF00) {
    this.prefix = prefix
    this.domain = domain
  }

  debug(...args: string[]): void {
    hilog.debug(this.domain, this.prefix, this.format, args)
  }

  info(...args: string[]): void {
    hilog.info(this.domain, this.prefix, this.format, args)
  }

  warn(...args: string[]): void {
    hilog.warn(this.domain, this.prefix, this.format, args)
  }

  error(...args: string[]): void {
    hilog.error(this.domain, this.prefix, this.format, args)
  }
}

export default new Logger(LOGGER_PREFIX, 0xFF02)

本案例涉及到的资源文件如下:

  1. string.json
// entry/src/main/resources/base/element/string.json
{
  "string": [
    {
      "name": "module_desc",
      "value": "模块描述"
    },
    {
      "name": "EntryAbility_desc",
      "value": "description"
    },
    {
      "name": "EntryAbility_label",
      "value": "文件管理"
    },
    {
      "name": "textarea_default",
      "value": "请输入保存至文件的内容"
    },
    {
      "name": "file_content",
      "value": "文件内容"
    },
    {
      "name": "button1",
      "value": "保存至应用沙箱目录"
    },
    {
      "name": "button2",
      "value": "读取保存的文件内容"
    },
    {
      "name": "title",
      "value": "文件管理"
    },
    {
      "name": "bar1",
      "value": "应用文件"
    },
    {
      "name": "bar2",
      "value": "公共文件"
    },
    {
      "name": "select_photo",
      "value": "从图库中选择一张图片展示"
    },
    {
      "name": "textarea_default2",
      "value": "文件内容..."
    },
    {
      "name": "button3",
      "value": "保存test.txt至用户目录"
    },
    {
      "name": "button4",
      "value": "读取test.txt文件内容"
    }
  ]
}

2. color.json

// entry/src/main/resources/base/element/color.json
{
  "color": [
    {
      "name": "start_window_background",
      "value": "#FFFFFF"
    },
    {
      "name": "text_color",
      "value": "#182431"
    },
    {
      "name": "picture_background",
      "value": "#0D182431"
    }

  ]
}

3. float.json

// entry/src/main/resources/base/element/float.json
{
  "float": [
    {
      "name": "default_22",
      "value": "22vp"
    },
    {
      "name": "default_24",
      "value": "24vp"
    },
    {
      "name": "default_294",
      "value": "294vp"
    },
    {
      "name": "default_312",
      "value": "312vp"
    },
    {
      "name": "default_40",
      "value": "40vp"
    },
    {
      "name": "default_16",
      "value": "16vp"
    },
    {
      "name": "default_13",
      "value": "13vp"
    },
    {
      "name": "default_336",
      "value": "336vp"
    },
    {
      "name": "default_8",
      "value": "8vp"
    },
    {
      "name": "default_139",
      "value": "139vp"
    },
    {
      "name": "default_20",
      "value": "20vp"
    },
    {
      "name": "default_100",
      "value": "100vp"
    },
    {
      "name": "default_48",
      "value": "48vp"
    },
    {
      "name": "default_147",
      "value": "147vp"
    },
    {
      "name": "default_10",
      "value": "10vp"
    },
    {
      "name": "default_223",
      "value": "223vp"
    },
    {
      "name": "default_302",
      "value": "302vp"
    },
    {
      "name": "default_213",
      "value": "213vp"
    },
    {
      "name": "default_120",
      "value": "120vp"
    },
    {
      "name": "default_251",
      "value": "251vp"
    },
    {
      "name": "default_41",
      "value": "41vp"
    },
    {
      "name": "default_30",
      "value": "30vp"
    },
    {
      "name": "default_7",
      "value": "7vp"
    },
    {
      "name": "default_360",
      "value": "360vp"
    },
    {
      "name": "default_56",
      "value": "56vp"
    },
    {
      "name": "default_680",
      "value": "680vp"
    },
    {
      "name": "default_200",
      "value": "200vp"
    },
    {
      "name": "default_12",
      "value": "12vp"
    }
  ]
}

其他资源请到源码中获取。

(五) 沙箱路径下的文件读写

  1. 构建主界面
// entry/src/main/ets/pages/HomePage.ets
import { ApplicationFileTab } from '../views/ApplicationFileTab'

@Entry
@ComponentV2
struct HomePage {
  build() {
    Column() {
      Column() {
        Text($r('app.string.title'))
          .width($r('app.float.default_312'))
          .height($r('app.float.default_41'))
          .textAlign(TextAlign.Start)
          .fontSize($r('app.float.default_30'))
          .fontFamily('HarmonyHeiTi-Bold')
          .fontColor($r('app.color.text_color'))
          .lineHeight($r('app.float.default_41'))
          .fontWeight(700)
          .margin({
            top: $r('app.float.default_7'),
            bottom: $r('app.float.default_8'),
            left: $r('app.float.default_24'),
            right: $r('app.float.default_24')
          })
      }
      .width($r('app.float.default_360'))
      .height($r('app.float.default_56'))

      Tabs() {
        TabContent() {
          ApplicationFileTab()
        }
        .tabBar(new SubTabBarStyle($r('app.string.bar1'))
          .indicator({ marginTop: $r('app.float.default_8') })
          .labelStyle({ font: { size: $r('app.float.default_16') } })
        )

        TabContent() {
          Scroll() {
            Text('PublicFilesTab')
          }
          .height($r('app.float.default_680'))
        }
        .height('100%')
        .tabBar(new SubTabBarStyle($r('app.string.bar2'))
          .indicator({marginTop : $r('app.float.default_8')})
          .labelStyle({font : {size : $r('app.float.default_16')}})
        )
      }
      .barWidth($r('app.float.default_200'))
      .barHeight($r('app.float.default_56'))
      .width('100%')
    }
    .backgroundColor($r('app.color.picture_background'))
    .justifyContent(FlexAlign.Center)
    .width('100%')
  }
}

2. 应用文件组件

// entry/src/main/ets/views/ApplicationFileTab.ets
import { readFile } from '../common/utils/ReadFile'
import { writeFile } from '../common/utils/WriteFile'

@ComponentV2
export struct ApplicationFileTab {
  // 用于记录读取的内容。
  @Local message: string = ''
  // 用于记录文本框中的内容。
  @Local content: string = ''

  build() {
    Column() {
      Text($r('app.string.textarea_default'))
        .width($r('app.float.default_294'))
        .height($r('app.float.default_22'))
        .fontColor($r('app.color.text_color'))
        .fontWeight(500)
        .fontSize($r('app.float.default_16'))
        .fontFamily('HarmonyHeiTi-Medium')
        .lineHeight($r('app.float.default_22'))
        .textAlign(TextAlign.Start)
        .margin({
          top: $r('app.float.default_13'),
          bottom: $r('app.float.default_13'),
          right: $r('app.float.default_8')
        })
      TextArea({ text: this.content })
        .width($r('app.float.default_336'))
        .height($r('app.float.default_139'))
        .borderRadius($r('app.float.default_24'))
        .backgroundColor($r('app.color.start_window_background'))
        .enableKeyboardOnFocus(false)
        .onChange((value: string) => {
          this.content = value
        })
      Text($r('app.string.file_content'))
        .width($r('app.float.default_294'))
        .height($r('app.float.default_22'))
        .fontSize($r('app.float.default_16'))
        .lineHeight($r('app.float.default_22'))
        .fontWeight(500)
        .margin({
          top: $r('app.float.default_13'),
          bottom: $r('app.float.default_13'),
          right: $r('app.float.default_8')
        })
      TextArea({ text: this.message })
        .enableKeyboardOnFocus(false)
        .width($r('app.float.default_336'))
        .height($r('app.float.default_139'))
        .backgroundColor($r('app.color.start_window_background'))
        .borderRadius($r('app.float.default_24'))

      Column() {
        Button($r('app.string.button1'))
          .width($r('app.float.default_312'))
          .height($r('app.float.default_40'))
            // 写入信息并清空输入内容,点击“保存”后清空文本框。
          .onClick(() => {
            writeFile(this.content)
            this.content = ''
          })
        Button($r('app.string.button2'))
          .width($r('app.float.default_312'))
          .height($r('app.float.default_40'))
          .margin({
            top: $r('app.float.default_12'),
            bottom: $r('app.float.default_100')
          })
          .onClick(() => {
            this.message = readFile()
          })
      }
      .width('100%')
      .margin({ top: $r('app.float.default_100') })
    }
    .width('100%')
    .height('100%')
  }
}

3. 实现沙箱路径下文件的写入操作

// entry/src/main/ets/common/utils/WriteFile.ets
import { fileIo } from '@kit.CoreFileKit'
import { common } from '@kit.AbilityKit'

let context = getContext(this) as common.UIAbilityContext
let filesDir = context.filesDir

/**
 * writeFile.
 * 将内容写入文件
 * @param content 要写入文件的内容
 */
export function writeFile(content: string): void {
  let filePath = filesDir + '/test.txt'
  // 基于文件路径打开文件流
  let fileStream = fileIo.createStreamSync(filePath, "w+")
  fileStream.writeSync(content)
  fileStream.close()
}

关键代码说明:

  • 首先需要结合context得到想要操作的文件路径。
  • 然后对该文件进行写入操作,这里使用的是createStreamSync创建文件流的形式。
  • 通过文件流的方式可以控制文件的读写方式,覆盖写的方式可以使得每次写操作互不影响。
  1. 实现沙箱路径下文件的读取操作
// entry/src/main/ets/common/utils/ReadFile.ets
import { fileIo } from '@kit.CoreFileKit'
import { common } from '@kit.AbilityKit'
import { buffer } from '@kit.ArkTS'

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

/**
 * readFile.
 * 读取文件内容并返回字符串。
 * @return 字符串。
 */
export function readFile(): string {
  let filePath = filesDir + '/test.txt'
  let stat = fileIo.statSync(filePath)
  let size = stat.size
  let buf = new ArrayBuffer(size)
  // 基于文件路径打开文件流
  let fileStream = fileIo.createStreamSync(filePath, "r+")
  // 读取文件流信息
  fileStream.readSync(buf)
  // 将读取的信息转换为字符串类型并返回
  let con = buffer.from(buf, 0)
  res = con.toString()
  fileStream.close()
  return res
}

关键代码说明:

  • 与写入类似,也通过文件路径去创建文件流,进行读取操作。
  • 通过statSync获取文件的详细信息,进而得到文件内容的大小,然后利用文件流将文件内容读取到ArrayBuffer中,再将ArrayBuffer转化为string返回,即可完成文件的读取操作。

(六) 图库的读取与写入

  1. 在主界面引入公共文件组件
// entry/src/main/ets/pages/HomePage.ets
// ...
import { publicFilesTab } from '../views/PublicFilesTab'

@Entry
@ComponentV2
struct HomePage {
  build() {
    Column() {
      // ...

      Tabs() {
        // ...
        
        TabContent() {
          Scroll() {
            publicFilesTab()
          }
          // ...
        }
        // ...
      }
      // ...
    }
    // ...
  }
}

2. 构建公共文件组件(实现保存图片到图库)

// entry/src/main/ets/views/PublicFilesTab.ets
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { fileIo } from '@kit.CoreFileKit'
import Logger from '../common/utils/Logger'
import { photoPickerGetUri } from '../common/utils/PictureSaving'

@ComponentV2
export struct publicFilesTab {
  @Local picture: string = ''
  @Local flag: Boolean = false
  @Local message: string = ''
  @Local content: string = ''
  @Local isInput: Boolean = false
  // 设置安全控件的按钮属性
  @Local saveButtonOptions: SaveButtonOptions = {
    icon: SaveIconStyle.FULL_FILLED,
    text: SaveDescription.SAVE_IMAGE,
    buttonType: ButtonType.Capsule
  }

  build() {
    Column() {
      Column() {
        Image($r('app.media.img'))
          .borderRadius($r('app.float.default_24'))
          .width($r('app.float.default_312'))
          .height($r('app.float.default_147'))
        // 创建安全控件按钮
        SaveButton(this.saveButtonOptions)
          .onClick(async (event, result: SaveButtonOnClickResult) => {
            if (result == SaveButtonOnClickResult.SUCCESS) {
              try {
                Logger.info('createAsset 成功, event: ' + event)
                let context = getContext()
                let phAccessHelper = photoAccessHelper
                  .getPhotoAccessHelper(context)
                // 创建媒体文件
                let uri = await phAccessHelper.createAsset(
                  photoAccessHelper.PhotoType.IMAGE, 'jpg'
                )
                Logger.info('createAsset 成功, uri: ' + uri)
                // 打开创建的媒体文件,读取本地文件并转换为 ArrayBuffer,便于写入
                let file = await fileIo.open(uri, fileIo.OpenMode.READ_WRITE)
                let buffer = getContext(this).resourceManager
                  .getMediaContentSync($r('app.media.img').id)
                // 将读取的 ArrayBuffer 写入新的媒体文件
                let writeLen = await fileIo.write(file.fd, buffer.buffer)
                Logger.info('写入成功, 长度=' + writeLen)
                await fileIo.close(file)
              } catch (err) {
                Logger.error('createAsset 失败, message = ', err)
              }
            } else {
              Logger.error('SaveButtonOnClickResult createAsset 失败')
            }
          })
      }
      .margin({ bottom: $r('app.float.default_10') })
      .width($r('app.float.default_336'))
      .height($r('app.float.default_223'))
      .borderRadius($r('app.float.default_24'))
      .justifyContent(FlexAlign.SpaceAround)
      .backgroundColor($r('app.color.start_window_background'))

      Column() {
        Text($r('app.string.select_photo'))
          .width($r('app.float.default_302'))
          .height($r('app.float.default_48'))
          .lineHeight($r('app.float.default_22'))
          .fontFamily('HarmonyHeiTi-Medium')
          .fontSize($r('app.float.default_16'))
          .fontWeight(500)
          .textAlign(TextAlign.Start)
          .fontColor($r('app.color.text_color'))
        Column() {
          if (!this.flag) {
            Image($r('app.media.ic_folder_add2'))
              .width($r('app.float.default_24'))
              .height($r('app.float.default_24'))
              .objectFit(ImageFit.Contain)
          } else {
            Image(this.picture)
              .width('100%')
              .height('100%')
              .borderRadius($r('app.float.default_24'))
          }
        }
        .width($r('app.float.default_312'))
        .height($r('app.float.default_147'))
        .borderRadius($r('app.float.default_16'))
        .backgroundColor($r('app.color.picture_background'))
        .onClick(async () => {
          await photoPickerGetUri().then(value => {
            this.flag = true
            this.picture = value
          })
        })
        .justifyContent(FlexAlign.Center)
        .alignItems(HorizontalAlign.Center)
      }
      .margin({ bottom: $r('app.float.default_10') })
      .backgroundColor($r('app.color.start_window_background'))
      .width($r('app.float.default_336'))
      .height($r('app.float.default_213'))
      .borderRadius($r('app.float.default_24'))
    }
    .width('100%')
  }
}

关键代码说明:

  • 首先需要在图库中创建媒体资源,随后将图片读取转化为ArrayBuffer,最后再将该内容写入在图库中创建的媒体资源中。
  • createAsset方法会返回在图库中新建的媒体资源的uri,只不过此时还是没有内容的空文件,然后需要将本地图片文件的内容读取出来,并写入该空文件,即可完成图片保存到图库的操作。
  1. 从图库中选择图片进行展示
// entry/src/main/ets/common/utils/PictureSaving.ets
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { BusinessError } from '@kit.BasicServicesKit'
import Logger from './Logger'

// 定义 URI 数组,用于接收 PhotoViewPicker 选择图片后返回的 URI。
let uris: Array<string> = []

/**
 * photoPickerGetUri.
 * 根据 PhotoViewPicker 的 select 方法返回所选文件的 URI。
 * @return Promise<string>.
 */
export async function photoPickerGetUri(): Promise<string> {
  try {
    let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions()
    PhotoSelectOptions.MIMEType 
      = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
    PhotoSelectOptions.maxSelectNumber = 1
    let photoPicker = new photoAccessHelper.PhotoViewPicker()
    await photoPicker.select(PhotoSelectOptions)
      .then((PhotoSelectResult: photoAccessHelper.PhotoSelectResult) => {
        Logger.info('PhotoViewPicker.select 成功, 返回的 PhotoSelectResult uri: ' 
                    + JSON.stringify(PhotoSelectResult))
        uris = PhotoSelectResult.photoUris
      }).catch((err: BusinessError) => {
        Logger.error('PhotoViewPicker.select 失败, 错误信息: ' 
                     + JSON.stringify(err))
      })
  } catch (error) {
    let err = error as BusinessError
    Logger.error('PhotoViewPicker 失败, 错误信息: ' + err.message)
  }
  return uris[0].toString()
}

关键代码说明:

  • 调用PhotoViewPicker的select方法来得到被选择图片的uri,即可完成读取图库中图片的信息。

(七) 用户目录的文件读写

// entry/src/main/ets/common/utils/SavingAndSelectUserFile.ets
import { BusinessError } from '@kit.BasicServicesKit'
import { picker, fileIo } from '@kit.CoreFileKit'
import { buffer } from '@kit.ArkTS'
import Logger from './Logger'

let uri: string = ''
// 获取应用文件路径

let message: string = ''

/**
 * saveToUser.
 * 将内容保存到用户目录文件中
 * @param content 要保存到用户目录文件中的内容
 */
export async function saveToUser(content: string) {
  try {
    let DocumentSaveOptions = new picker.DocumentSaveOptions()
    DocumentSaveOptions.newFileNames = ['test.txt']
    let documentPicker = new picker.DocumentViewPicker()
    documentPicker.save(DocumentSaveOptions)
      .then((DocumentSaveResult: Array<string>) => {
        Logger.info('DocumentViewPicker.save 成功,返回的 uri: ' 
                    + JSON.stringify(DocumentSaveResult))
        uri = DocumentSaveResult[0]
        let file = fileIo.openSync(uri, fileIo.OpenMode.READ_WRITE)
        // 根据文件路径打开文件流。
        fileIo.writeSync(file.fd, content)
      }).catch((err: BusinessError) => {
        Logger.error('DocumentViewPicker.save 失败,错误信息: ' 
                     + JSON.stringify(err))
      })
  } catch (error) {
    let err: BusinessError = error as BusinessError
    Logger.error('DocumentViewPicker 失败,错误信息: ' + err.message)
  }
}

/**
 * readUserFile.
 * 读取用户目录文件中的内容。
 * @return Promise<string>.
 */
export async function readUserFile(): Promise<string> {
  try {
    let DocumentSelectOptions = new picker.DocumentSelectOptions()
    let documentPicker = new picker.DocumentViewPicker()
    await documentPicker.select(DocumentSelectOptions)
      .then((DocumentSelectResult: Array<string>) => {
        Logger.info('DocumentViewPicker.select 成功,返回的 uri: ' 
                    + JSON.stringify(DocumentSelectResult))
        uri = DocumentSelectResult[0]
        let file = fileIo.openSync(uri, fileIo.OpenMode.READ_WRITE)
        let stat = fileIo.statSync(file.fd)
        let size = stat.size
        let buf = new ArrayBuffer(size)
        fileIo.readSync(file.fd, buf)
        let con = buffer.from(buf, 0)
        message = con.toString()
        Logger.info('DocumentViewPicker.select 成功,读取的内容: ' + message)
        return message
      }).catch((err: BusinessError) => {
        Logger.error('DocumentViewPicker.select 失败,错误信息: ' 
                     + JSON.stringify(err))
      })
  } catch (error) {
    let err = error as BusinessError
    Logger.error('DocumentViewPicker.select 失败,错误信息: ' + err.message)
  }
  return message
}

关键代码说明:

  • 在DocumentViewPicker提供的save方法的帮助下,可以向用户目录下创建一个文件并返回它的uri。
  • DocumentViewPicker提供的select方法,可以选择想要访问的文件,并返回其uri。获取到uri后,即可完成对文件的读取,并将信息返回的操作。

二、文件管理知识点

Core File Kit(文件基础服务)为开发者提供统一的文件访问和管理能力,涵盖应用文件和用户文件,帮助用户高效地查找、管理、备份各类文件,满足多样化的文件操作需求。

在文件分类上,Core File Kit将文件划分为三类:应用文件,如安装包、资源、缓存等,由应用拥有和管理;用户文件,如图片、视频、文档等,由登录用户所有;系统文件,包括系统资源、设备文件等,由系统统一管理,开发者无需操作。按文件存储位置,分为本地文件系统(本地设备或外置设备)和分布式文件系统(支持跨设备访问)。

Core File Kit支持对应用文件进行查看、创建、读写、删除、复制、移动、获取属性等操作;支持将应用文件上传至服务器,或从服务器下载资源;支持查询应用及文件系统的空间使用情况;支持通过URI或文件描述符方式,实现跨应用文件分享;支持配置应用数据的备份与恢复;同时,提供统一的用户文件访问接口,便于用户文件的选择与保存;还支持在多设备间进行文件访问和传输。

亮点方面,Core File Kit采用沙箱隔离机制,为每个应用提供专属的文件目录空间,确保文件的隔离性与安全性。通过分享机制,开发者可安全、高效地实现应用间文件共享,确保数据一致性与传输安全,是构建HarmonyOS文件能力的基础组件之一。

(一) 应用文件

应用文件的所有者为应用,包括应用安装文件、应用资源文件、应用缓存文件等。

1. 应用沙箱目录

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

  • 对于每个应用,系统会在内部存储空间映射出一个专属的“应用沙箱目录”,它是“应用文件目录”与一部分系统文件(应用运行必需的少量系统文件)所在的目录组成的集合。
  • 应用沙箱限制了应用可见的数据范围。在“应用沙箱目录”中,应用仅能看到自己的应用文件以及少量的系统文件(应用运行必需的少量系统文件)。因此,本应用的文件也不为其他应用可见,从而保护了应用文件的安全。
  • 应用可以在“应用文件目录”下保存和处理自己的应用文件;系统文件及其目录对于应用是只读的;而应用若需访问用户文件,则需要通过特定API同时经过用户的相应授权才能进行。

下图展示了应用沙箱下,应用可访问的文件范围和方式。

2. 应用文件访问

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

  1. 接口说明

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

接口名功能接口类型支持同步支持异步
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等,建议使用异步接口,避免应用崩溃。

  1. 开发示例

在对应用文件开始访问前,开发者需要获取应用文件路径。

  • 新建并读写一个文件

以下示例代码演示了如何新建一个文件并对其读写。

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)
}
  • 读取文件内容并写入到另一个文件

以下示例代码演示了如何从一个文件读写内容到另一个文件。

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的设置。对于已存在且读写过的文件,文件偏移指针默认在上次读写操作的终止位置。

  • 以流的形式读写文件

以下示例代码演示了如何使用流接口读取test.txt的文件内容并写入到destFile.txt文件中。

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]}`)
  }
}
  • 使用文件流

以下示例代码演示了如何使用文件可读流,文件可写流。

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)来处理文件哈希流。

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. 用户文件uri

用户文件uri是文件的唯一标识,在对用户文件进行访问与修改等操作时往往都会使用到uri,不建议开发者解析uri中的片段用于业务代码开发。

uri类型可以归纳为文档类uri和媒体文件uri两类

  • 文档类uri:由picker拉起文件管理器选择或保存返回,以及通过fileAccess模块获取。
  • 媒体文件uri:由picker通过拉起图库选择图片或者视频返回,通过photoAccessHelper模块获取图片或者视频文件的uri,以及通过userFileManager模块获取图片、视频或者音频文件的uri。

2. 选择用户文件

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

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

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

图库选择器对象,用来支撑选择图片/视频等用户场景。在使用前,需要先创建PhotoViewPicker实例。

let photoPicker = new photoAccessHelper.PhotoViewPicker()
  • 用法一
select(option?: PhotoSelectOptions) : Promise<PhotoSelectResult>

通过选择模式拉起photoPicker界面,用户可以选择一个或多个图片/视频。接口采用promise异步返回形式,传入可选参数PhotoSelectOptions对象,返回PhotoSelectResult对象。示例如下:

import { BusinessError } from '@kit.BasicServicesKit'
async function example01() {
  try {
    let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions()
    PhotoSelectOptions.MIMEType 
      = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
    PhotoSelectOptions.maxSelectNumber = 5
    let photoPicker = new photoAccessHelper.PhotoViewPicker()
    photoPicker.select(PhotoSelectOptions)
      .then((PhotoSelectResult: photoAccessHelper.PhotoSelectResult) => {
        console.info('PhotoViewPicker.select successfully, PhotoSelectResult uri: ' 
                     + JSON.stringify(PhotoSelectResult))
      }).catch((err: BusinessError) => {
        console.error('PhotoViewPicker.select failed with' 
                      + `err: ${err.code}, ${err.message}`)
      })
  } catch (error) {
    let err: BusinessError = error as BusinessError;
    console.error('PhotoViewPicker failed with' 
                  + `err: ${err.code}, ${err.message}`)
  }
}
  • 用法二
select(
  option: PhotoSelectOptions, 
  callback: AsyncCallback<PhotoSelectResult>
): void

通过选择模式拉起photoPicker界面,用户可以选择一个或多个图片/视频。接口采用callback异步返回形式,传入参数PhotoSelectOptions对象,返回PhotoSelectResult对象。示例如下:

import { BusinessError } from '@kit.BasicServicesKit'
async function example02() {
  try {
    let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions()
    PhotoSelectOptions.MIMEType 
      = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
    PhotoSelectOptions.maxSelectNumber = 5
    let photoPicker = new photoAccessHelper.PhotoViewPicker();
    photoPicker.select(
      PhotoSelectOptions, 
      (
        err: BusinessError, 
        PhotoSelectResult: 
        photoAccessHelper.PhotoSelectResult
      ) => {
        if (err) {
          console.error('PhotoViewPicker.select failed with' 
                        + `err: ${err.code}, ${err.message}`)
          return
        }
        console.info('PhotoViewPicker.select successfully,PhotoSelectResult uri:' 
                     + JSON.stringify(PhotoSelectResult));
      }
    )
  } catch (error) {
    let err: BusinessError = error as BusinessError;
    console.error('PhotoViewPicker failed with' 
                  + `err: ${err.code}, ${err.message}`);
  }
}
  • 用法三
select(callback: AsyncCallback<PhotoSelectResult>) : void

通过选择模式拉起photoPicker界面,用户可以选择一个或多个图片/视频。接口采用callback异步返回形式,返回PhotoSelectResult对象。示例如下:

import { BusinessError } from '@kit.BasicServicesKit'
async function example03() {
  try {
    let photoPicker = new photoAccessHelper.PhotoViewPicker()
    photoPicker.select((
      err: BusinessError, 
      PhotoSelectResult: photoAccessHelper.PhotoSelectResult
    ) => {
      if (err) {
        console.error('PhotoViewPicker.select failed with' 
                      + `err: ${err.code}, ${err.message}`)
        return
      }
      console.info('PhotoViewPicker.select successfully,PhotoSelectResult uri:' 
                   + JSON.stringify(PhotoSelectResult))
    })
  } catch (error) {
    let err: BusinessError = error as BusinessError
    console.error(`PhotoViewPicker failed with err: ${err.code}, ${err.message}`)
  }
}

2. 选择文档类文件

  • 导入选择器模块和基础文件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
let uris: Array<string> = []
// 请确保 getContext(this) 返回结果为 UIAbilityContext
let context = getContext(this) as common.Context 
// 创建文件选择器实例
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}`)
  })
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)

3. 选择音频类文件

  • 导入选择器模块和基础文件API模块。
import  { picker } from '@kit.CoreFileKit'
import { fileIo as fs } from '@kit.CoreFileKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { common } from '@kit.AbilityKit'
  • 创建音频类型文件选择选项实例。
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}`)
  })
  • 待界面从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可以使用基础文件API的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)

3. 保存用户文件

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

  1. 保存图片或视频类文件

当用户需要保存图片、视频等用户文件到图库时,无需在应用中申请相册管理模块权限'ohos.permission.WRITE_IMAGEVIDEO',应用可以通过安全控件或授权弹窗的方式,将用户指定的媒体资源保存到图库中。

(1) 使用安全控件保存媒体库资源

下面以使用安全控件创建一张图片资源为例。开发步骤如下:

  • 设置安全控件按钮属性。
  • 创建安全控件按钮。
  • 调用MediaAssetChangeRequest.createImageAssetRequest和PhotoAccessHelper.applyChanges接口创建图片资源。
import { photoAccessHelper } from '@kit.MediaLibraryKit'

@Entry
@ComponentV2
struct Index {
  saveButtonOptions: SaveButtonOptions = {
    icon: SaveIconStyle.FULL_FILLED,
    text: SaveDescription.SAVE_IMAGE,
    buttonType: ButtonType.Capsule
  } // 设置安全控件按钮属性

  build() {
    Column() {
      SaveButton(this.saveButtonOptions) // 创建安全控件按钮
        .onClick(async (event, result: SaveButtonOnClickResult) => {
           if (result == SaveButtonOnClickResult.SUCCESS) {
             try {
               let context = getContext()
               let phAccessHelper 
                 = photoAccessHelper.getPhotoAccessHelper(context)
               // 需要确保fileUri对应的资源存在
               let fileUri 
                 = 'file://com.example.temptest/data/storage' 
                 + '/el2/base/haps/entry/files/test.jpg';
               let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest
                 = photoAccessHelper.MediaAssetChangeRequest
                   .createImageAssetRequest(context, fileUri)
               await phAccessHelper.applyChanges(assetChangeRequest)
               console.info('createAsset successfully, uri: ' 
                            + assetChangeRequest.getAsset().uri)
             } catch (err) {
               console.error('create asset failed with ' 
                             + `error: ${err.code}, ${err.message}`)
             }
           } else {
             console.error('SaveButtonOnClickResult create asset failed')
           }
        })
    }
    .width('100%')
  }
}

除了上述通过fileUri从应用沙箱指定资源内容的方式,开发者还可以通过ArrayBuffer的方式添加资源内容。

(2) 使用弹窗授权保存媒体库资源

下面以弹窗授权的方式保存一张图片资源为例。开发步骤如下:

  • 指定待保存到媒体库的位于应用沙箱的应用文件图片uri。
  • 指定待保存照片的创建选项,包括文件后缀和照片类型,标题和照片子类型可选。
  • 调用showAssetsCreationDialog,基于弹窗授权的方式获取的目标媒体文件uri。
  • 将来源于应用沙箱的照片内容写入媒体库的目标uri。
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { fileIo } from '@kit.CoreFileKit'

let context = getContext(this)
let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context)

async function example() {
  try {
    // 指定待保存到媒体库的位于应用沙箱的图片uri
    let srcFileUri = 'file://com.example.temptest/data/storage/'
      + 'el2/base/haps/entry/files/test.jpg'
    let srcFileUris: Array<string> = [
      srcFileUri
    ]
    // 指定待保存照片的创建选项,包括文件后缀和照片类型,标题和照片子类型可选
    let photoCreationConfigs: Array<photoAccessHelper.PhotoCreationConfig> = [
      {
        title: 'test', // 可选
        fileNameExtension: 'jpg',
        photoType: photoAccessHelper.PhotoType.IMAGE,
        subtype: photoAccessHelper.PhotoSubtype.DEFAULT, // 可选
      }
    ]
    // 基于弹窗授权的方式获取媒体库的目标uri
    let desFileUris: Array<string> 
      = await phAccessHelper.showAssetsCreationDialog(
        srcFileUris, photoCreationConfigs
      )
    // 将来源于应用沙箱的照片内容写入媒体库的目标uri
    let desFile: fileIo.File 
      = await fileIo.open(desFileUris[0], fileIo.OpenMode.WRITE_ONLY)
    let srcFile: fileIo.File 
      = await fileIo.open(srcFileUri, fileIo.OpenMode.READ_ONLY)
    await fileIo.copyFile(srcFile.fd, desFile.fd)
    fileIo.closeSync(srcFile)
    fileIo.closeSync(desFile)
    console.info('create asset by dialog successfully')
  } catch (err) {
    console.error('failed to create asset by dialog successfully ' 
                  + `errCode is: ${err.code}, ${err.message}`)
  }
}

2. 保存文档类文件

  • 模块导入。
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']
  • 创建文件选择器DocumentViewPicker实例。调用save()接口拉起FilePicker界面进行文件保存。
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}`)
  })
  • 待界面从FilePicker返回后,使用基础文件API的fs.openSync接口,通过URI打开这个文件得到文件描述符(fd)。
const uri = ''
//这里需要注意接口权限参数是fs.OpenMode.READ_WRITE。
let file = fs.openSync(uri, fs.OpenMode.READ_WRITE)
console.info('file fd: ' + file.fd)
  • 通过(fd)使用基础文件API的fs.writeSync接口对这个文件进行编辑修改,编辑修改完成后关闭(fd)。
let writeLen: number = fs.writeSync(file.fd, 'hello, world')
console.info('write data to file succeed and size is:' + writeLen)
fs.closeSync(file)

3. 保存音频类文件

  • 模块导入。
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']
  • 创建音频选择器AudioViewPicker实例。调用save()接口拉起FilePicker界面进行文件保存。
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}`)
  })
  • 待界面从FilePicker返回后,可以使用基础文件API的fs.openSync接口,通过URI打开这个文件得到文件描述符(fd)。
//这里需要注意接口权限参数是fileIo.OpenMode.READ_WRITE。
let file = fs.openSync(uri, fs.OpenMode.READ_WRITE)
console.info('file fd: ' + file.fd)
  • 通过(fd)使用基础文件API的fs.writeSync接口对这个文件进行编辑修改,编辑修改完成后关闭(fd)。
let writeLen = fs.writeSync(file.fd, 'hello, world')
console.info('write data to file succeed and size is:' + writeLen)
fs.closeSync(file)

4. DOWNLOAD模式保存文件

该模式具备自动在 Download/包名/ 路径创建目录、跳过文件选择界面直接保存文件、并返回具备持久化权限的URI供用户创建文件的能力。

  • 模块导入。
import { fileUri, 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()
// 配置保存的模式为DOWNLOAD,若配置了DOWNLOAD模式,
// 此时配置的其他documentSaveOptions参数将不会生效。
documentSaveOptions.pickerMode = picker.DocumentPickerMode.DOWNLOAD
  • 保存到下载目录。
let uri: string = ''
// 请确保 getContext(this) 返回结果为 UIAbilityContext
let context = getContext(this) as common.Context
const documentViewPicker = new picker.DocumentViewPiker(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}`)
  })

✋ 需要参加鸿蒙认证的请点击 鸿蒙认证链接