HarmonyOS APP《画伴梦工厂》开发第30篇-跨设备分享——systemShare集成

第4.4篇:跨设备分享------systemShare 集成

难度 :⭐⭐ 进阶

前置知识 :第 2.9 篇 视频导出与本地保存

涉及源文件products/default/src/main/ets/services/VideoExportService.ets


概述

在"画伴梦工厂"中,用户创作完成动画视频后,不仅仅希望保存到本地------更希望能与家人、朋友分享,甚至发送到其他设备上观看。HarmonyOS 提供了 systemShare(系统分享)能力,通过标准化的分享面板,让应用可以将内容分享到其他应用或跨设备传输。

systemShare@kit.ShareKit 中的核心模块,它封装了系统级的分享服务,支持视频、图片、文本等多种数据类型,并且天然具备跨设备分享能力------用户可以将内容一键分享到同一华为账号下的平板、智慧屏、手机等其他设备。

本文将深入解析 systemShare 在项目中的完整集成实践,涵盖 ShareController 创建与配置、SharedData 多记录数据构建、uniformTypeDescriptor 类型描述、以及跨设备分享的实际应用场景。


一、systemShare 整体架构

1.1 核心类与接口

systemShare 模块围绕三个核心概念构建:

类/接口 说明
systemShare.ShareController 分享控制器,负责展示系统分享面板并管理分享流程
systemShare.SharedData 分享数据容器,可包含一条或多条分享记录
systemShare.ShareControllerOptions 分享控制选项,配置选择模式和预览模式
systemShare.SelectionMode 选择模式枚举:SINGLE(单选应用)、MULTIPLE(多选应用)
systemShare.SharePreviewMode 预览模式枚举:DETAIL(详细预览)、SUMMARY(概览预览)

1.2 分享流程概览

复制代码
用户点击"分享"按钮
       │
       ▼
prepareVideo() → 准备视频文件(缓存目录)
       │
       ▼
buildSharedData() → 构建分享数据(视频 + 文本)
       │
       ▼
new ShareController(data) → 创建分享控制器
       │
       ▼
controller.show(context, options) → 弹出系统分享面板
       │
       ├──→ 用户选择目标应用 → 执行分享
       │
       └──→ 用户取消 → 静默结束

整个过程仅需四个步骤------准备文件、构建数据、创建控制器、调用 show 方法------即可完成一次完整的系统分享。


二、ShareController:分享控制器

2.1 创建 ShareController

ShareController 是分享流程的入口,其构造函数接收一个 SharedData 实例:

typescript 复制代码
const data = await VideoExportService.buildSharedData(videoUri, title, story, rawFilePath);
const controller = new systemShare.ShareController(data);

ShareController 实例化后,会立即解析 SharedData 中的记录信息,准备展示分享面板。

2.2 启动分享面板

typescript 复制代码
const options: systemShare.ShareControllerOptions = {
  selectionMode: systemShare.SelectionMode.SINGLE,
  previewMode: systemShare.SharePreviewMode.DETAIL
};
await controller.show(context, options);

show 方法接收两个参数:

  1. contextUIAbilityContext 实例,用于获取当前页面的上下文环境。
  2. optionsShareControllerOptions 配置对象,控制分享面板的行为和外观。

show 是一个异步方法,返回 Promise<void>。当用户选择目标应用完成分享或取消操作时,Promise 会 resolve。

注意ShareController 实例为一次性使用。每次分享都需要重新创建 ShareControllerSharedData


三、ShareControllerOptions 配置详解

ShareControllerOptions 提供了两个关键的配置项,分别控制分享应用的选择方式和预览界面的展示形态。

3.1 selectionMode:选择模式

typescript 复制代码
selectionMode: systemShare.SelectionMode.SINGLE
枚举值 说明 适用场景
SelectionMode.SINGLE 单次选择模式,用户一次只能选择一个目标应用 快速分享给单个好友或设备
SelectionMode.MULTIPLE 多选模式,用户可以选择多个目标应用同时分享 同时分享到多个社交平台

在"画伴梦工厂"中,我们使用 SINGLE 模式。对于儿童动画视频的分享场景,通常用户只会将视频分享给特定的联系人(如家人)或发送到特定的设备(如智慧屏),单次选择模式更符合直觉。

3.2 previewMode:预览模式

typescript 复制代码
previewMode: systemShare.SharePreviewMode.DETAIL
枚举值 说明 效果
SharePreviewMode.DETAIL 详细预览模式 分享面板中会显示分享内容的详细预览信息(缩略图、标题、描述)
SharePreviewMode.SUMMARY 概览预览模式 仅显示简洁的分享概览信息

项目中选择了 DETAIL 模式。当用户分享动画视频时,详细预览可以让用户在发送前确认视频的标题和故事描述是否正确,提供更好的用户体验。

3.3 配置组合效果

复制代码
DETAIL + SINGLE:
┌─────────────────────────────────────┐
│         系统分享面板                  │
│  ┌──────────────────────────────┐   │
│  │  🎬 我的小汽车                │   │
│  │  一辆红色的小汽车在公路上...   │   │
│  │  [视频缩略图]                 │   │
│  └──────────────────────────────┘   │
│                                      │
│  选择应用:                          │
│  [微信] [钉钉] [智慧屏] [蓝牙]       │
│                                      │
└─────────────────────────────────────┘

四、SharedData:构建分享数据

4.1 多记录数据模型

systemShare.SharedData 支持包含多条记录,每条记录可以有不同的数据类型。在项目中,我们构建了视频记录和文本记录两条数据:

typescript 复制代码
static async buildSharedData(
  videoUri: Resource | string,
  title: string,
  story: string,
  rawFilePath: string
): Promise<systemShare.SharedData> {
  const prepared = await VideoExportService.prepareVideo(videoUri, title, rawFilePath);
  
  // 视频记录
  const videoRecord: ShareVideoRecord = {
    utd: uniformTypeDescriptor.UniformDataType.MPEG4,
    uri: prepared.uri,
    title: title,
    description: story
  };
  const data = new systemShare.SharedData(videoRecord);
  
  // 文本记录(添加为次要记录)
  const textRecord: ShareTextRecord = {
    utd: uniformTypeDescriptor.UniformDataType.PLAIN_TEXT,
    content: title + '\n' + story,
    title: title,
    description: story
  };
  data.addRecord(textRecord);
  return data;
}

4.2 记录类型接口

项目中定义了两个分享记录接口:

typescript 复制代码
interface ShareVideoRecord {
  utd: string;          // Uniform Type Descriptor,标识数据类型
  uri: string;          // 视频文件的 URI
  title: string;        // 作品标题
  description: string;  // 作品故事描述
}

interface ShareTextRecord {
  utd: string;          // 数据类型标识
  content: string;      // 文本内容
  title: string;        // 标题
  description: string;  // 描述
}

4.3 记录添加机制

  • 主记录 :通过 new systemShare.SharedData(primaryRecord) 在构造函数中指定。
  • 次要记录 :通过 data.addRecord(record) 方法动态添加,支持添加多条。

当系统分享面板接收到包含多条记录的 SharedData 时,会根据目标应用的能力自动选择合适的记录进行分享。例如:

目标应用 行为
微信/钉钉 同时展示视频和文本,用户可选择发送
智慧屏/电视 优先分享视频记录,忽略文本记录
备忘录/笔记 优先分享文本记录,视频作为附件
蓝牙传输 传输视频文件,文本作为文件描述

这种"多条记录、按需选择"的机制,使得一次分享可以适配多种不同类型的目标应用。


五、uniformTypeDescriptor:统一类型描述

5.1 什么是 UTD?

uniformTypeDescriptor(Uniform Type Descriptor,统一类型描述符)是 HarmonyOS 中用于标识文件和数据类型的标准化描述体系。它类似于 MIME 类型,但更加精细和层次化。

@kit.ArkData 中的 uniformTypeDescriptor 模块预定义了系统中常见的标准数据类型:

常量 对应类型 说明
UniformDataType.MPEG4 视频 MPEG-4 视频格式(.mp4)
UniformDataType.PLAIN_TEXT 文本 纯文本格式
UniformDataType.JPEG 图片 JPEG 图片格式
UniformDataType.PNG 图片 PNG 图片格式
UniformDataType.MP3 音频 MP3 音频格式
UniformDataType.PDF 文档 PDF 文档格式

5.2 UTD 在分享中的作用

在 systemShare 中,每条分享记录都必须指定 utd 字段,其作用包括:

  1. 应用匹配:系统根据 UTD 类型筛选出能够处理该类型数据的目标应用。
  2. 格式识别:目标应用通过 UTD 了解收到的数据类型,选择合适的处理方式。
  3. 预览展示:系统分享面板根据 UTD 决定如何展示预览内容(视频缩略图、文本摘要等)。

5.3 项目中 UTD 的使用

typescript 复制代码
import { uniformTypeDescriptor } from '@kit.ArkData';

// 视频记录使用 MPEG4 类型
const videoRecord: ShareVideoRecord = {
  utd: uniformTypeDescriptor.UniformDataType.MPEG4,
  // ...
};

// 文本记录使用 PLAIN_TEXT 类型
const textRecord: ShareTextRecord = {
  utd: uniformTypeDescriptor.UniformDataType.PLAIN_TEXT,
  // ...
};

选择正确的 UTD 类型至关重要------如果视频记录错误地使用 PLAIN_TEXT,系统分享面板将无法正确识别文件格式,目标应用也无法正常打开分享内容。


六、完整分享流程:从准备到展示

6.1 视频准备

分享的第一步是确保视频文件在本地可访问。prepareVideo 方法处理了两种场景:

typescript 复制代码
const prepared = await VideoExportService.prepareVideo(videoUri, title, rawFilePath);
  • Resource(rawfile 资源) :通过 resourceManager.getRawFileContent 读取内置视频,写入应用缓存目录。
  • string(本地路径):校验路径有效性,直接使用现有文件。

具体细节已在第 2.9 篇中详细讲解,此处不再赘述。

6.2 数据构建

buildSharedData 方法接收四个参数:

参数 类型 说明
videoUri `Resource string`
title string 作品标题
story string 作品故事描述
rawFilePath string rawfile 中的视频路径(仅 Resource 类型需要)

方法内部依次完成:准备视频 → 创建视频记录 → 实例化 SharedData → 添加文本记录。

6.3 展示分享面板

typescript 复制代码
const context = getContext() as common.UIAbilityContext;
const controller = new systemShare.ShareController(data);
const options: systemShare.ShareControllerOptions = {
  selectionMode: systemShare.SelectionMode.SINGLE,
  previewMode: systemShare.SharePreviewMode.DETAIL
};
await controller.show(context, options);

controller.show 调用后,系统会弹出分享面板,用户完成选择或取消后,Promise resolve。

6.4 完整流程时序图

复制代码
VideoExportService.showSystemShare()
       │
       ├── prepareVideo(videoUri, title, rawFilePath)
       │     ├── typeof videoUri === 'string' → 直接使用本地文件
       │     └── typeof videoUri === 'Resource' → 读取 rawfile → 写入 cacheDir
       │     └── 返回 PreparedVideoFile { path, uri, fileName }
       │
       ├── buildSharedData(videoUri, title, story, rawFilePath)
       │     ├── 调用 prepareVideo() 获取 prepared
       │     ├── 创建 ShareVideoRecord { utd: MPEG4, uri, title, description }
       │     ├── new SharedData(videoRecord)  ← 主记录
       │     ├── 创建 ShareTextRecord { utd: PLAIN_TEXT, content, title, description }
       │     └── data.addRecord(textRecord)   ← 添加次要记录
       │
       ├── new ShareController(data)
       │
       └── controller.show(context, {
             selectionMode: SINGLE,
             previewMode: DETAIL
           })
             │
             ├── 用户选择应用 → 分享成功
             │
             └── 用户取消 → 流程结束

七、与 UI 层集成

在 Index.ets 页面中,分享功能通过 shareCurrentVideo 方法触发:

typescript 复制代码
private async shareCurrentVideo(): Promise<void> {
  if (this.exportBusy) {
    return;
  }
  this.exportBusy = true;
  this.showNotice('正在打开分享');
  try {
    await VideoExportService.showSystemShare(
      this.getCurrentVideo(),
      this.getCurrentWorkTitle(),
      this.getCurrentWorkStory(),
      this.getCurrentRawVideoPath()
    );
    this.sharedActive = true;
    this.showNotice('已打开分享面板');
  } catch (error) {
    this.showNotice('分享失败:' + this.getErrorMessage(error as Error));
  } finally {
    this.exportBusy = false;
  }
}

设计要点

1. 防重复操作锁

exportBusy 是一个 @State 布尔变量,用于防止用户在分享进行中重复点击:

typescript 复制代码
if (this.exportBusy) {
  return;  // 正在分享中,忽略重复请求
}

2. 用户反馈

分享过程中通过 showNotice 显示状态提示文字:

  • "正在打开分享" → 用户操作触发后立即提示
  • "已打开分享面板" → 分享面板成功弹出后提示
  • "分享失败:xxx" → 发生异常时提示错误原因

3. try/catch/finally 资源清理

typescript 复制代码
try {
  // 执行分享
} catch (error) {
  // 异常处理
} finally {
  this.exportBusy = false;  // 无论成功失败,最终释放锁
}

这种模式确保 exportBusy 在任何情况下都能正确释放,不会出现"死锁"状态。


八、跨设备分享场景

systemShare 的跨设备能力是其核心价值之一。当用户在同一华为账号下拥有多台设备时,系统分享面板会自动发现并列出可用的跨设备目标。

8.1 手机 → 平板

场景:孩子在手机上用"画伴梦工厂"制作了一段动画,想在大屏平板上全屏观看。

流程

  1. 点击"分享"按钮 → 系统弹出分享面板
  2. 分享面板自动显示同一华为账号下的平板设备
  3. 选择平板 → 视频通过分布式软总线传输到平板
  4. 平板上打开视频,大屏观看体验更佳

8.2 手机 → 智慧屏

场景:家庭聚会时,孩子想把自己创作的动画展示在电视上。

流程

  1. 点击"分享" → 分享面板显示智慧屏设备
  2. 选择智慧屏 → 视频传输到电视
  3. 电视播放动画,全家一起观看

8.3 手机 → 其他应用

场景:将视频分享到微信、钉钉等社交应用。

流程

  1. 点击"分享" → 分享面板显示已安装的社交应用
  2. 选择微信 → 系统将视频和文本描述一起发送到微信
  3. 在微信中选择联系人发送

8.4 跨设备分享的技术基础

systemShare 的跨设备能力依赖于 HarmonyOS 的分布式软总线技术:

  • 设备发现:自动发现同一华为账号下的在线设备
  • 安全传输:端到端加密,保障数据传输安全
  • 类型适配:目标设备根据 UTD 类型自动匹配处理方式
  • 无缝体验:用户无需手动配对或配置

九、systemShare vs. 其他分享方式

9.1 与 DocumentViewPicker 的对比

维度 systemShare DocumentViewPicker
核心功能 分享到其他应用/设备 保存到本地文件系统
数据流向 发送出去 保存到本地
跨设备 ✅ 天然支持 ❌ 仅本地保存
数据类型 视频、文本、图片等多类型 主要是文件
用户交互 选择目标应用 选择保存位置
使用场景 分享给好友、发送到其他设备 备份到本地、导出到电脑

9.2 与 startAbilityForResult 的对比

startAbilityForResult 也可以启动其他应用,但它是"定向启动"------开发者必须预先知道目标应用的 Ability 信息。而 systemShare 是"系统级分发"------由用户自由选择目标,开发者只需提供数据即可。

typescript 复制代码
// startAbilityForResult:定向启动
await context.startAbilityForResult({
  bundleName: 'com.example.target',
  abilityName: 'TargetAbility'
});

// systemShare:系统分发
await controller.show(context, options);

十、最佳实践与注意事项

10.1 数据容量考虑

  • 分享数据不宜过大。大文件分享建议先压缩或提供下载链接。
  • 文本记录内容长度适中,过长的描述可能在某些应用中显示不全。

10.2 UTD 类型准确性

  • 务必为每条记录指定正确的 UTD 类型,否则目标应用可能无法识别数据。
  • 常见的视频类型使用 UniformDataType.MPEG4,图片使用 UniformDataType.JPEGUniformDataType.PNG

10.3 上下文生命周期

  • context 必须是有效的 UIAbilityContext,不能使用已销毁的页面上下文。
  • 建议在调用分享前检查上下文状态,避免分享面板弹出时页面已关闭。

10.4 资源清理

  • ShareController 实例不使用时无需手动销毁,系统会自动管理。
  • 缓存目录中的临时视频文件可以在适当时机清理,避免占用过多存储空间。

10.5 用户隐私

  • 分享内容中不应包含用户敏感信息。
  • 项目中的文本记录仅包含作品标题和故事,不包含用户个人信息。

十一、完整代码总览

最终,VideoExportService 中与分享相关的完整代码如下:

typescript 复制代码
// 展示系统分享面板
static async showSystemShare(
  videoUri: Resource | string,
  title: string,
  story: string,
  rawFilePath: string
): Promise<void> {
  const context = getContext() as common.UIAbilityContext;
  const data = await VideoExportService.buildSharedData(videoUri, title, story, rawFilePath);
  const controller = new systemShare.ShareController(data);
  const options: systemShare.ShareControllerOptions = {
    selectionMode: systemShare.SelectionMode.SINGLE,
    previewMode: systemShare.SharePreviewMode.DETAIL
  };
  await controller.show(context, options);
}

// 构建分享数据
static async buildSharedData(
  videoUri: Resource | string,
  title: string,
  story: string,
  rawFilePath: string
): Promise<systemShare.SharedData> {
  const prepared = await VideoExportService.prepareVideo(videoUri, title, rawFilePath);
  
  const videoRecord: ShareVideoRecord = {
    utd: uniformTypeDescriptor.UniformDataType.MPEG4,
    uri: prepared.uri,
    title: title,
    description: story
  };
  const data = new systemShare.SharedData(videoRecord);
  
  const textRecord: ShareTextRecord = {
    utd: uniformTypeDescriptor.UniformDataType.PLAIN_TEXT,
    content: title + '\n' + story,
    title: title,
    description: story
  };
  data.addRecord(textRecord);
  return data;
}

总结

本文深入讲解了 HarmonyOS systemShare 在"画伴梦工厂"中的完整集成实践:

知识点 说明
ShareController 分享控制器,通过构造函数传入 SharedData,调用 show 弹出分享面板
ShareControllerOptions 配置 selectionMode(单选/多选)和 previewMode(详细/概览预览)
SharedData 多记录 支持主记录 + 多条次要记录,不同目标应用按需选择
uniformTypeDescriptor 统一类型描述,标识数据格式,影响应用匹配和预览展示
分享流程 prepareVideo → buildSharedData → new ShareController → show
防重复操作 exportBusy 状态锁 + try/catch/finally 资源释放
跨设备分享 手机 → 平板/智慧屏/社交应用,基于分布式软总线自动发现设备

systemShare 的精妙之处在于其"一次集成、处处可用"的设计理念------开发者只需构建数据、弹出面板,系统负责应用的匹配、数据的传输、跨设备的协同。这种设计让应用能够以极低的成本获得强大的分享能力,也是 HarmonyOS 全场景智慧体验的典型体现。


参考源码

本文所有代码均来自项目文件:

  • products/default/src/main/ets/services/VideoExportService.ets --- 视频导出服务的完整实现,包含 systemShare 分享、DocumentViewPicker 保存、文件操作等核心方法