OpenHarmony中的应用文件分享机制
应用文件分享是应用之间通过分享URI(Uniform Resource Identifier)或文件描述符FD(File Descriptor)的方式,进行文件共享的过程。由于FD分享的文件关闭FD后,无法再打开分享文件,因此不推荐使用,本文重点介绍URI分享方式。
应用可分享目录
从沙箱说起
应用沙箱是一种以安全防护为目的的隔离机制,避免数据受到恶意路径穿越访问。在这种沙箱的保护机制下,应用可见的目录范围即为“应用沙箱目录”。
- 对于每个应用,系统会在内部存储空间映射出一个专属的“应用沙箱目录”,它是“应用文件目录”与一部分系统文件(应用运行必需的少量系统文件)所在的目录组成的集合。
- 应用沙箱限制了应用可见的数据的最小范围。在“应用沙箱目录”中,应用仅能看到自己的应用文件以及少量的系统文件(应用运行必需的少量系统文件)。因此,本应用的文件也不为其他应用可见,从而保护了应用文件的安全。
- 应用可以在“应用文件目录”下保存和处理自己的应用文件;系统文件及其目录对于应用是只读的;而应用若需访问用户文件,则需要通过特定API同时经过用户的相应授权才能进行。
下图展示了应用沙箱下,应用可访问的文件范围和方式。
如前文所述,“应用沙箱目录”内分为两类:应用文件目录和系统文件目录。
系统文件目录对应用的可见范围由OpenHarmony系统预置,开发者无需关注。
在此主要介绍应用文件目录,如下图所示。应用文件目录下某个文件或某个具体目录的路径称为应用文件路径。应用文件目录下的各个文件路径,具备不同的属性和特征。
可分享目录
基于以上背景,应用可分享的目录如下。
沙箱路径 | 物理路径 | 说明 |
---|---|---|
/data/storage/el1/base | /data/app/el1/<currentUserId>/base/<PackageName> | 应用el1级别加密数据目录 |
/data/storage/el2/base | /data/app/el2/<currentUserId>/base/<PackageName> | 应用el2级别加密数据目录 |
/data/storage/el2/distributedfiles | /mnt/hmdfs/<currentUserId>/account/device_view/ <networkId>/data/<PackageName> |
应用el2加密级别有帐号分布式数据融合目录 |
文件URI规范
文件URI的格式为:
格式为file://<bundleName>/<path>
- file:文件URI的标志。
- bundleName:该文件资源的属主。
- path:文件资源在应用沙箱中的路径。
分享文件给其他应用
获取文件路径
在分享文件给其他应用前,开发者需要先获取应用文件路径。
属性 路径 filesDir <路径前缀>/<加密等级>/base/files
import UIAbility from '@ohos.app.ability.UIAbility';
import fileuri from '@ohos.file.fileuri';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
// 获取文件的沙箱路径
let pathInSandbox = this.context.filesDir + "/test.txt";
// 将沙箱路径转换为uri
let uri = fileuri.getUriFromPath(pathInSandbox);
// 获取的uri为"file://com.example.demo/data/storage/el2/base/files/test.txt"
}
}
具体来说,先通过应用上下文Context
获取文件在沙箱中的路径,然后再通过该路径生成对应的的URI
设置获取文件的权限以及选择要分享的应用
在OpenHarmony中,Ability是应用所具备能力的抽象,也是应用程序的重要组成部分。Ability是系统调度应用的最小单元,是能够完成一个独立功能的组件,一个应用可以包含一个或多个Ability。 类似Android中的Application和Activity的融合。
分享文件给其他应用需要使用startAbility()
接口,将获取到的URI填充在want
的参数uri
中,标注URI的文件类型,type
字段可参考Want
属性,并通过设置want
的flag
来设置对应的读写权限,action
字段配置为ohos.want.action.sendData
表示进行应用文件分享,开发示例如下。
import fileuri from '@ohos.file.fileuri';
import window from '@ohos.window';
import wantConstant from '@ohos.app.ability.wantConstant';
import UIAbility from '@ohos.app.ability.UIAbility';
import Want from '@ohos.app.ability.Want';
import { BusinessError } from '@ohos.base';
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
// 获取文件沙箱路径
let filePath = this.context.filesDir + '/test.txt';
// 将沙箱路径转换为uri
let 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: 'text/plain'
}
this.context.startAbility(want)
.then(() => {
console.info('Invoke getCurrentBundleStats succeeded.');
})
.catch((err: BusinessError) => {
console.error(`Invoke startAbility failed, code is ${err.code}, message is ${err.message}`);
});
}
// ...
}
使用其他应用分享的文件
配置module.json5配置文件
module.json5主要包含以下内容:
- Module的基本配置信息,例如Module名称、类型、描述、支持的设备类型等基本信息。
- 应用组件信息,包含UIAbility组件和ExtensionAbility组件的描述信息。
- 应用运行过程中所需的权限信息
被分享应用需要在module.json5配置文件的actions
标签的值配置为ohos.want.action.sendData
,表示接收应用分享文件,配置uris
字段,表示接收URI的类型,即只接收其他应用分享该类型的URI,如下表示本应用只接收scheme
为file
,类型为txt
的文件,示例如下。
{
"module": {
...
"abilities": [
{
...
"skills": [
{
...
"actions": [
"ohos.want.action.sendData"
],
"uris": [
{
"scheme": "file",
"type": "text/plain"
}
]
}
]
}
]
}
}
Want匹配
前面展示的代码中有这么一段未解释完全的地方:
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: 'text/plain'
}
this.context.startAbility(want)
// balabala
flag
配置读写权限都能理解,那么“隐式拉起规则”是个什么东西?
我们可以注意到,startAbility
接口似乎并没有指定要拉起哪个Ability,这个示例中所有的参数都在want
里面,那么很明显,startAbility
就是靠这个want
参数去匹配Ability的。
在启动目标应用组件时,会通过显式Want或者隐式Want进行目标应用组件的匹配,这里说的匹配规则就是调用方传入的want参数中设置的参数如何与目标应用组件声明的配置文件进行匹配。
显式Want匹配原理如下表所示。
隐式Want匹配原理如下表所示。
简单来说,abilityName
是否留空决定了是采用显式匹配还是隐式匹配。
从隐式Want
的定义,可得知:
- 调用方传入的
want
参数,表明调用方需要执行的操作,并提供相关数据以及其他应用类型限制。 - 待匹配应用组件的
skills
配置,声明其具备的能力(module.json5
配置文件中的skills
标签参数)。
系统将调用方传入的want
参数(包含action
、entities
、uri
和type
属性)与已安装待匹配应用组件的skills
配置(包含actions
、entities
、uris
和type
属性)依次进行匹配。当四个属性匹配均通过,则此应用才会被应用选择器展示给用户进行选择。
因此,在上面的例子中,被分享应用在module.json5
配置文件的skills
标签填入了与分享方填入的want
参数相匹配的值,就能通过隐式匹配的方式成功被startAbility
匹配上。
被分享方获取信息
被分享方的UIAbility被启动后,可以在其onCreate()
或者onNewWant()
回调中获取传入的Want参数信息。
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void;
UIAbility实例处于完全关闭状态下被创建完成后进入该生命周期回调,执行初始化业务逻辑操作。即UIAbility实例冷启动时进入该生命周期回调。
通过接口want的参数获取分享文件的URI,获取文件URI后通过fs.open()接口打开文件,获取对应的file对象后,可对文件进行读写操作。
// xxx.ets
import fs from '@ohos.file.fs';
import Want from '@ohos.app.ability.Want';
import { BusinessError } from '@ohos.base';
function getShareFile() {
try {
let want: Want = ...; // 获取分享方传递过来的want信息
// 从want信息中获取uri字段
let uri = want.uri;
if (uri == null || uri == undefined) {
console.info('uri is invalid');
return;
}
try {
// 根据需要对被分享文件的URI进行相应操作。例如读写的方式打开URI获取file对象
let file = fs.openSync(uri, fs.OpenMode.READ_WRITE);
console.info('open file successfully!');
} catch (err) {
let error: BusinessError = err as BusinessError;
console.error(`Invoke openSync failed, code is ${error.code}, message is ${error.message}`);
}
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`Invoke openSync failed, code is ${err.code}, message is ${err.message}`);
}
}