# 文件操作API的常见参数类型转换以及场景有哪些

#### 问题现象

常见文件操作API的参数涉及沙箱路径、文件句柄fd、文件uri，如何进行转换以及涉及常见转换场景有哪些？

#### 背景知识

在ArkTS中，@ohos.file.fs包中提供了多种文件操作API，包括创建、读取、写入、删除文件等。在这些常用操作API中，涉及待处理的文件参数，一般有以下几类：

*   待处理文件的应用沙箱路径。
    
    对于每个应用，系统会在内部存储空间映射出一个专属的“应用沙箱目录”，它是“应用文件目录”与一部分系统文件（应用运行必需的少量系统文件）所在的目录组成的集合。应用文件目录结构图如下：
    
    禁止直接使用上图中四级目录之前的目录名组成的路径字符串，否则可能导致后续应用版本因应用文件路径变化导致不兼容问题。正确的做法应通过上下文Context属性获取应用文件路径，包括但不限于上图中绿色背景的路径。
    
*   待处理文件的文件描述符（即fd标识）。
    
    ArkTS中系统对象File的属性，File对象的属性见以下：
    
*   待处理文件uri。
    
    uri（Uniform Resource Identifier）即文件统一资源标志符，是指向资源的字符串标识。
    

常见文件操作API的参数类型转换相关接口：

*   fs.openSync：以同步方法打开文件或目录。支持使用uri打开文件。
*   fileUri.getUriFromPath：通过传入的路径path生成应用自己的uri(不支持媒体类型uri的获取)；将path转uri时，路径中的中文及非数字字母的特殊字符将会被编译成对应的ASCII码，拼接在uri中。
*   fs.dup：复制文件描述符，并返回对应的File对象。
*   FileUri.getFullDirectoryUri：获取所在路径uri。uri指向文件则返回所在路径的uri，uri指向目录则不处理直接返回原串；uri指向的文件不存在或属性获取失败则返回空串。

#### 解决方案

一、文件操作API的参数类型的常见参数类型转换、以及常见业务场景如下：

针对上面背景知识描述，要想获取到标准规范的待处理文件参数，一般有安全沙箱目录，文件uri，文件fd三大类型的互联转换：

| 序号 | 参数转换 | 常见业务场景 | 转换方式 |
| --- | --- | --- | --- |
| 1 | sandboxPath -> fd | 应用拷贝rawfile目录下文件至沙箱进行读写 | let file = fs.openSync(sandboxPath); let fd = file.fd; |
| 2 | sandboxPath -> uri | 图片编辑场景下，获取图片uri后拉起图片编辑类应用 | let uri = fileUri.getUriFromPath(sandboxPath); |
| 3 | fd -> sandboxPath | 文件管理器类应用需要根据某一个文件获取整个目录下同类或其他文件 | let file = fs.dup(fd); let sandboxPath = file.path; |
| 4 | fd -> uri | 文件管理器类应用需要根据某一个文件获取整个目录下同类或其他文件 | let file = fs.dup(fd); fileUri.getUriFromPath(file.path); |
| 5 | uri -> fd | 应用打开文件管理器操作文本文件进行读写 | let file = fs.openSync(uri); let fd = file.fd; |
| 6 | uri -> sandboxPath | 应用打开文件管理器操作文本文件进行读写 | let file = fs.openSync(uri); let sandboxPath = file.path; |

二、文件操作API的参数类型的常见参数类型转换场景：

*   应用读取rawfile目录下文件至沙箱进行读写：
    
    **sandboxPath -> fd转换：** 一般通过fs.openSync接口，打开沙箱路径sandboxPath，获取File对象，进而获取文件描述符fd。
    
    ```
    // 1、sandboxPath转化fd
    let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    let content = context.resourceManager.getRawFileContentSync('testPic.png');
    // 获取沙箱文件sandboxPath：
    let sandboxPath = context.filesDir + '/testPic.png';
    // 通过fs.openSync接口获取File对象
    let file = fs.openSync(sandboxPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    fs.writeSync(file.fd, content.buffer);
    fs.closeSync(file);
    hilog.info(0x0000, TAG, `sandboxPath转化为fd 成功。`);
    ```
    
*   图片编辑场景下，获取图片uri后拉起图片编辑类应用：
    
    **sandboxPath -> uri：** 通过fileUri.getUriFromPath接口将沙箱文件转换为文件uri。
    
    ```
    let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    let sandboxPath = context.filesDir + 'testPic';
    // 2、sandboxPath转化uri
    let uri = fileUri.getUriFromPath(sandboxPath);
    let abilityStartCallback: common.AbilityStartCallback = {
      onError: (code, name, message) => {
        const tip: string = `code:` + code + ` name:` + name + ` message:` + message;
        hilog.error(0x0000, TAG, `startAbilityByType: ${tip}`);
      },
      onResult: (result) => {
        // 获取到回调结果中编辑后的图片uri并做对应的处理
        hilog.info(0x0000, TAG, `PhotoEditorCaller result: ${JSON.stringify(result)}`);
      }
    };
    context.startAbilityByType('photoEditor', {
      // 原始图片的uri,只支持传入一个uri
      'ability.params.stream': [uri],
      // 至少需要分享读权限给到图片编辑面板
      'ability.want.params.uriPermissionFlag': wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION
    
    
    } as Record<string, Object>, abilityStartCallback, (err) => {
      if (err) {
        hilog.error(0x0000, TAG, `startAbilityByType: fail, err: ${JSON.stringify(err)}`);
      } else {
        hilog.info(0x0000, TAG, 'startAbilityByType: success');
      }
    });
    ```
    
*   文件管理器类应用需要根据某一个文件获取整个目录下同类或其他文件：
    
    **fd -> sandboxPath：** 根据fs.dup(fd)接口获取File对象，从而获取沙箱路径sandboxPath。
    
    **fd -> uri：** 有上述步骤获取到沙箱路径sandboxPath；再通过fileUri.getUriFromPath（sandboxPath）获取uri对象。
    
    ```
    let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    let filePath = context.filesDir + 'testPic';
    let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    let fd: number = file.fd;
    // 通过fs.dup接口将fd转换为sandboxPath
    let sandboxPath = fs.dup(fd).path;
    hilog.info(0x0000, TAG, `sandboxPath is ${sandboxPath}`);
    // 再通过sandboxPath获取uri
    let uri = fileUri.getUriFromPath(sandboxPath);
    hilog.info(0x0000, TAG, `uri is ${uri}`);
    fs.closeSync(file);
    ```
    
*   应用打开文件管理器操作文本文件进行读写：
    
    **uri -> fd转换：** 一般通过fs.openSync接口，打开沙箱路径sandboxPath，获取File对象，进而获取文件描述符fd。
    
    **uri -> sandboxPath转换：** 需要使用fileUri.FileUri先获取fileUri对象，再通过FileUri.getFullDirectoryUri（fileUri）获取全目录json对象上的path属性，最后获得沙箱路径sandboxPath。
    
    ```
    let uris: Array<string> = [];
    let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    // 创建文件选择器实例
    const documentSelectOptions = new picker.DocumentSelectOptions();
    documentSelectOptions.maxSelectNumber = 1;
    const documentViewPicker = new picker.DocumentViewPicker(context);
    documentViewPicker.select(documentSelectOptions).then((documentSelectResult: Array<string>) => {
      // 文件选择成功后，返回被选中文档的uri结果集。
      uris = documentSelectResult;
      hilog.info(0x0000, TAG, `documentViewPicker.select to file succeed and uris are: ${uris}`);
      let file = fs.openSync(uris[0], fs.OpenMode.READ_ONLY);
      // 通过fs.openSync获取File对象，从而获得fd、sandboxPath
      hilog.info(0x0000, TAG, `file fd: ${file.fd}`);
      hilog.info(0x0000, TAG, `sandboxPath is ${file.path}`);
    }).catch((err: BusinessError) => {
      hilog.info(0x0000, TAG, `Invoke documentViewPicker.select failed, code is ${err.code},
      message is ${err.message}`);
    });
    ```
    

完整示例代码如下：

```
import { fileIo as fs, fileUri, picker } from '@kit.CoreFileKit';
import { common, wantConstant } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = 'documentConvert';

@Entry
@Component
struct Index {
  build() {
    Column({ space: 20 }) {
      Button('sandboxPath转化为fd')
        .onClick(() => {
          try {
            // 1、sandboxPath转化fd
            let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
            let content = context.resourceManager.getRawFileContentSync('testPic.png');
            // 获取沙箱文件sandboxPath：
            let sandboxPath = context.filesDir + '/testPic.png';
            // 通过fs.openSync接口获取File对象
            let file = fs.openSync(sandboxPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
            fs.writeSync(file.fd, content.buffer);
            fs.closeSync(file);
            hilog.info(0x0000, TAG, `sandboxPath转化为fd 成功。`);
          } catch (e) {
            let err = e as BusinessError;
            hilog.error(0x0000, TAG, `sandboxPath转化为fd 失败, code is ${err.code},message is ${err.message}`);
          }
        });

      Button('sandboxPath转化为uri')
        .onClick(() => {
          try {
            let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
            let sandboxPath = context.filesDir + 'testPic';
            // 2、sandboxPath转化uri
            let uri = fileUri.getUriFromPath(sandboxPath);
            let abilityStartCallback: common.AbilityStartCallback = {
              onError: (code, name, message) => {
                const tip: string = `code:` + code + ` name:` + name + ` message:` + message;
                hilog.error(0x0000, TAG, `startAbilityByType: ${tip}`);
              },
              onResult: (result) => {
                // 获取到回调结果中编辑后的图片uri并做对应的处理
                hilog.info(0x0000, TAG, `PhotoEditorCaller result: ${JSON.stringify(result)}`);
              }
            };
            context.startAbilityByType('photoEditor', {
              // 原始图片的uri,只支持传入一个uri
              'ability.params.stream': [uri],
              // 至少需要分享读权限给到图片编辑面板
              'ability.want.params.uriPermissionFlag': wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION

            } as Record<string, Object>, abilityStartCallback, (err) => {
              if (err) {
                hilog.error(0x0000, TAG, `startAbilityByType: fail, err: ${JSON.stringify(err)}`);
              } else {
                hilog.info(0x0000, TAG, 'startAbilityByType: success');
              }
            });
          } catch (e) {
            let err = e as BusinessError;
            hilog.error(0x0000, TAG, `sandboxPath转化为uri 失败, code is ${err.code},message is ${err.message}`);
          }
        });

      Button('fd转化sandboxPath以及fd转化uri')
        .onClick(() => {
          try {
            let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
            let filePath = context.filesDir + 'testPic';
            let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
            let fd: number = file.fd;
            // 通过fs.dup接口将fd转换为sandboxPath
            let sandboxPath = fs.dup(fd).path;
            hilog.info(0x0000, TAG, `sandboxPath is ${sandboxPath}`);
            // 再通过sandboxPath获取uri
            let uri = fileUri.getUriFromPath(sandboxPath);
            hilog.info(0x0000, TAG, `uri is ${uri}`);
            fs.closeSync(file);
          } catch (e) {
            let err = e as BusinessError;
            hilog.error(0x0000, TAG,
              `fd转化sandboxPath以及fd转化uri 失败, code is ${err.code},message is ${err.message}`);
          }
        });

      Button('uri->fd转换、uri->sandboxPath转换')
        .onClick(() => {
          try {
            let uris: Array<string> = [];
            let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
            // 创建文件选择器实例
            const documentSelectOptions = new picker.DocumentSelectOptions();
            documentSelectOptions.maxSelectNumber = 1;
            const documentViewPicker = new picker.DocumentViewPicker(context);
            documentViewPicker.select(documentSelectOptions).then((documentSelectResult: Array<string>) => {
              // 文件选择成功后，返回被选中文档的uri结果集。
              uris = documentSelectResult;
              hilog.info(0x0000, TAG, `documentViewPicker.select to file succeed and uris are: ${uris}`);
              let file = fs.openSync(uris[0], fs.OpenMode.READ_ONLY);
              // 通过fs.openSync获取File对象，从而获得fd、sandboxPath
              hilog.info(0x0000, TAG, `file fd: ${file.fd}`);
              hilog.info(0x0000, TAG, `sandboxPath is ${file.path}`);
            }).catch((err: BusinessError) => {
              hilog.info(0x0000, TAG, `Invoke documentViewPicker.select failed, code is ${err.code},
              message is ${err.message}`);
            });
          } catch (e) {
            let err = e as BusinessError;
            hilog.error(0x0000, TAG,
              `uri->fd转换、uri->sandboxPath转换 失败, code is ${err.code},message is ${err.message}`);
          }
        });
    }
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%');
  };
}
```

#### 常见FAQ

Q：通过documentViewPicker.select选择文件，将用户选择的文件路径，即回调参数作为参数值，调用@ohos.file.fs的fs.statSync为什么报错，fs.openSync就没报错？

A：documentViewPicker.select返回的是用户选择文件的uri数组，fs.openSync要求传入沙箱路径或者文件uri参数所以没报错；fs.statSync要求传入沙箱路径或者文件fd值，参数类型不匹配，所以报错了。