HarmonyOS 鸿蒙Next PC文件适配最佳实践

HarmonyOS 鸿蒙Next PC文件适配最佳实践

1. API 使用指导

文件操作主要涉及Core File Kit(文件基础服务)提供的接口进行文件的选择,保存,持久化等操作。若应用需要通过自定义的文件选择器访问路径,则需要使用访问控制模块提供的授权申请能力获取权限。

1.1 Core File Kit

1.1.1 文件URI

FileUri

文件的URI,根据问卷路径不同URI会有所区别:

  • 应用沙箱uri:file://<bundleName>/<sandboxPath>;
  • 公共目录文件类uri:file://docs/storage/Users/currentUser/<publicPath>;
  • 公共目录媒体类uri:file://media/<mediaType>/IMG_DATATIME_ID/<displayName>;

getUriFromPath

通过传入的路径path生成应用自己的uri(不支持媒体类型uri的获取);将path转uri时,路径中的中文及非数字字母的特殊字符将会被编译成对应的ASCII码,拼接在uri中。

参考链接:[@ohos.file.fileuri (文件URI)](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fileuri)

1.1.2 选择器

select

通过选择模式拉起documentPicker界面,用户可以选择一个或多个文件。接口采用callback异步返回形式,传入参数DocumentSelectOptions对象,返回选择文件的uri数组。

参考链接:[@ohos.file.picker (选择器)](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-picker)

1.1.3 文件分享

persistPermission

异步方法对所选择的多个文件或目录URI持久化授权,以promise形式返回结果,该接口仅对具有该系统能力的设备开放。

revokePermission

异步方法对所选择的多个文件或目录uri取消持久化授权,以promise形式返回结果,该接口仅对具有该系统能力的设备开放。

activatePermission

异步方法使能多个已经永久授权过的文件或目录,以promise形式返回结果,该接口仅对具有该系统能力的设备开放。

deactivatePermission

异步方法取消使能授权过的多个文件或目录,以promise形式返回结果,该接口仅对具有该系统能力的设备开放。

checkPersistentPermission

异步方法校验所选择的多个文件或目录URI持久化授权,以promise形式返回结果。

参考链接:[@ohos.fileshare (文件分享)](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-fileshare)

1.1.4 Core File Kit涉及的参考文档

参考链接:

1.2 访问控制

三方应用需要访问公共目录时,需通过弹窗授权向用户申请授予 Download 目录权限、Documents 目录权限或 Desktop 目录权限。

参考链接:获取并使用公共目录

1.3 AbilityKit

三方应用可通过接口,由系统从已安装的应用中寻找符合要求的应用,打开特定文件。

参考链接:目标方接入步骤

2. 文件操作场景指导及示例

2.1 选择用户文件

2.1.1 通过filepicker提供的select方法拉起指定目录

通过select函数拉起文件管理提供的filepicker。通过filepicker中的DocumentSelectOptions参数,可以指定以下内容:

名称 类型 必填 说明
maxSelectNumber number 选择文件最大个数,上限500,有效值范围1-500(选择目录仅对具有该系统能力的设备开放。且目录选择的最大个数为1)。默认值是1。系统能力:SystemCapability.FileManagement.UserFileService
defaultFilePathUri string 指定选择的文件或者目录路径。
fileSuffixFilters Array 选择文件的后缀类型,传入字符串数组,每一项代表一个后缀选项,每一项内部用“
selectMode DocumentSelectMode 支持选择的资源类型。比如:文件、文件夹和二者混合,仅对具有该系统能力的设备开放,默认值是文件类型。仅支持2in1设备。系统能力:SystemCapability.FileManagement.UserFileService.FolderSelection
authMode boolean 拉起授权picker,默认为false(非授权模式)。当authMode为true时为授权模式,defaultFilePathUri必填,表明待授权uri。仅支持2in1设备。系统能力:SystemCapability.FileManagement.UserFileService.FolderSelection

拉起文档目录示例

以下示例指定了拉起filepicker时的默认路径为Document路径,同时指定了可选择的文件后缀。选择完成后,该应用会在Document目录下生成test1文件夹。

//...
public PullDocPicker() { 
   try { 
      let documentSelectOptions = new picker.DocumentSelectOptions(); 
      documentSelectOptions.defaultFilePathUri = 'file://docs/storage/Users/currentUser/Documents/' 
      documentSelectOptions.selectMode = picker.DocumentSelectMode.MIXED; 
      documentSelectOptions.fileSuffixFilters = 
         ['文字/Word格式(*.doc *docx)|*.doc,*docx', '.*', '.docx', '.doc', '.pptx', '.ppt'];
      documentSelectOptions.fileSuffixFilters = []
      let documentPicker = new picker.DocumentViewPicker(); 
      documentPicker.select(documentSelectOptions).then((documentSelectResult: Array<string>) => { 
         hilog.info(0x0000, FilePickerManager.TAG_LOG, 'FilePickerManager documentSelectResult uri %{public}s', 
            JSON.stringify(documentSelectResult)); 
         fs.mkdir('/storage/Users/currentUser/Documents/test1') 
      }).catch((err: BusinessError) => { 
         hilog.info(0x0000, FilePickerManager.TAG_LOG, 'DocumentViewPicker.select failed with err %{public}s', 
            JSON.stringify(err)); 
      }); 
   } catch (error) { 
      let err: BusinessError = error as BusinessError; 
      hilog.info(0x0000, FilePickerManager.TAG_LOG, 'DocumentViewPicker failed with err %{public}s', 
         JSON.stringify(err)); 
   } 
} 
//...

效果示例

2.1.2 通过startAbility方法拉起指定目录

可以调用startAbility接口拉起filepicker,并指定拉起的默认路径。

拉起用户根目录示例

//...
public PullCustomPicker() { 
   let want: Want = { 
     deviceId: '', 
     bundleName: 'com.huawei.hmos.filemanager', 
     abilityName: 'MainAbility' 
   }; 
   want.parameters = { 
     'fileUri': 'file://docs/storage/Users/currentUser' 
   }; 
   let context = getContext(this) as common.UIAbilityContext; 
   context.startAbility(want, (error: BusinessError) => { 
     hilog.info(0x0000, FilePickerManager.TAG_LOG, 'PullCustomPicker failed with err %{public}s', 
       JSON.stringify(error)); 
   }) 
 } 
//...

效果示例

2.1.3 申请3D目录权限

公共目录获取接口仅用于获取公共目录路径,不对公共目录访问权限进行校验。若需访问公共目录需申请对应的公共目录访问权限。三方应用需要访问公共目录时,需通过弹窗授权向用户申请授予 Download 目录权限、Documents 目录权限或 Desktop目录权限,具体参考访问控制-向用户申请授权

"requestPermissions" : [ 
   "ohos.permission.READ_WRITE_DOWNLOAD_DIRECTORY", 
   "ohos.permission.READ_WRITE_DOCUMENTS_DIRECTORY", 
   "ohos.permission.READ_WRITE_DESKTOP_DIRECTORY", 
]

获取文档目录权限示例

//在应用窗口拉起时进行权限的申请,实际场景下可以在需要的时候再通过requestPermission操作申请 
onWindowStageCreate(windowStage: window.WindowStage): void { 
   //... 
   const permissions: Array<Permissions> = ['ohos.permission.READ_WRITE_DOCUMENTS_DIRECTORY']; 
   permissionManager.getInstance().checkPermissions(permissions,this.context); 
   //SLEEPING 
 } 
//... 
public async checkPermissions(permissions: Array<Permissions>, context: common.UIAbilityContext) { 
  let grantStatus: abilityAccessCtrl.GrantStatus = await this.checkPermissionGrant(permissions[0]); 
  if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) { 
    // 已经授权,可以继续访问目标操作 
    hilog.info(0x0000, PermissionManager.TAG_LOG, 'checkPermissions', 'already granted'); 
  } else { 
    // 申请权限 
    this.reqPermissionsFromUser(permissions,context); 
  } 
} 
//... 
public reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void { 
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); 
    // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗 
    atManager.requestPermissionsFromUser(context, permissions).then((data) => { 
      let grantStatus: Array<number> = data.authResults; 
      let length: number = grantStatus.length; 
      for (let i = 0; i < length; i++) { 
        if (grantStatus[i] === 0) { 
          // 用户授权,可以继续访问目标操作 
        } else { 
          // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限 
          atManager.requestPermissionOnSetting(context, ['ohos.permission.READ_WRITE_DOCUMENTS_DIRECTORY']).then((data: Array<abilityAccessCtrl.GrantStatus>) => { 
            hilog.info(0x0000, PermissionManager.TAG_LOG, 'reqPermissionsFromUser', 'data:' + JSON.stringify(data)); 
          }).catch((err: BusinessError) => { 
            hilog.info(0x0000, PermissionManager.TAG_LOG, 'reqPermissionsFromUser', 'data:' + JSON.stringify(err)); 
          }); 
          return; 
        } 
      } 
      // 授权成功 
    }).catch((err: BusinessError) => { 
      console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`); 
      hilog.info(0x0000, PermissionManager.TAG_LOG, 'checkPermissions', 'already granted'); 
    }) 
  }

效果示例

2.2 保存用户文件

2.2.1 通过filepicker提供的save方法保存文件

通过save函数拉起文件管理提供的filepicker。通过filepicker中的DocumentSaveOptions参数,可以指定以下内容:

名称 类型 必填 说明
maxSelectNumber number 选择文件最大个数,上限500,有效值范围1-500(选择目录仅对具有该系统能力的设备开放。且目录选择的最大个数为1)。默认值是1。系统能力:SystemCapability.FileManagement.UserFileService
defaultFilePathUri string 指定选择的文件或者目录路径。
fileSuffixFilters Array 选择文件的后缀类型,传入字符串数组,每一项代表一个后缀选项,每一项内部用“
selectMode DocumentSelectMode 支持选择的资源类型。比如:文件、文件夹和二者混合,仅对具有该系统能力的设备开放,默认值是文件类型。仅支持2in1设备。系统能力:SystemCapability.FileManagement.UserFileService.FolderSelection
authMode boolean 拉起授权picker,默认为false(非授权模式)。当authMode为true时为授权模式,defaultFilePathUri必填,表明待授权uri。仅支持2in1设备。系统能力:SystemCapability.FileManagement.UserFileService.FolderSelection

保存文件示例:

//...
public SaveFileByPicker() { 
   try { 
     let desktopDirUri: string = 'file://docs/storage/Users/currentUser/Desktop/'; 
     let documentSaveOptions = new picker.DocumentSaveOptions(); 
     documentSaveOptions.newFileNames = ['1.txt']; 
     documentSaveOptions.defaultFilePathUri = desktopDirUri;   
     documentSaveOptions.pickerMode = picker.DocumentPickerMode.DEFAULT; 
     let documentPicker = new picker.DocumentViewPicker(); 
     documentPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => { 
       hilog.info(0x0000, FilePickerManager.TAG_LOG, 'FilePickerManager documentSaveResult uri %{public}s', 
         JSON.stringify(documentSaveResult)); 
     }).catch((err: BusinessError) => { 
       hilog.info(0x0000, FilePickerManager.TAG_LOG, 'SaveFileByPicker.select failed with err %{public}s', 
         JSON.stringify(err)); 
     }); 
   } catch (error) { 
     let err: BusinessError = error as BusinessError; 
     hilog.info(0x0000, FilePickerManager.TAG_LOG, 'SaveFileByPicker failed with err %{public}s', 
       JSON.stringify(err)); 
   } 
 } 
//...

效果示例:

2.3 持久化授权

需要持久化授权能力的主要场景如下:

场景 典型应用示例
先使用A应用打开B文件后,关闭A应用。再次打开A应用时,通过历史记录打开B文件。 通过应用侧的历史记录打开文档。
使用IM类应用A接收/发送文件后,双击使用应用B打开文件浏览编辑。 打开通过QQ发送的word文档。
在A应用的文件中插入附件/超链接后,双击附件/超链接使用应用B打开。 打开亿图图示中的超链接。
通过拖拽/复制粘贴添加到A应用中的文件尝试通过B应用打开。 打开通过拖拽/复制粘贴到QQ中发送的文件。

2.3.1 对路径进行持久化授权

在以上描述的场景中,需要调用persistPermission接口对文件或者目录的路径进行持久化授权,从而确保应用可以在之后用到时可以持有访问的权限。文件持久化能力需要获取FILE_ACCESS_PERSIST权限。权限申请流程参考访问控制-申请应用权限

//module.json5 
//... 
"requestPermissions": [ 
   { 
   "name": "ohos.permission.FILE_ACCESS_PERSIST", 
   } 
//...

持久化授权示例:

//... 
private CustomPersistPermission(targetUri: string, targetMode: fileShare.OperationMode) { 
   let policyInfo: fileShare.PolicyInfo = { 
     uri: targetUri, 
     operationMode: targetMode, 
   }; 
   let policies: Array<fileShare.PolicyInfo> = [policyInfo]; 
   fileShare.persistPermission(policies).then(() => { 
     hilog.info(0x0000, FilePickerManager.TAG_LOG, '%{public}s', 'CustomPersistPermission successfully'); 
   }).catch((err: BusinessError<Array<fileShare.PolicyErrorResult>>) => { 
     hilog.info(0x0000, FilePickerManager.TAG_LOG, 'CustomPersistPermission failed with err %{public}s', 
       JSON.stringify(err)); 
   }); 
 }

2.3.2 激活已经持有的持久化授权

已经进行持久化授权的文件或路径,在每一次需要访问前,需要激活已经持有的持久化权限。

//... 
private CustomActivatePermission(targetUri: string, targetMode: fileShare.OperationMode) { 
   let policyInfo: fileShare.PolicyInfo = { 
     uri: targetUri, 
     operationMode: targetMode, 
   }; 
   let policies: Array<fileShare.PolicyInfo> = [policyInfo]; 
   fileShare.activatePermission(policies).then(() => { 
     hilog.info(0x0000, FilePickerManager.TAG_LOG, '%{public}s', 'CustomActivatePermission successfully'); 
   }).catch((err: BusinessError<Array<fileShare.PolicyErrorResult>>) => { 
     hilog.info(0x0000, FilePickerManager.TAG_LOG, 'CustomActivatePermissionfailed with err %{public}s', 
       JSON.stringify(err)); 
   }); 
 }

2.3.3 取消已经持有的持久化授权

已经进行持久化授权的文件或路径,可以通过以下方式取消。

//... 
private CustomRevokePermission(targetUri: string, targetMode: fileShare.OperationMode) { 
  let policyInfo: fileShare.PolicyInfo = { 
    uri: targetUri, 
    operationMode: targetMode, 
  }; 
  let policies: Array<fileShare.PolicyInfo> = [policyInfo]; 
  fileShare.revokePermission(policies).then(() => { 
    hilog.info(0x0000, FilePickerManager.TAG_LOG, '%{public}s', 'CustomRevokePermission successfully'); 
  }).catch((err: BusinessError<Array<fileShare.PolicyErrorResult>>) => { 
    hilog.info(0x0000, FilePickerManager.TAG_LOG, 'CustomRevokePermission failed with err %{public}s', 
      JSON.stringify(err)); 
  }); 
}

2.3.4 取消已经激活的持久化授权

已经激活了持久化授权的文件或路径,可以通过以下方式取消激活状态。

//... 
private CustomDeactivatePermission(targetUri: string, targetMode: fileShare.OperationMode) { 
  let policyInfo: fileShare.PolicyInfo = { 
    uri: targetUri, 
    operationMode: targetMode, 
  }; 
  let policies: Array<fileShare.PolicyInfo> = [policyInfo]; 
  fileShare.deactivatePermission(policies).then(() => { 
    hilog.info(0x0000, FilePickerManager.TAG_LOG, '%{public}s', 'CustomDeactivatePermission successfully'); 
  }).catch((err: BusinessError<Array<fileShare.PolicyErrorResult>>) => { 
    hilog.info(0x0000, FilePickerManager.TAG_LOG, 'CustomDeactivatePermission failed with err %{public}s', 
      JSON.stringify(err)); 
  }); 
}

2.4 跨应用预览用户文件

以下应用类型存在跨应用预览用户文件的场景:

  • IM类应用:发送文件/接收文件后,尝试使用其他应用预览/编辑文件。
  • 文档类应用:文件以附件/超链接形式存在,尝试使用其他应用预览/编辑文件。

2.4.1 通过filepicker插入文件场景

通过系统提供的filepicker能力插入文件后,应用即可获取该文件的临时访问权限。在进行持久化授权后,即可分享给其他应用打开该文件。以下代码示例在选择文件后,进行了读权限的持久化授权,并分享给预览应用打开文件。在实际应用场景中,可以灵活指定需要的权限和需要分享给的目标应用。

//... 
let uriTemp: string = ''; 
try { 
  // 1.拉起系统picker 
  let documentSelectOptions = new picker.DocumentSelectOptions(); 
  let documentPicker = new picker.DocumentViewPicker; 
  documentPicker.select(documentSelectOptions).then((documentSelectResult: Array<string>) => { 
    uriTemp = documentSelectResult[0]; 
    hilog.info(0x0000, FileShareManager.TAG_LOG, 'SharePublicFile documentSelectResult uri %{public}s', uriTemp); 
    // 2.持久化授权 
    let policyInfo: fileShare.PolicyInfo = { 
      uri: uriTemp, 
      operationMode: fileShare.OperationMode.READ_MODE, 
    }; 
    let policies: Array<fileShare.PolicyInfo> = [policyInfo]; 
    fileShare.persistPermission(policies).then(() => { 
      hilog.info(0x0000, FileShareManager.TAG_LOG, 'SharePublicFile persistPermission successfully'); 
    }).catch((err: BusinessError<Array<fileShare.PolicyErrorResult>>) => { 
      hilog.info(0x0000, FileShareManager.TAG_LOG, 
        'SharePublicFile persistPermission failed with error message %{public}s', JSON.stringify(err)); 
    }); 
    // 3.分享文件 
    let want: Want = { 
      // 配置被分享文件的读写权限,例如对被分享应用进行读写授权 
      flags: wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION | wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION, 
      // 只配置读权限 flags:wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION, 
      // 配置分享应用的隐式拉起规则 action: 'ohos.want.action.sendData', 
      action: 'ohos.want.action.viewData', 
      uri: uriTemp, 
      type: 'application/pdf' 
    } 
    let context = getContext(this) as common.UIAbilityContext; 
    context.startAbility(want, (error: BusinessError) => { 
      hilog.info(0x0000, FileShareManager.TAG_LOG, 'SharePublicFile result code %{public}s', JSON.stringify(error)); 
    }) 
  }).catch((err: BusinessError) => { 
    hilog.info(0x0000, FileShareManager.TAG_LOG, 'SharePublicFile.select failed with err %{public}s', 
      JSON.stringify(err)); 
  }); 
} catch (error) { 
  let err: BusinessError = error as BusinessError; 
  hilog.info(0x0000, FileShareManager.TAG_LOG, 'SharePublicFile failed with err %{public}s', 
    JSON.stringify(err)); 
}

2.4.2 通过拖拽插入文件场景

通过拖拽插入文件后,应用即可获取该文件的临时访问权限。在进行持久化授权后,即可分享给其他应用打开该文件。

参考demo:

//...
import { uniformDataStruct, uniformTypeDescriptor, unifiedDataChannel } from '@kit.ArkData'; 
import { fileUri, fileIo as fs } from '@kit.CoreFileKit' 
import { common } from '@kit.AbilityKit' 

@Entry 
@Component 
struct Index { 
  @State ondrop_uri: string = ''; 
  @State description:string = ''; 
  uiContext = this.getUIContext(); 
  udKey: string = ''; 
  build() { 
    Column() { 
      Column() { 
        Text("拖出方") 
        Text("oriUri:" + 'file://data/image/1.png') 
        Text("description:" + 'This is the description of the hyperlink') 
      } 
      .width(300) 
      .height(200) 
      .borderWidth(2) 
      .margin(20) 
      .onDragStart((event) => { 
 
        //创建FILE_URI类型数据 
        let fileUriDetails : Record<string, string> = { 
          'attr1': 'value1', 
          'attr2': 'value2', 
        } 
        let fileUri : uniformDataStruct.FileUri = { 
          uniformDataType : 'general.file-uri', 
          oriUri : 'file://data/image/1.png', 
          fileType : 'general.image', 
          details : fileUriDetails, 
        } 
 
        //创建HYPERLINK类型数据 
        let hyperlink : uniformDataStruct.Hyperlink = { 
          uniformDataType:'general.hyperlink', 
          url : 'file://data/image/1.png', 
          description : 'This is the description of the hyperlink', 
        } 
 
        //新建一个新的unifiedData 
        let unifiedData = new unifiedDataChannel.UnifiedData(); 
        //新建一个UnifiedRecord 初始化塞入FORM类型的数据(可自定义) 
        let record = new unifiedDataChannel.UnifiedRecord(uniformTypeDescriptor.UniformDataType.HYPERLINK, hyperlink); 
        //addEntry 添加另一份FILE_URI数据到record里面 
        record.addEntry(uniformTypeDescriptor.UniformDataType.FILE_URI, fileUri); 
        //将record塞入unifiedData 
        unifiedData.addRecord(record); 
        //将unifiedData塞入dragEvent 
        event.setData(unifiedData) 
      }) 
 
      Button("Reset") 
        .onClick(() =>{ 
          this.ondrop_uri = '' 
          this.description = '' 
        }) 
 
      Column() { 
        Text("落入方") 
        Text("oriUri:" + this.ondrop_uri) 
        Text("description:" + this.description) 
      } 
      .width(300) 
      .height(200) 
      .borderWidth(2) 
      .margin(20) 
      .onDrop((event) =>{ 
        //配置异步拖拽参数 
        let context = this.uiContext.getHostContext() as common.UIAbilityContext; 
        let pathDir: string = context.distributedFilesDir; 
        let destUri = fileUri.getUriFromPath(pathDir); 
 
        //配置数据处理回调 
        let progressListener: unifiedDataChannel.DataProgressListener = (progress: unifiedDataChannel.ProgressInfo, dragData: UnifiedData|null) => { 
          if(dragData != null) { 
            //拿到records 
            let records:Array<unifiedDataChannel.UnifiedRecord> = dragData.getRecords(); 
            //循环遍历查询 
            for (let i = 0; i < records.length; i++) { 
              let unifiedDataRecord = records[i] as unifiedDataChannel.UnifiedRecord; 
              //找到对应类型的数据 getEntry 
              let fileUriRead : uniformDataStruct.FileUri = unifiedDataRecord.getEntry(uniformTypeDescriptor.UniformDataType.FILE_URI) as uniformDataStruct.FileUri; 
              if (fileUriRead != undefined) { 
                //拿到数据类型中对应的字段 
                console.info(`oriUri: ${fileUriRead.oriUri}`); 
                this.ondrop_uri = fileUriRead.oriUri; 
              } 
              let hyperlink = unifiedDataRecord.getEntry(uniformTypeDescriptor.UniformDataType.HYPERLINK) as uniformDataStruct.Hyperlink; 
              if (hyperlink != undefined) { 
                console.info(`description: ${hyperlink.description}`); 
                this.description = hyperlink.description as string; 
              } 
            } 
          } else { 
            console.log('dragData is undefined'); 
          } 
          console.log(`percentage: ${progress.progress}`); 
        }; 
 
        //配置异步获取数据options 
        let options: DataSyncOptions = { 
          destUri: destUri, 
          fileConflictOptions: unifiedDataChannel.FileConflictOptions.OVERWRITE, //配置数据冲突处理方式 
          progressIndicator: unifiedDataChannel.ProgressIndicator.DEFAULT,  //配置进度条,数据处理超过500ms生效 
          dataProgressListener: progressListener, 
        } 
        try { 
          this.udKey = (event as DragEvent).startDataLoading(options); 
          console.log('udKey: ', this.udKey); 
        } catch(e) { 
          console.log(`startDataLoading errorCode: ${e.code}, errorMessage: ${e.message}`); 
        } 
      }, {disableDataPrefetch: true}) //当使用startDataLoading获取数据时需设置该参数为true,防止拖拽提前获取数据。 
 
      Button('取消数据传输') 
        .onClick(() => { 
          try { 
            this.getUIContext().getDragController().cancelDataLoading(this.udKey); 
          } catch (e) { 
            console.log(`cancelDataLoading errorCode: ${e.code}, errorMessage: ${e.message}`); 
          } 
        }) 
        .margin({top: 10}) 
 
    } 
    .height('100%') 
    .width('100%') 
  } 
}

2.4.3 通过剪贴板插入文件场景

通过剪贴板插入文件后,应用即可获取该文件的临时访问权限。在进行持久化授权后,即可分享给其他应用打开该文件。

2.5 文件格式关联

2.5.1 打开特定后缀的文件场景

应用作为特定文件格式打开的目标方,适配已达到可以在打开方式中被选择的目的。

参考链接:目标方接入步骤

2.5.2 注册右键扩展菜单场景

三方应用可以接入文件管理内右键菜单,并指定操作。在应用工程对应的待注册右键菜单的模块的module.json5文件中,在”module”下新增子节点”fileContextMenu”,配置值为指定的右键配置文件,示例如下:

//module.json5 
//... 
"module":{ 
    "name": "entry", 
    "type": "entry", 
    "mainElement": "EntryAbility", 
    "fileContextMenu": "$profile:menu", 
//...

此含义为右键配置文件在此模块的resources\base\profile目录下,文件名为menu.json。右键菜单的注入方式核心重点主要是配置文件的编写,配置文件的名字当前无限制,只要和第一步中指定的文件名保持一致即可,放置位置也需要按照配置指定目录存放,即开发视图的resources\base\profile目录下。

下表为配置文件标签说明

属性名称 含义 数据类型 是否可缺省
fileContextMenu 标识当前module注册右键菜单的数量。(单模块和单应用注册数量不能超过5个,配置超过数量文管当前只解析随机5个) 对象数组 不可缺省

下表为rightMenu标签配置说明

属性名称 含义 数据类型 是否可缺省
abilityName 表示当前右键菜单对应的需要拉起的ability名称 字符串 不可缺省
menuItem 右键菜单显示的文件信息 资源id 不可缺省
menuHandler 一个ability可以创建多个右键菜单,用该字段来区分用户拉起的不同右键菜单项。该字段在用户点击右键菜单执行时,会作为参数传递给右键菜单应用 字符串 不可缺省
menuContext 定义

更多关于HarmonyOS 鸿蒙Next PC文件适配最佳实践的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

鸿蒙Next PC文件适配主要涉及分布式文件系统能力。开发者需使用HarmonyOS提供的File API实现跨设备文件访问,重点关注fileio和statfs模块。适配要点:

  1. 使用ohos.file.fs处理文件操作;
  2. 通过分布式文件管理实现设备间文件共享;
  3. 注意URI格式统一采用"datashare://"协议。

关键接口包括openSyncreadSync等同步操作接口。权限配置需在config.json声明所需文件访问权限。文件路径建议使用系统预置目录常量如DIR_DOCUMENTS

更多关于HarmonyOS 鸿蒙Next PC文件适配最佳实践的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


针对HarmonyOS Next PC文件适配,以下是关键实践要点:

  1. 文件URI处理
  • 使用@ohos.file.fileuri模块处理不同路径类型URI:
// 沙箱路径转URI
let uri = fileUri.getUriFromPath('/data/storage/el2/base/files/test.txt');
  1. 文件选择器
  • 通过DocumentViewPicker实现文件选择/保存:
// 文件选择
let picker = new picker.DocumentViewPicker();
picker.select({fileSuffixFilters: ['.pdf']}).then((uris) => {
  // 处理选中文件
});
  1. 持久化授权
  • 跨应用访问需持久化授权:
// 持久化读权限
fileShare.persistPermission([{
  uri: fileUri,
  operationMode: fileShare.OperationMode.READ_MODE
}]);
  1. 公共目录访问
  • 申请对应目录权限:
// module.json5配置
"requestPermissions": [
  "ohos.permission.READ_WRITE_DOCUMENTS_DIRECTORY"
]
  1. 文件分享
  • 使用Want启动关联应用:
let want = {
  action: 'ohos.want.action.viewData',
  uri: fileUri,
  type: 'application/pdf'
};
context.startAbility(want);
  1. 回收站操作
  • 删除文件到回收站:
fileManagerService.deleteToTrash(fileUri).then((trashUri) => {
  console.info(`Moved to trash: ${trashUri}`);
});
  1. 右键菜单扩展
  • 注册文件关联操作:
// profile/menu.json
{
  "abilityName": "MainAbility",
  "menuItem": "$string:open_with",
  "menuContext": [{
    "menuKind": 1,
    "fileSupportType": [".docx"]
  }]
}

关键注意事项:

  • 媒体文件使用file://media/前缀URI
  • 持久化授权需声明ohos.permission.FILE_ACCESS_PERSIST权限
  • 拖拽文件时需激活临时权限
  • 卸载应用会自动清除相关授权

建议参考完整示例代码实现具体功能场景。

回到顶部