欢迎光临
我们一直在努力

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

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

文章目录

  • 轻规划鸿蒙开发实战6:Share Kit 碰一碰近场快传,外链 aeroplan 协议与 Base64 字节流无缝对接
        • 背景介绍
        • 1. 架构纵览:碰一碰近场通信与外链唤醒管线
        • 2. 发送端:Share Kit 碰一碰数据注册与投射
          • 碰一碰数据投射核心代码
        • 3. 配置文件:注册自定义 Scheme 协议
          • `module.json5` 核心配置
        • 4. 接收端:Scheme 外链拦截与 Base64 反序列化
          • EntryAbility 接收解析代码
        • 5. 极客避坑:Base64 编码的超长 URL 限制与熔断
          • 避坑指南:URL 长度容灾策略
        • 6. 总结与下期预告
背景介绍

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

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

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

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

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


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

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

接收端应用 (轻规划)

接收端系统 (Want 路由)

发送端系统 (Share Kit)

发送端应用 (轻规划)

接收端应用 (轻规划)

接收端系统 (Want 路由)

发送端系统 (Share Kit)

发送端应用 (轻规划)

#mermaid-svg-x9ZwuaXhO3WwYNLX{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-x9ZwuaXhO3WwYNLX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-x9ZwuaXhO3WwYNLX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-x9ZwuaXhO3WwYNLX .error-icon{fill:#552222;}#mermaid-svg-x9ZwuaXhO3WwYNLX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-x9ZwuaXhO3WwYNLX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-x9ZwuaXhO3WwYNLX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-x9ZwuaXhO3WwYNLX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-x9ZwuaXhO3WwYNLX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-x9ZwuaXhO3WwYNLX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-x9ZwuaXhO3WwYNLX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-x9ZwuaXhO3WwYNLX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-x9ZwuaXhO3WwYNLX .marker.cross{stroke:#333333;}#mermaid-svg-x9ZwuaXhO3WwYNLX svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-x9ZwuaXhO3WwYNLX p{margin:0;}#mermaid-svg-x9ZwuaXhO3WwYNLX .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-x9ZwuaXhO3WwYNLX text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-x9ZwuaXhO3WwYNLX .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-x9ZwuaXhO3WwYNLX .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-x9ZwuaXhO3WwYNLX .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-x9ZwuaXhO3WwYNLX .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-x9ZwuaXhO3WwYNLX #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-x9ZwuaXhO3WwYNLX .sequenceNumber{fill:white;}#mermaid-svg-x9ZwuaXhO3WwYNLX #sequencenumber{fill:#333;}#mermaid-svg-x9ZwuaXhO3WwYNLX #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-x9ZwuaXhO3WwYNLX .messageText{fill:#333;stroke:none;}#mermaid-svg-x9ZwuaXhO3WwYNLX .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-x9ZwuaXhO3WwYNLX .labelText,#mermaid-svg-x9ZwuaXhO3WwYNLX .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-x9ZwuaXhO3WwYNLX .loopText,#mermaid-svg-x9ZwuaXhO3WwYNLX .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-x9ZwuaXhO3WwYNLX .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-x9ZwuaXhO3WwYNLX .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-x9ZwuaXhO3WwYNLX .noteText,#mermaid-svg-x9ZwuaXhO3WwYNLX .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-x9ZwuaXhO3WwYNLX .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-x9ZwuaXhO3WwYNLX .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-x9ZwuaXhO3WwYNLX .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-x9ZwuaXhO3WwYNLX .actorPopupMenu{position:absolute;}#mermaid-svg-x9ZwuaXhO3WwYNLX .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-x9ZwuaXhO3WwYNLX .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-x9ZwuaXhO3WwYNLX .actor-man circle,#mermaid-svg-x9ZwuaXhO3WwYNLX line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-x9ZwuaXhO3WwYNLX :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

物理碰一碰 (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。在 skills 的 uris 列表中声明自定义协议,这样当系统检测到此类 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.ets 的 onCreate(冷启动场景)与 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 协议,打造无感的跨端续写体验! 敬请期待。

赞(0)
未经允许不得转载:171主机测评 » 轻规划鸿蒙开发实战6:Share Kit 碰一碰近场快传,外链 aeroplan 协议与 Base64 字节流无缝对
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址