如何实现应用外预览pdf文件功能
================

#### 问题现象

如何实现在应用外进行pdf文件预览的功能？

#### 背景知识

[Preview Kit（文件预览服务）](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/preview-kit-guide)为应用提供便捷的文件快速预览能力，支持跳出应用进行文件的预览。

| 
约束与限制

 | 

说明

 |
| :-- | :-- |
| 

支持的国家和地区

 | 

当前Preview Kit仅支持中国境内（香港特别行政区、澳门特别行政区、中国台湾除外）。

 |
| 

支持的设备

 | 

模拟器支持情况，请见模拟器与真机的差异列表中[应用服务](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-emulator-specification#section38231424133213)章节的Preview Kit（文件预览服务）说明。

文件预览功能支持华为Phone、Tablet和2in1，文件打开加速功能仅支持2in1设备。

 |

#### 解决方案

通过HarmonyOS提供的Preview Kit（文件预览服务）实现pdf文件在应用外预览。实现思路如下：

1.  使用resourceManager.[getRawFdSync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-resource-manager#getrawfdsync10)获取resources/rawfile目录下rawfile文件所在HAP的文件描述符（fd）。
    
    let srcFileDescriptor \= this.context.resourceManager.getRawFdSync('test.pdf'); _// 需要在rawfile目录下手动添加名为test.pdf的文件_
    
2.  通过[fs.statSync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fsstatsync)获取文件或目录详细属性信息，调用[isFile](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#isfile)判断文件是否是普通文件。
    
    if (!fs.statSync(srcFileDescriptor.fd).isFile()) {
      console.error('Not a regular file');
      return;
    }
    
3.  通过[UIAbilityContext](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-inner-application-uiabilitycontext)获取沙箱地址filesDir，调用[fs.openSync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fsopensync)打开文件，调用[fs.readSync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fsreadsync)/[fs.writeSync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fswritesync)执行数据读写，操作完成后调用[fs.closeSync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fsclosesync)释放资源。
    
    let pathDir \= this.context.filesDir; _// 通过UIAbilityContext获取沙箱地址filesDir_
    let filePath \= pathDir \+ '/test.pdf';
    _//_ _以同步方法打开文件或目录。若文件不存在，则创建文件/读写打开_
    file \= fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ\_WRITE) as fs.File;
    let bufsize \= 4096;
    let buf \= new ArrayBuffer(bufsize); _//_ _用于保存读取到的文件数据的缓冲区。_
    let off \= 0, len \= 0, readLen \= 0; _// 动态调整目标文件的写入位置. 写入实际读取的字节数,累计已读取的总字节数_
    len \= fs.readSync(srcFileDescriptor.fd, buf, { offset: srcFileDescriptor.offset \+ off, length: bufsize });
    while (len) {
      readLen += len;
      fs.writeSync(file.fd, buf, { offset: off, length: len });
      off \= off \+ len;
      _//_ _当剩余未读取的字节数小于当前分块大小时，调整bufsize为剩余大小，避免无效读取_
      if ((srcFileDescriptor.length \- readLen) < bufsize) {
        bufsize \= srcFileDescriptor.length \- readLen;
      }
      len \= fs.readSync(srcFileDescriptor.fd, buf, { offset: srcFileDescriptor.offset \+ off, length: bufsize });
    }
    
4.  通过filePreview.[openPreview](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/preview-arkts#section144826162913)传入文件预览信息，打开预览窗口。
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/9c/v3/PqCDKRU3RbySv1szYKmZmw/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260709T134318Z&HW-CC-Expire=86400&HW-CC-Sign=32FF6A2EA04EF3F1A65A9BE1E08C4946AABB5A4FF57F5B2E915DD8286E7939D1)
    
    *   文件预览信息mimeType参数必须和文件一致，否则无法打开。例如pdf文件类型对应application/pdf。详细见[文件预览支持的文件类型](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/preview-introduction#section44960372019)。
    *   文件预览信息uri参数file://com.example.myapplication中com.example.myapplication为应用包名，实际使用时需要替换为当前工程项目中的应用包名。
    
    filePreview.openPreview(this.uiContext, this.fileInfo).then(() \=\> {
      console.info('openPreview success');
    }).catch((err: BusinessError) \=\> {
      console.error('openPreview failed, err = ' \+ err.message);
    });
    
5.  完整代码示例如下：
    
    import common from '@ohos.app.ability.common';
    import fs from '@ohos.file.fs';
    import { filePreview } from '@kit.PreviewKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    
    @Entry
    @Component
    struct pdfPreviewDemo {
      context: common.UIAbilityContext \= this.getUIContext().getHostContext() as common.UIAbilityContext;
      uiContext: Context \= this.getUIContext().getHostContext() as common.Context;
      _//_ _文件预览信息_
      private fileInfo: filePreview.PreviewInfo \= {
        title: 'test.pdf', _// 文件的标题名称_
        uri: 'file://com.example.myapplication/data/storage/el2/base/haps/entry/files/test.pdf', _//_ _文件的uri，此处com.example.myapplication为包名，请按照应用的实际包名替换_
        mimeType: 'application/pdf' _//_ _文件（夹）的媒体资源类型_
      };
    
      copyFile() {
        let file: fs.File | undefined \= undefined;
        try {
          _// 获取resources/rawfile目录下rawfile文件所在HAP的文件描述符（fd）_
          let srcFileDescriptor \= this.context.resourceManager.getRawFdSync('test.pdf'); _// 需要在rawfile目录下手动添加名为test.pdf的文件_
        _  // 判断文件是否是普通文件。true：是普通文件；false：不是普通文件。_
          if (!fs.statSync(srcFileDescriptor.fd).isFile()) {
            console.error('Not a regular file');
            return;
          }
          let pathDir \= this.context.filesDir; _// 通过UIAbilityContext获取沙箱地址filesDir_
          let filePath \= pathDir \+ '/test.pdf';
         _ // 以同步方法打开文件或目录。若文件不存在，则创建文件/读写打开_
          file \= fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ\_WRITE) as fs.File;
          let bufsize \= 4096;
          let buf \= new ArrayBuffer(bufsize); _//_ _用于保存读取到的文件数据的缓冲区。_
          let off \= 0, len \= 0, readLen \= 0; _//_ _动态调整目标文件的写入位置. 写入实际读取的字节数,累计已读取的总字节数_
          len \= fs.readSync(srcFileDescriptor.fd, buf, { offset: srcFileDescriptor.offset \+ off, length: bufsize });
          while (len) {
            readLen += len;
            fs.writeSync(file.fd, buf, { offset: off, length: len });
            off \= off \+ len;
           _ // 当剩余未读取的字节数小于当前分块大小时，调整bufsize为剩余大小，避免无效读取_
            if ((srcFileDescriptor.length \- readLen) < bufsize) {
              bufsize \= srcFileDescriptor.length \- readLen;
            }
            len \= fs.readSync(srcFileDescriptor.fd, buf, { offset: srcFileDescriptor.offset \+ off, length: bufsize });
          }
        } catch (error) {
          console.error('openPreview failed, err = ' \+ error.message);
        } finally {
          _//_ _关闭文件或目录_
          if (file) {
            fs.closeSync(file);
          }
          this.context.resourceManager.closeRawFdSync('test.pdf');
        }
      }
    
      build() {
        Row() {
          Column() {
            Button('传到沙箱')
              .onClick(() \=\> {
                this.copyFile();
              })
              .margin({ bottom: 10 });
            Button('预览文件')
              .onClick(() \=\> {
                filePreview.openPreview(this.uiContext, this.fileInfo).then(() \=\> {
                  console.info('openPreview success');
                }).catch((err: BusinessError) \=\> {
                  console.error('openPreview failed, err = ' \+ err.message);
                });
              });
          }
          .width('100%');
        }
        .height('100%');
      }
    }