轻规划鸿蒙开发实战6:Share Kit 碰一碰近场快传,外链 aeroplan 协议与 Base64 字节流无缝对接

背景介绍

在社交和日常团队协作场景下,用户经常有分享规划目标(如一份精心定制的“程序员三十岁前财务自由计划”、“西藏骑行旅行路线”等)的需求。

传统的分享极其繁琐且存在体验割裂:用户需要点击分享,将长文本复制到剪贴板或生成一张大图,接着手动切换打开第三方即时通讯软件,发送给朋友;而接收方则需下载并保存图片或复制文本,再次返回到目标 App 中进行导入解析。这种漫长且伴随着频繁跨应用跳转的操作路径,不仅容易因为系统内存不足导致后台进程被清理,还大幅降低了分享的转化率。

HarmonyOS NEXT 提供了强大的系统级近场通信能力——Share Kit(碰一碰分享)
在这里插入图片描述

当用户开启碰一碰时,两台登录不同华为账号的鸿蒙手机轻轻触碰,就可以无缝唤起系统级的快传气泡。我们在“轻规划”(AeroPlan)中,通过定制专属的 aeroplan:// 链接协议,将九宫格核心数据打包为 Base64 字节流,两机一碰,即刻在对端拉起并完整复原这套精美规划。

今天,我们将从协议定制、配置文件声明、Base64 编解码落盘全链路进行实战解构。


1. 架构纵览:碰一碰近场通信与外链唤醒管线

当分享发起时,A 设备负责将内存中的复杂规划数据序列化为短文本链接;碰一碰激活后,系统在近场建立临时 P2P 通信信道,将链接投射给 B 设备;B 设备捕捉到 aeroplan:// 后自动反向冷启动并解析入库。其底层的核心流转管线如下:

接收端应用 (轻规划) 接收端系统 (Want 路由) 发送端系统 (Share Kit) 发送端应用 (轻规划) 接收端应用 (轻规划) 接收端系统 (Want 路由) 发送端系统 (Share Kit) 发送端应用 (轻规划) 物理碰一碰 (NFC握手 ->> 建立临时蓝牙/Wi-Fi P2P通道) 数据序列化 (JSON ->> Base64 压缩字节流) 1 注册 SharedData & 呼起 ShareController 2 极速投递 scheme: aeroplan://import_plan 3 解析 Want / 匹配 module.json5 中的 skills 规则 4 拉起应用生命周期 (onCreate / onNewWant) 5 拦截 URI ->> 提取 Base64 字符串 ->> 还原 JSON 数据 6 UI 层弹窗确认 ->> 写入本地超轻量级关系数据库 (RDB) 7

通过这一物理碰一碰设计,我们将传统流程中至少 6 次的主动操作压缩为“碰一碰 -> 确认导入” 2 步,消除了用户在跨应用数据流转过程中的繁琐体验。


2. 发送端:Share Kit 碰一碰数据注册与投射

使用 Share Kit 发送数据,我们需要向系统服务注册当前需要投射的分享数据卡片。这需要借助于鸿蒙系统自带的 @kit.ShareKit 模块。在实际开发中,由于用户的规划数据(包括各节点任务、打卡周期、优先级等)是结构化的内存对象,我们需要将其序列化为字节流,再转换为 Base64 格式,拼装在自定义的外链中。

碰一碰数据投射核心代码
import { systemShare } from '@kit.ShareKit';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { util } from '@kit.ArkTS';

/**
 * 近场快传助手类,负责将本地内存规划序列化为 Base64 并拉起系统分享面板
 */
export class NearFieldShareHelper {
  /**
   * 触发碰一碰近场数据分享
   * @param context UIAbility 上下文,用于提供拉起系统级气泡所需的生命周期上下文
   * @param planTitle 规划的标题,例如“西藏骑行路线”
   * @param planData 规划的 JSON 字符串数据
   */
  public static async triggerNearFieldShare(context: common.UIAbilityContext, planTitle: string, planData: string): Promise<void> {
    try {
      // 1. 将复杂的规划 JSON 数据转化为字节流,用于后续进行 Base64 编码,确保在传输链路上的高抗干扰性
      const textEncoder = new util.TextEncoder();
      // encodeInto 返回 Uint8Array,这属于直接内存映射操作,比普通的循环转换性能高出 60% 以上
      const rawBytes = textEncoder.encodeInto(planData);
      
      // 2. 利用系统工具类,同步将字节流转换为标准的 Base64 字符串,用于外链中安全传递
      const base64Helper = new util.Base64Helper();
      const base64Str = base64Helper.encodeToStringSync(rawBytes);
      
      // 3. 构建专属自定义 Scheme 协议外链(URI),确保接收端收到后能正确路由到轻规划应用
      // 考虑到标题中可能包含中文字符和特殊标点,使用 encodeURIComponent 对标题进行安全编码
      const shareUrl = `aeroplan://import_plan?title=${encodeURIComponent(planTitle)}&data=${base64Str}`;

      // 4. 创建系统级共享数据块,UTD(Uniform Type Descriptor)声明为链接类型
      const shareData = new systemShare.SharedData({
        utd: 'usid.link', // 'usid.link' 为鸿蒙官方约定的超链接标准类型标识符
        title: `【轻规划分享】${planTitle}`,
        description: "快来碰一碰导入我为你定制的专属规划!",
        content: shareUrl // 包含 Base64 字节流的 Scheme 地址
      });

      // 5. 构建 ShareController 并配置预览模式与选择模式,启动近场共享气泡
      const controller = new systemShare.ShareController(shareData);
      
      // 唤起系统分享控制器
      await controller.show(context, {
        previewMode: systemShare.SharePreviewMode.DETAIL, // 详细预览模式,显示标题与说明
        selectionMode: systemShare.ShareSelectionMode.SINGLE // 单目标传输模式,防止近场干扰误投
      });
      
      console.info("NearFieldShareHelper", "Share controller shown successfully");
    } catch (err) {
      // 捕获可能产生的业务级错误,防止因系统接口调用失败导致应用发生崩溃
      const error = err as BusinessError;
      console.error("NearFieldShareHelper", `Share trigger failed: Code: ${error.code}, Message: ${error.message}`);
    }
  }
}

3. 配置文件:注册自定义 Scheme 协议

为了让接收端能够拦截并唤起 aeroplan://import_plan 协议,我们需要在项目的主模块配置文件 module.json5 中注册对应的 Scheme。在 skillsuris 列表中声明自定义协议,这样当系统检测到此类 URI 时,就会自动唤起我们的应用。

module.json5 核心配置

entry/src/main/module.json5 文件的 abilities 数组中,找到 EntryAbility,并配置以下 skills 字段:

{
  "module": {
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:layered_image",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:startIcon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "actions": [
              "ohos.want.action.viewData" // 声明该 Ability 具备查看数据资产的能力
            ],
            "entities": [
              "entity.system.default",
              "entity.system.browsable" // 声明可以通过浏览器或者外链的形式进行唤醒跳转
            ],
            "uris": [
              {
                "scheme": "aeroplan",      // 自定义 Scheme 协议头,必须与发送端构建的 shareUrl 完全对应
                "host": "import_plan",     // 主机路径名,用于区分子业务路由
                "linkFeature": "importPlan" // 自定义链接特征描述
              }
            ]
          }
        ]
      }
    ]
  }
}

4. 接收端:Scheme 外链拦截与 Base64 反序列化

当 B 设备接收到碰一碰传来的 aeroplan:// 外链时,系统根据上面的 skills 配置查找到“轻规划”应用并自动唤起。我们需要在 EntryAbility.etsonCreate(冷启动场景)与 onNewWant(热启动场景,应用已驻留后台)生命周期钩子中进行数据劫持与提取。

EntryAbility 接收解析代码
import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { util } from '@kit.ArkTS';

/**
 * 接收端主能力类,负责监听外部 Want 调起并解析其中的 Base64 数据
 */
export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 处理应用处于未启动状态(冷启动)时被碰一碰拉起的情况
    this.handleIncomingWant(want);
  }

  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 应用已经在后台运行(热启动)时被碰一碰拉起,触发此生命周期回调
    // 必须在此重新获取传入的最新 Want,否则拿到的将是首次启动的旧 Want 数据
    this.handleIncomingWant(want);
  }

  /**
   * 拦截并过滤传入的 Want 信息,定位 aeroplan 协议的 URI
   */
  private handleIncomingWant(want: Want) {
    const uri = want.uri;
    // 判断传入的 uri 是否存在且符合约定的 Scheme 规则
    if (uri && uri.startsWith('aeroplan://import_plan')) {
      console.info("EntryAbility", `Incoming scheme detected: ${uri}`);
      this.parseAndImportPlan(uri);
    }
  }

  /**
   * 解析 Scheme 路径,完成 Base64 反序列化并交由 UI 层处理
   * @param uriStr 传入的完整 Scheme URI 字符串
   */
  private parseAndImportPlan(uriStr: string) {
    try {
      // 1. 构建 URL 解析工具类,用于提取 URL 上的查询参数
      const url = new URL(uriStr);
      const dataParam = url.searchParams.get('data');
      if (!dataParam) {
        console.warn("EntryAbility", "Parameter 'data' not found in incoming scheme");
        return;
      }

      // 2. 将 URL 传参中的 Base64 编码字符串还原为底层的字节数组
      const base64Helper = new util.Base64Helper();
      const decodedBytes = base64Helper.decodeSync(dataParam);
      
      // 3. 将字节数组通过 TextDecoder 解码成应用可识别的 JSON 文本字符串
      const textDecoder = new util.TextDecoder('utf-8');
      const planJson = textDecoder.decodeWithStream(decodedBytes);

      // 4. 将解析出的 JSON 暂存到全局 AppStorage,以实现从业务引擎到 UI 渲染页面的跨层级事件总线通知
      AppStorage.setOrCreate('pendingImportPlanData', planJson);
      // 开启标志位,通知应用内的弹窗组件弹出“发现好友分享的新规划,是否导入?”的确认窗
      AppStorage.setOrCreate('triggerImportDialog', true);
      
      console.info("EntryAbility", "Successfully decoded and staged pending plan data");
    } catch (e) {
      // 捕获解码转换过程中的任何异常(如传输中断导致 Base64 结构受损)
      console.error("EntryAbility", "Decode incoming plan failed. Error structure: " + JSON.stringify(e));
    }
  }
}

分享方界面:
在这里插入图片描述

接收方气泡提示:
在这里插入图片描述

接收方自动弹窗确认并导入数据:
在这里插入图片描述


5. 极客避坑:Base64 编码的超长 URL 限制与熔断

在 HarmonyOS 系统的 URL 传参解析中,如果我们将一个非常庞大的规划项目(包含成百上千个子任务、复杂日志、背景图片 Base64 串等)强行转化为 Base64,生成的 Scheme 长度可能超过 8KB。在部分旧款设备或高负载场景下,系统拉起 Scheme 时会对超长链接进行截断,导致数据损坏无法解码。

避坑指南:URL 长度容灾策略

为了确保系统的稳定可靠,避免在超大规划分享时发生闪退或乱码错误,我们在打包发送阶段设计了安全熔断与降级策略

const MAX_URL_LENGTH_BYTES = 4096; // 4KB 核心安全限制阈值

if (base64Str.length > MAX_URL_LENGTH_BYTES) {
  // 触发本地降级逻辑:
  // 若规划数据包过大,不再通过 Scheme 中携带明文 Base64 的方式传输,
  // 而是改为调用云端临时中转接口,将数据托管至分布式临时云空间,仅在 Scheme 中携带 6 位随机“提取码”
  console.warn("NearFieldShareHelper", "Data package too large for Scheme URL, degrading to code retrieval");
  
  // 示例伪代码:
  // const extractCode = await CloudSyncService.uploadTempData(planData);
  // const shareUrl = `aeroplan://import_plan?code=${extractCode}`;
}

这一熔断安全垫设计,从应用架构的健壮性上切断了由于超长 URL 限制导致的不可预期稳定性风险,实现了良好的用户体验保障。


6. 总结与下期预告

通过原生 Share Kit 碰一碰近场传输功能,搭配自定义的专属 aeroplan:// 协议与高效的 Base64 字节流编解码处理方案,“轻规划”消灭了效率应用跨设备沟通的繁琐壁垒,让灵感在设备触碰的瞬间实现零摩擦传递。

近场快传解决了“两机碰一碰,数据瞬间达”的物理距离交互。但如果用户手里有多个属于自己的鸿蒙设备(如手机、平板和二合一电脑),如何实现个人工作流的无缝流转呢?例如,在小屏手机上正在记录的文字,可以直接在身边的平板大屏上同一个光标位置继续编辑,完全不需要任何手动保存或分享。

在下一期中,我们将全面切入鸿蒙开发的进阶深水区:Ability Kit 原生应用接续(Continuation),在 EntryAbility 全生命周期重写 onContinue 协议,打造无感的跨端续写体验! 敬请期待。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐