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属性,并通过设置wantflag来设置对应的读写权限,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,如下表示本应用只接收schemefile,类型为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参数(包含actionentitiesuritype属性)与已安装待匹配应用组件的skills配置(包含actionsentitiesuristype属性)依次进行匹配。当四个属性匹配均通过,则此应用才会被应用选择器展示给用户进行选择

因此,在上面的例子中,被分享应用在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}`);
  }
}

Reference