鸿蒙元服务踩坑:文件下载、选择、打开
因为项目有开发元服务的需求,因此需要将原本给应用开发封装的文件操作相关代码拿到元服务里用。
本以为也没很复杂的功能,直接用应该问题不大,结果还是踩了坑……
原本给应用使用的代码请查看:鸿蒙应用开发:文件下载、选择、打开
文件下载
之前讲文件下载选择下载地址时,有个重点:
2. 核心在于获取公共目录地址;按照官方文档,要使用`DocumentViewPicker.save()`方法,而其返回值是**FileUri格式的字符串**;例如:`file://docs/storage/Users/currentUser/Download/component-Library.zip`;
3. 而`@ohos.file.fs`库中,大部分方法都是要求传入`path`格式的字符串;因此需要使用`FileUri`类进行解析后获得`path`;例如:`/storage/Users/currentUser/Download/component-Library.zip`;
这一整套逻辑在Application开发时没有任何问题。
但是在开发元服务时就遇到了问题,原因是FileUri
不支持元服务。
declare namespace fileUri {/*** FileUri represents the uri of the file.** @extends uri.URI* @syscap SystemCapability.FileManagement.AppFileService* @since 10*/class FileUri extends uri.URI {/*** Constructor for obtaining the instance of the FileUri class.** @param { string } uriOrPath - Uri or Path.* @syscap SystemCapability.FileManagement.AppFileService* @since 10*/constructor(uriOrPath: string);}
}
是否可以不进行FileUri解析?
遇到该问题的第一反应就是,在使用FileUri
解析之前只是个字符串。如果不能解析那么是否可以直接用fileUri格式的字符串进行相关文件操作呢?
在整个过程中用到了如下方法:
/**** Access file with sync interface.** @param { string } path - path.* @param { AccessModeType } [mode = fs.AccessModeType.EXIST] - accessibility mode.* @returns { boolean } Returns the file is accessible or not.* @syscap SystemCapability.FileManagement.File.FileIO* @crossplatform* @atomicservice* @since 12*/
declare function accessSync(path: string, mode?: AccessModeType): boolean;
经过测试file://docs/storage/Users/currentUser/Download/component-Library.zip
格式字符串在使用accessSync判断时会报错Operation not permitted
😭。
然后转而尝试使用statSync
:
/*** Get file information with sync interface.** @param { string | number } file - path or file descriptor.* @returns { Stat } Returns the Stat object.* @syscap SystemCapability.FileManagement.File.FileIO* @crossplatform* @atomicservice* @since 11*/
declare function statSync(file: string | number): Stat;
测试发现还是报错😭😭。
最后尝试openSync
,想通过是否可以正常打开文件来判断。
/*** Open file with sync interface.** @param { string } path - path.* @param { number } [mode = OpenMode.READ_ONLY] - mode.* @returns { File } Returns the File object to record the file descriptor.* @throws { BusinessError } 13900001 - Operation not permitted* @syscap SystemCapability.FileManagement.File.FileIO* @crossplatform* @atomicservice* @since 11*/
declare function openSync(path: string, mode?: number): File;
实测file://docs/storage/Users/currentUser/Download/component-Library.zip
格式字符串的可以正常使用。😄😄😄
正在我验证方案成功,开心地修改代码并自测的时候,问题又出现了……
openSync
的第二个参数是打开模式:打开文件的选项,必须指定如下选项中的一个,默认以只读方式打开:
。
用openSync
来替代accessSync
判断文件是否存在,用默认的只读方式打开就可以了。使用也正常。
但是当真实下载文件时,需要创建新文件并写入ArrayBuffer,这个时候就要用到fs.OpenMode.CREATE
创建模式。
该模式对于fileUri格式字符串支持有坑,测试结果如下:
当pickerMode为DOWNLOAD,fs.openSync入参为uri格式字符串,OpenMode是CREATE(新下载时,文件不存在);fs.openSync会报operation not permitted错误。 其他情况:
1.当pickerMode为DOWNLOAD,fs.openSync入参为**path格式字符串**,OpenMode是CREATE(新下载时,文件不存在);运行正常;
2.当pickerMode为DOWNLOAD,fs.openSync入参为uri格式字符串,**OpenMode是WRITE_ONLY(文件已存在)**;运行正常;
3.当**pickerMode为DEAULT**,fs.openSync入参为uri格式字符串,OpenMode是CREATE(新下载时,文件不存在);运行正常; 该问题本身和应用或元服务无关,只是`fs.openSync`的bug。
😭😭😭此路不通
其他替代方案
在将以上问题反馈给官方技术支持后,又继续考虑其他方案。
declare namespace fileUri {/*** FileUri represents the uri of the file.** @extends uri.URI* @syscap SystemCapability.FileManagement.AppFileService* @since 10*/class FileUri extends uri.URI {/*** Constructor for obtaining the instance of the FileUri class.** @param { string } uriOrPath - Uri or Path.* @syscap SystemCapability.FileManagement.AppFileService* @since 10*/constructor(uriOrPath: string);}
}
看FileUri
的定义是继承自uri.URI
,点进去发现:
/*** URI Represents a Uniform Resource Identifier (URI) reference.** @syscap SystemCapability.Utils.Lang* @crossplatform* @atomicservice* @since 11* @name URI*/class URI {}
有@atomicservice
标注,支持元服务。
一时间豁然开朗,因为FileUri
的Path解析能力是继承自uri.URI
,经过测试后发现功能正常。
因此最终,该方法中也只是将let uri = new fileUri.FileUri(documentSaveResult[0]);
替换为let uriObj = new uri.URI(documentSaveResult[0]);
。
文件选择
文件选择功能除了解析path,还获取了文件名,不过文件名解析相对简单,即便手动解析问题也不大。
let uri = new fileUri.FileUri(documentSelectResult[0]);if (options.success != undefined) {options.success({'path': uri.path,'name': uri.name})}
最终修改为:
let uriObj = new uri.URI(documentSelectResult[0]);if (options.success != undefined) {options.success({'path': uriObj.path,'name': uriObj.getLastSegment()})}
文件打开
let filePath = options.fileName;// 将沙箱路径转换为urilet uri = fileUri.getUriFromPath(filePath);let want: Want = {// 配置被分享文件的读写权限,例如对被分享应用进行读写授权flags: wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION | wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION,// 配置分享应用的隐式拉起规则action: 'ohos.want.action.sendData',uri: uri,type: options.contentType}
文件打开的问题就很大了,经过测试Want
中的uri
参数必须是uri格式,使用path格式调用无效。
解决该问题有两个方向:
业务向
因为文件打开的前一步一般是获取文件目录地址;而文件目录地址无论是通过picker.select()
选择还是picker.save()
下载,其原始返回值都是uri格式,因此可以缓存uri格式字符串用于打开。
这样代码中的let uri = fileUri.getUriFromPath(filePath);
就不在需要了。
不过这里存在一些picker的风险点:
从FilePicker返回的图片地址uri是不是只是在一定的时间内有访问权限
重启应用后,uri失效是正常的,因为通过picker生成uri的同时系统会对uri有临时授权,应用被杀掉后该临时授权失效,需要重新通过picker选择来生成uri。
刚需
如果上面方案无法解决,而元服务里,这个功能又是刚需,非做不可,只能考虑参照官方文档文档类uri介绍,手动进行格式转换。
手动转换的话,功能当然也不是不能用,担忧在于鸿蒙是新系统,现在又是beta阶段,这些格式存在频繁变动的可能。
所以在最开始解决该问题时也没有第一时间想着手动处理。
总结
因为对于HarmonyOS而言,元服务不是前期交付的重点,所以坑也比应用开发多。
不过最近时间元服务相关的修复和变动也挺频繁,如果是在做元服务开发,尽量保持使用最新的beta版本,以免浪费时间在官方已解决的问题上。