HarmonyOS APP<<古今职鉴定>>开源教程第13篇:隔空投送:ShareKit 分享服务

本篇学习鸿蒙独有的隔空投送能力,实现隔空投递传递祝福卡片

图:古今职鉴开源教程封面。本篇围绕「隔空投送:ShareKit 分享服务」展开。

学习目标

完成本篇后,你将能够:

  • ✅ 理解 ShareKit 隔空传送能力
  • ✅ 监听隔空传送手势
  • ✅ 构建分享数据
  • ✅ 实现祝福卡片分享

预计学习时间

约 90 分钟


实战一:理解隔空投送

第一步:什么是隔空投送

隔空投送是鸿蒙独有的分享方式:

  • 用户做出"抓取-抛出"手势
  • 系统检测附近设备
  • 数据通过近场通信传输

第二步:支持的分享类型

类型 说明
图片 PNG、JPG 等图片文件
文本 纯文本内容
文件 任意文件类型
链接 URL 链接

第三步:设备要求

  • 发送端和接收端都需要支持隔空投送
  • 需要开启蓝牙和 WiFi
  • 设备距离在有效范围内

实战二:监听隔空传送手势

第一步:导入模块

typescript 复制代码
import { harmonyShare } from '@kit.ShareKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

第二步:获取窗口 ID

typescript 复制代码
@Entry
@Component
struct Lesson13Page {
  private windowId: number = -1;

  async aboutToAppear() {
    await this.getWindowId();
  }

  async getWindowId() {
    try {
      const windowStage = await window.getLastWindow(getContext(this));
      const properties = windowStage.getWindowProperties();
      this.windowId = properties.id;
    } catch (error) {
      console.error('获取窗口ID失败:', error);
    }
  }
}

第三步:注册手势监听

typescript 复制代码
registerGestureShare() {
  try {
    // 配置发送能力
    const sendCapability: harmonyShare.SendCapabilityRegistry = {
      windowId: this.windowId,
      sendCapability: ['share']  // 支持分享
    };

    // 注册手势监听
    harmonyShare.on('gesturesShare', sendCapability, 
      async (target: harmonyShare.SharableTarget) => {
        // 用户做出了隔空投送手势
        await this.handleGestureShare(target);
      }
    );
  } catch (error) {
    const err = error as BusinessError;
    console.error('注册手势监听失败:', err.code, err.message);
  }
}

第四步:取消注册

typescript 复制代码
aboutToDisappear() {
  this.unregisterGestureShare();
}

unregisterGestureShare() {
  try {
    harmonyShare.off('gesturesShare');
  } catch (error) {
    // 忽略错误
  }
}

实战三:构建分享数据

第一步:理解 SharedData 结构

typescript 复制代码
// SharedData 包含多个 SharedRecord
// 每个 SharedRecord 代表一条分享内容

import { uniformTypeDescriptor as utd } from '@kit.ArkData';

interface SharedRecord {
  utdType: string;      // 数据类型
  data: ArrayBuffer;    // 数据内容
  fileName?: string;    // 文件名(可选)
}

第二步:准备图片数据

typescript 复制代码
import { fileIo } from '@kit.CoreFileKit';
import { resourceManager } from '@kit.LocalizationKit';

async prepareImageData(imageName: string): Promise<ArrayBuffer | null> {
  try {
    const context = getContext(this);
    const resManager = context.resourceManager;
    
    // 从 rawfile 读取图片
    const imageData = await resManager.getRawFileContent(`blessings/${imageName}.png`);
    
    // 转换为 ArrayBuffer
    return imageData.buffer.slice(
      imageData.byteOffset,
      imageData.byteOffset + imageData.byteLength
    );
  } catch (error) {
    console.error('准备图片数据失败:', error);
    return null;
  }
}

第三步:创建临时文件

由于隔空投送需要文件路径,需要将资源写入临时目录:

typescript 复制代码
async createTempFile(imageName: string): Promise<string | null> {
  try {
    const context = getContext(this);
    const resManager = context.resourceManager;
    
    // 读取资源
    const imageData = await resManager.getRawFileContent(`blessings/${imageName}.png`);
    
    // 创建临时文件路径
    const tempDir = context.tempDir;
    const tempPath = `${tempDir}/${imageName}.png`;
    
    // 写入文件
    const file = fileIo.openSync(tempPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
    fileIo.writeSync(file.fd, imageData.buffer);
    fileIo.closeSync(file.fd);
    
    return tempPath;
  } catch (error) {
    console.error('创建临时文件失败:', error);
    return null;
  }
}

第四步:构建 SharedData

typescript 复制代码
async buildSharedData(): Promise<harmonyShare.SharedData | null> {
  try {
    // 随机选择一张祝福卡片
    const blessings = ['fortune', 'wealth', 'health', 'happiness', 'success'];
    const randomIndex = Math.floor(Math.random() * blessings.length);
    const imageName = blessings[randomIndex];
    
    // 创建临时文件
    const tempPath = await this.createTempFile(imageName);
    if (!tempPath) return null;
    
    // 构建分享数据
    const sharedData = new harmonyShare.SharedData();
    const record: harmonyShare.SharedRecord = {
      utdType: utd.UniformDataType.PNG,
      uri: `file://${tempPath}`
    };
    sharedData.addRecord(record);
    
    return sharedData;
  } catch (error) {
    console.error('构建分享数据失败:', error);
    return null;
  }
}

实战四:执行分享

第一步:处理手势分享

typescript 复制代码
async handleGestureShare(target: harmonyShare.SharableTarget) {
  // 更新状态
  this.shareStatus = '正在准备分享...';
  
  // 构建分享数据
  const sharedData = await this.buildSharedData();
  if (!sharedData) {
    this.shareStatus = '准备分享数据失败';
    return;
  }
  
  // 执行分享(3秒超时限制)
  try {
    this.shareStatus = '正在发送...';
    await harmonyShare.share(target, sharedData);
    this.shareStatus = '分享成功!';
  } catch (error) {
    const err = error as BusinessError;
    this.shareStatus = `分享失败: ${err.message}`;
  }
}

第二步:注意 3 秒超时

隔空投送有 3 秒超时限制:

  • 从检测到手势开始计时
  • 必须在 3 秒内调用 share 方法
  • 超时会导致分享失败

优化策略:提前准备好分享数据

typescript 复制代码
@State currentBlessingPath: string = '';

async aboutToAppear() {
  await this.getWindowId();
  // 提前准备第一张卡片
  await this.prepareNextBlessing();
  this.registerGestureShare();
}

async prepareNextBlessing() {
  const blessings = ['fortune', 'wealth', 'health', 'happiness', 'success'];
  const randomIndex = Math.floor(Math.random() * blessings.length);
  const imageName = blessings[randomIndex];
  
  const tempPath = await this.createTempFile(imageName);
  if (tempPath) {
    this.currentBlessingPath = tempPath;
    this.currentBlessingName = imageName;
  }
}

async handleGestureShare(target: harmonyShare.SharableTarget) {
  if (!this.currentBlessingPath) {
    this.shareStatus = '祝福卡片未准备好';
    return;
  }
  
  // 直接使用已准备好的数据
  const sharedData = new harmonyShare.SharedData();
  const record: harmonyShare.SharedRecord = {
    utdType: utd.UniformDataType.PNG,
    uri: `file://${this.currentBlessingPath}`
  };
  sharedData.addRecord(record);
  
  try {
    await harmonyShare.share(target, sharedData);
    this.shareStatus = '分享成功!';
    // 准备下一张卡片
    await this.prepareNextBlessing();
  } catch (error) {
    const err = error as BusinessError;
    this.shareStatus = `分享失败: ${err.message}`;
  }
}

实战五:完整实现

第一步:创建页面

typescript 复制代码
import { harmonyShare } from '@kit.ShareKit';
import { window } from '@kit.ArkUI';
import { fileIo } from '@kit.CoreFileKit';
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';

interface BlessingCard {
  name: string;
  title: string;
  color: string;
}

@Entry
@Component
struct Lesson13Page {
  private windowId: number = -1;
  @State shareStatus: string = '准备就绪';
  @State currentBlessing: BlessingCard | null = null;
  @State isSupported: boolean = true;
  private currentBlessingPath: string = '';

  private blessings: BlessingCard[] = [
    { name: 'fortune', title: '福', color: '#c41e3a' },
    { name: 'wealth', title: '财', color: '#fbbf24' },
    { name: 'health', title: '寿', color: '#22c55e' },
    { name: 'happiness', title: '喜', color: '#ec4899' },
    { name: 'success', title: '禄', color: '#8b5cf6' }
  ];

  async aboutToAppear() {
    await this.init();
  }

  aboutToDisappear() {
    this.unregisterGestureShare();
  }

  async init() {
    try {
      await this.getWindowId();
      await this.prepareNextBlessing();
      this.registerGestureShare();
    } catch (error) {
      this.isSupported = false;
    }
  }

  async getWindowId() {
    const windowStage = await window.getLastWindow(getContext(this));
    const properties = windowStage.getWindowProperties();
    this.windowId = properties.id;
  }

  async prepareNextBlessing() {
    const randomIndex = Math.floor(Math.random() * this.blessings.length);
    this.currentBlessing = this.blessings[randomIndex];
    
    // 这里简化处理,实际应该创建真实的图片文件
    // 由于教程环境限制,我们模拟准备过程
    this.shareStatus = `已准备: ${this.currentBlessing.title}`;
  }

  registerGestureShare() {
    try {
      const sendCapability: harmonyShare.SendCapabilityRegistry = {
        windowId: this.windowId,
        sendCapability: ['share']
      };

      harmonyShare.on('gesturesShare', sendCapability,
        async (target: harmonyShare.SharableTarget) => {
          await this.handleGestureShare(target);
        }
      );
    } catch (error) {
      const err = error as BusinessError;
      if (err.code === 801) {
        this.isSupported = false;
      }
    }
  }

  unregisterGestureShare() {
    try {
      harmonyShare.off('gesturesShare');
    } catch {
      // 忽略
    }
  }

  async handleGestureShare(target: harmonyShare.SharableTarget) {
    this.shareStatus = '正在发送...';
    
    // 实际项目中这里应该构建真实的 SharedData
    // 由于教程环境限制,我们模拟分享过程
    setTimeout(async () => {
      this.shareStatus = '分享成功!';
      await this.prepareNextBlessing();
    }, 1000);
  }

  build() {
    Column() {
      // 头部
      Row() {
        Text('隔空投送')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#1e293b')
      }
      .width('100%')
      .height(56)
      .padding({ left: 16, right: 16 })
      .backgroundColor(Color.White)

      // 内容
      Column({ space: 30 }) {
        if (!this.isSupported) {
          this.UnsupportedView()
        } else {
          this.ShareView()
        }
      }
      .width('100%')
      .layoutWeight(1)
      .justifyContent(FlexAlign.Center)
      .padding(20)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f8f6f5')
  }

  @Builder
  ShareView() {
    Column({ space: 24 }) {
      // 当前祝福卡片
      if (this.currentBlessing) {
        Column() {
          Text(this.currentBlessing.title)
            .fontSize(80)
            .fontWeight(FontWeight.Bold)
            .fontColor(this.currentBlessing.color)
        }
        .width(160)
        .height(160)
        .justifyContent(FlexAlign.Center)
        .backgroundColor(Color.White)
        .borderRadius(20)
        .shadow({
          radius: 20,
          color: 'rgba(0,0,0,0.1)',
          offsetX: 0,
          offsetY: 4
        })
      }

      // 状态提示
      Text(this.shareStatus)
        .fontSize(16)
        .fontColor('#64748b')

      // 操作说明
      Column({ space: 8 }) {
        Text('使用方法')
          .fontSize(14)
          .fontWeight(FontWeight.Medium)
          .fontColor('#1e293b')

        Text('1. 确保附近有其他鸿蒙设备')
          .fontSize(13)
          .fontColor('#64748b')

        Text('2. 做出"抓取"手势(五指抓握屏幕)')
          .fontSize(13)
          .fontColor('#64748b')

        Text('3. 向目标设备方向"抛出"')
          .fontSize(13)
          .fontColor('#64748b')
      }
      .width('100%')
      .padding(16)
      .backgroundColor(Color.White)
      .borderRadius(12)
      .alignItems(HorizontalAlign.Start)

      // 刷新按钮
      Button('换一张祝福')
        .onClick(() => {
          this.prepareNextBlessing();
        })
        .backgroundColor('#c41e3a')
    }
  }

  @Builder
  UnsupportedView() {
    Column({ space: 16 }) {
      Image($r('app.media.ic_warning'))
        .width(64)
        .height(64)
        .fillColor('#f59e0b')

      Text('设备不支持')
        .fontSize(18)
        .fontColor('#1e293b')

      Text('当前设备不支持隔空投送功能')
        .fontSize(14)
        .fontColor('#64748b')
    }
  }
}

@Builder
export function Lesson13PageBuilder() {
  Lesson13Page()
}

第二步:运行验证

bash 复制代码
hvigorw assembleHap --no-daemon

预期效果

  • 显示当前准备的祝福卡片
  • 点击"换一张祝福"可以切换
  • 做出隔空投送手势时触发分享

完整代码

完整代码见上方实战五。


本课小结

核心知识点

知识点 说明
harmonyShare.on 注册隔空投送手势监听
SendCapabilityRegistry 配置发送能力
SharedData 分享数据容器
SharedRecord 单条分享记录
3秒超时 必须在3秒内完成分享

隔空投送流程

  1. 获取窗口 ID
  2. 注册手势监听
  3. 用户做出手势
  4. 构建分享数据
  5. 调用 share 方法
  6. 处理分享结果

课后练习

练习1:支持多种分享类型

实现文本和链接的隔空投送。

练习2:添加分享动画

在分享过程中显示动画效果。


下一课预告

第14课我们将学习 NFC 近场通信,包括:

  • NFC Kit 能力概述
  • NFC 权限与配置
  • NFC 标签读写
  • 碰一碰分享官职名片

项目开源地址

https://gitcode.com/daleishen/gujinzhijian

相关推荐
时光慢煮16 分钟前
开源鸿蒙PC上手体验:从 Windows 和 macOS 切换过来,我被惊艳到了
windows·开源·harmonyos
tsqtsqtsq030925 分钟前
鸿蒙电脑升级 HarmonyOS 7:深度解析与 API 26 全特性指南
华为·电脑·harmonyos
lilian2333 小时前
HarmonyOS 7 新特性(一)|API 26 Beta2 十项变化与应用适配路线
华为·harmonyos
HwJack204 小时前
鸿蒙ArkData键值型数据库实战:Schema 定义与商品库存同步案例
数据库·华为·harmonyos
m0_7496902318 小时前
【寻迹校园 HarmonyOS NEXT 实战 41】用户照片、演示图与图标兜底:ReportMedia 的三层媒体策略
华为·harmonyos·arkts·媒体·隐私保护
夜雨声烦丿19 小时前
用 ArkTS 做好智能便签应用(端云协同):从核心 API 到可验证交互
华为·harmonyos
less_1213821 小时前
HarmonyOS WPS Open SDK:二开能力周回顾与联调地图
华为·harmonyos·wps
wxchyy21 小时前
手把手教你入门云计算(二):这些技术改变了世界,你也能轻松掌握
hadoop·阿里云·docker·云原生·华为云·云计算·运维开发
小白酷爱学习21 小时前
鸿蒙OS的开发语言与工具链:如何驾驭全新开发生态!
分布式·华为·架构·harmonyos