轻规划鸿蒙开发实战6:Share Kit 碰一碰近场快传,外链 aeroplan 协议与 Base64 字节流无缝对
轻规划鸿蒙开发实战6:Share Kit 碰一碰近场快传,外链 aeroplan 协议与 Base64 字节流无缝对接
文章目录
背景介绍
在社交和日常团队协作场景下,用户经常有分享规划目标(如一份精心定制的“程序员三十岁前财务自由计划”、“西藏骑行旅行路线”等)的需求。
传统的分享极其繁琐且存在体验割裂:用户需要点击分享,将长文本复制到剪贴板或生成一张大图,接着手动切换打开第三方即时通讯软件,发送给朋友;而接收方则需下载并保存图片或复制文本,再次返回到目标 App 中进行导入解析。这种漫长且伴随着频繁跨应用跳转的操作路径,不仅容易因为系统内存不足导致后台进程被清理,还大幅降低了分享的转化率。
HarmonyOS NEXT 提供了强大的系统级近场通信能力——Share Kit(碰一碰分享)。
当用户开启碰一碰时,两台登录不同华为账号的鸿蒙手机轻轻触碰,就可以无缝唤起系统级的快传气泡。我们在“轻规划”(AeroPlan)中,通过定制专属的 aeroplan:// 链接协议,将九宫格核心数据打包为 Base64 字节流,两机一碰,即刻在对端拉起并完整复原这套精美规划。
今天,我们将从协议定制、配置文件声明、Base64 编解码落盘全链路进行实战解构。
1. 架构纵览:碰一碰近场通信与外链唤醒管线
当分享发起时,A 设备负责将内存中的复杂规划数据序列化为短文本链接;碰一碰激活后,系统在近场建立临时 P2P 通信信道,将链接投射给 B 设备;B 设备捕捉到 aeroplan:// 后自动反向冷启动并解析入库。其底层的核心流转管线如下:
通过这一物理碰一碰设计,我们将传统流程中至少 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)