查看: 765|回复: 3

鸿蒙RN碰一碰分享落地实践:Share Kit桥接适配与真机踩坑

[复制链接]
发表于 4 小时前 | 显示全部楼层 |阅读模式
碰一碰分享是HarmonyOS NEXT上很有代表性的近场交互能力:两台手机顶部轻碰,商品信息、文件或链接就能直接传过去。相比扫码和链接跳转,“碰”这个动作的成本极低,在电商、社交等强分享场景里尤其好用。这篇文章记录我们在HarmonyOS NEXT上把碰一碰分享接入React Native(RN)的开发经验,包括TurboModule桥接方案、核心接口使用、踩过的坑和分析思路。本文基于React Native 0.84 + RNOH 0.84.1编写,设备为HarmonyOS 6.0,不同版本间可能有差异,以实际测试为准。

一、整体方案:Share Kit + 自定义TurboModule

碰一碰分享底层依赖的是HarmonyOS Share Kit的近场发现能力。在原生鸿蒙应用里可以直接调用Share Kit接口,但在RN开发模式下,需要通过自定义TurboModule把原生能力暴露给JS侧。我们实现了一个名为TapShareModule的桥接模块,对上层提供三个核心方法:
  1. import TapShareModule from '../native/NativeTapShareModule';
  2. // 检查是否支持碰一碰
  3. const available = await TapShareModule.isTapShareAvailable();
  4. // 注册分享数据
  5. const result = await TapShareModule.shareSku(payload);
  6. // 停止分享
  7. await TapShareModule.stopShare();
复制代码

shareSku接收的payload承载了分享的业务数据,其中targetRoute是接收方点击Deep Link拉起应用后要跳转的路由,sourceScene用于标识分享来源场景,方便后续做数据统计。
  1. interface TapShareSkuPayload {
  2.     skuId: string;
  3.     spuId?: string;
  4.     title: string;
  5.     imageUrl?: string;
  6.     price?: string;
  7.     currency?: string;
  8.     stockStatus?: 'normal' | 'low' | 'out_of_stock';
  9.     targetRoute: string; // 接收方打开应用时跳转的路由
  10.     sourceScene: string; // 来源场景
  11. }
  12. interface TapShareResult {
  13.     code: number;
  14.     message: string;
  15.     shareId?: string;
  16.     status: 'waiting' | 'success' | 'cancelled' | 'failed';
  17.     targetRoute?: string;
  18. }
复制代码

二、能力检测:必须真机,不能跳过

Share Kit依赖NFC等近场通信硬件,模拟器不支持,必须真机测试。因此开发中首先要做好能力检测,否则在不支持的设备上直接调用shareSku会抛异常,影响用户体验。
  1. const checkSupport = async () => {
  2.     if (!TapShareModule) {
  3.         console.log('TapShareModule 未注册');
  4.         return false;
  5.     }
  6.     try {
  7.         const available = await TapShareModule.isTapShareAvailable();
  8.         console.log('碰一碰分享能力:', available ? '可用' : '不可用');
  9.         return available;
  10.     } catch (error) {
  11.         console.error('检测失败:', error);
  12.         return false;
  13.     }
  14. };
复制代码

如果设备或系统版本不支持,isTapShareAvailable()会返回false。分享入口需要据此做降级处理,比如改用链接分享或二维码。

三、两个典型错误写法

错误一:不检查能力可用性直接调用shareSku
  1. // 错误写法 直接调用 shareSku
  2. const handleShare = async () => {
  3.     const result = await TapShareModule.shareSku(payload);
  4.     // 如果在不支持 Share Kit 的设备上,会抛异常
  5. };
复制代码

正确做法是先判断模块是否存在、能力是否可用,再决定走碰一碰还是降级方案。
  1. // 正确写法 先检查能力可用性
  2. const handleShare = async () => {
  3.     if (!TapShareModule) {
  4.         Alert.alert('提示', 'TapShare 模块未注册');
  5.         return;
  6.     }
  7.     const available = await TapShareModule.isTapShareAvailable();
  8.     if (!available) {
  9.         Alert.alert('提示', '当前设备不支持碰一碰分享');
  10.         return;
  11.     }
  12.     const result = await TapShareModule.shareSku(payload);
  13. };
复制代码

错误二:分享成功后不清理状态

shareSku的返回值是异步的,上次分享的结果如果不清理,下次进入页面时可能会影响UI判断。
  1. // 错误写法 分享后不处理
  2. const handleShare = () => {
  3.     TapShareModule.shareSku(payload);
  4.     // 没有清理状态,下次分享时上次的结果还在
  5. };
  6. // 正确写法 分享前后管理状态
  7. const [result, setResult] = useState<TapShareResult | null>(null);
  8. const handleShare = async () => {
  9.     setResult(null); // 清空上次结果
  10.     const res = await TapShareModule.shareSku(payload);
  11.     setResult(res);
  12. };
复制代码

四、完整实战:SKU分享组件

以电商商品详情页为例,分享按钮的完整实现包含状态管理、payload构建和结果展示:
  1. const SkuShareButton = ({ sku }) => {
  2.     const [sharing, setSharing] = useState(false);
  3.     const [result, setResult] = useState(null);
  4.     const buildPayload = (sku) => ({
  5.         skuId: sku.id,
  6.         spuId: sku.spuId,
  7.         title: sku.title,
  8.         imageUrl: sku.image,
  9.         price: sku.price,
  10.         currency: 'USD',
  11.         stockStatus: sku.stock > 10 ? 'normal' : 'low',
  12.         targetRoute: `/sku/detail?skuId=${sku.id}`,
  13.         sourceScene: 'sku_detail',
  14.     });
  15.     const handleShare = async () => {
  16.         setSharing(true);
  17.         try {
  18.             if (!TapShareModule) {
  19.                 throw new Error('模块未注册');
  20.             }
  21.             const payload = buildPayload(sku);
  22.             const res = await TapShareModule.shareSku(payload);
  23.             setResult(res);
  24.         } catch (error) {
  25.             Alert.alert('分享失败', String(error));
  26.         } finally {
  27.             setSharing(false);
  28.         }
  29.     };
  30.     return (
  31.         <View>
  32.             <Pressable onPress={handleShare} disabled={sharing}>
  33.                 <Text>{sharing ? '准备中...' : '碰一碰分享'}</Text>
  34.             </Pressable>
  35.             {result?.status === 'waiting' && (
  36.                 <View>
  37.                     <Text>已注册,请碰一碰手机</Text>
  38.                     <Pressable onPress={() => TapShareModule.stopShare()}>
  39.                         <Text>取消</Text>
  40.                     </Pressable>
  41.                 </View>
  42.             )}
  43.         </View>
  44.     );
  45. };
复制代码

调用shareSku成功后,系统会注册一份待分享内容,两台支持碰一碰的设备亮屏解锁后顶部轻碰,数据即开始传输。用户点击取消时调用stopShare解除注册;如果已经分享完成,stopShare不影响已完成的传输。

接收端通过Deep Link拉起应用,在App.tsx用Linking监听URL并解析参数:
  1. const handleDeepLink = (url: string) => {
  2.     const parsed = parseTapShareUrl(url);
  3.     if (parsed) {
  4.         // 跳转到 SKU 详情页
  5.         navigation.navigate('SkuDetail', { skuId: parsed.skuId });
  6.     }
  7. };
  8. const parseTapShareUrl = (url: string): TapShareSkuPayload | null => {
  9.     try {
  10.         // 示例 URL: skuassistant://sku/detail?skuId=SKU-HM-001&title=...
  11.         const match = url.match(/skuassistant:\/\/sku\/detail\?(.+)/);
  12.         if (!match) return null;
  13.         const params = new URLSearchParams(match[1]);
  14.         return {
  15.             skuId: params.get('skuId') || '',
  16.             title: params.get('title') || '',
  17.             targetRoute: url,
  18.             sourceScene: 'sku_detail',
  19.         };
  20.     } catch {
  21.         return null;
  22.     }
  23. };
复制代码

五、原生桥接实现三层结构

TapShareModule的桥接实现分三层。第一层是JS侧接口定义:
  1. import type { TurboModule } from 'react-native';
  2. import { TurboModuleRegistry } from 'react-native';
  3. export interface TapShareSkuPayload {
  4.     skuId: string;
  5.     spuId?: string;
  6.     title: string;
  7.     imageUrl?: string;
  8.     price?: string;
  9.     currency?: string;
  10.     stockStatus?: string;
  11.     targetRoute: string;
  12.     sourceScene: string;
  13. }
  14. export interface TapShareResult {
  15.     code: number;
  16.     message: string;
  17.     shareId?: string;
  18.     status: string;
  19.     targetRoute?: string;
  20. }
  21. export interface Spec extends TurboModule {
  22.     shareSku(payload: TapShareSkuPayload): Promise<TapShareResult>;
  23.     stopShare(): Promise<boolean>;
  24.     isTapShareAvailable(): Promise<boolean>;
  25. }
  26. export default TurboModuleRegistry.getEnforcing<Spec>('TapShareModule');
复制代码

第二层是C++方法映射,把JS调用映射到ArkTS实现:
  1. TapShareModule::TapShareModule(const ArkTSTurboModule::Context ctx, const std::string name)
  2.     : ArkTSTurboModule(ctx, name) {
  3.     methodMap_ = {
  4.         ARK_ASYNC_METHOD_METADATA(shareSku, 1),
  5.         ARK_ASYNC_METHOD_METADATA(stopShare, 0),
  6.         ARK_ASYNC_METHOD_METADATA(isTapShareAvailable, 0),
  7.     };
  8. }
复制代码

第三层是ArkTS原生实现。能力检测用HarmonyOS的canIUse API判断当前系统是否支持Share Kit:
  1. import { harmonyShare } from '@kit.ShareKit';
  2. async isTapShareAvailable(): Promise<boolean> {
  3.     return canIUse('SystemCapability.Collaboration.HarmonyShare');
  4. }
复制代码

注册碰一碰分享则通过harmonyShare的事件监听实现:
  1. // 注册碰一碰事件监听
  2. harmonyShare.on('knockShare', (sharableTarget) => {
  3.     // 碰一碰触发后的回调
  4.     this.handleKnockShare(sharableTarget);
  5. });
  6. // 取消注册
  7. harmonyShare.off('knockShare');
复制代码

原生侧根据传递的payload参数构造JSON数据,通过Share Kit的sharableTarget分发到接收端,接收端再通过Deep Link拉起应用并传递参数。

六、错误码速查与状态处理

实际开发中,建议为TapShare维护一份错误码表,方便定位问题和展示给用户:
  1. const TapShareErrorCodes = {
  2.     1001: { level: 'info', message: '已注册,等待碰一碰' },
  3.     1002: { level: 'info', message: '已更新待分享内容' },
  4.     2001: { level: 'info', message: '已解除注册' },
  5.     3001: { level: 'warn', message: '系统不支持 Share Kit' },
  6.     3002: { level: 'error', message: '模块未注册' },
  7.     5001: { level: 'error', message: '调用失败' },
  8.     5002: { level: 'error', message: '解除注册失败' },
  9. };
  10. const handleTapShareResult = (result: TapShareResult) => {
  11.     const codeInfo = TapShareErrorCodes[result.code];
  12.     if (codeInfo.level === 'error') {
  13.         console.error(`[TapShare] ${codeInfo.message}: ${result.message}`);
  14.     } else if (codeInfo.level === 'warn') {
  15.         console.warn(`[TapShare] ${codeInfo.message}`);
  16.     } else {
  17.         console.log(`[TapShare] ${codeInfo.message}`);
  18.     }
  19. };
复制代码

接收端拿到Deep Link后,页面需要根据解析出的skuId等标识从API拉取完整的商品数据渲染。不要在payload里塞全量数据。

七、鸿蒙上的坑:五个关键注意点

坑1:必须真机测试。Share Kit依赖NFC和近场通信硬件,模拟器上不可用。开发调试时一定用真机,并且要准备至少两台HarmonyOS NEXT设备。

坑2:不能实时获知传输结果。发送端无法直接获取接收端是否成功接收。真正的结果由HarmonyOS系统通过通知告知用户。所以分享后的UI提示要明确说明“等待碰一碰触发”,而不是“传输成功”。

坑3:设备要求严格。两台设备都必须是HarmonyOS NEXT,都亮屏解锁,顶部区域相互接触。任何条件不满足都可能触发失败。

坑4:分享数据大小限制。当前版本中payload的总数据量不宜过大,建议只传必要的业务标识(skuId、targetRoute),详细数据由接收端通过API自行拉取。这样payload小了,传输更快,数据也更安全。

坑5:业务标识vs完整数据。不要把商品描述、价格、图片全塞进payload,接收端拿到Deep Link后按需请求接口即可。

八、适配步骤与降级建议

发送端适配四步走:先检测能力,不可用则降级到URL分享;构建最小payload;注册分享;UI提示用户碰一碰。
  1. // 1. 检测能力
  2. const available = await TapShareModule.isTapShareAvailable();
  3. if (!available) {
  4.     // 降级到 URL 分享
  5.     return shareByUrl(payload);
  6. }
  7. // 2. 构建 payload
  8. const payload = buildMinimalPayload(sku);
  9. // 3. 注册分享
  10. const result = await TapShareModule.shareSku(payload);
  11. // 4. UI 提示
  12. showKnockGuide(result);
复制代码

接收端则配置Deep Link scheme,在App.tsx中监听Linking,解析URL参数,跳转对应页面并拉取完整数据。

还有一个容易被忽略的点:组件卸载时要清理状态。用useEffect的清理函数调用stopShare,避免页面已销毁但分享注册还挂着的情况。

九、结语

碰一碰分享是HarmonyOS的独特能力,其最大价值在于“碰”这个交互成本极低——不需要扫码、不需要点链接、不需要输验证码。在电商场景里,这种交互对于商品分享、邀请拼单等业务有天然的适配空间。但在不支持碰一碰的设备上一定要有降级方案,不能把碰一碰作为唯一分享方式。建议分享场景接上埋点追踪,记录从哪个页面发起的分享、分享状态如何,方便后续优化。

文中代码示例基于HarmonyOS 6.0和RNOH 0.84.1实测通过,可直接复用。版本升级后需重新回归验证。
回复

使用道具 举报

发表于 半小时前 | 显示全部楼层

Re: 鸿蒙RN碰一碰分享落地实践:Share Kit桥接适配与真机踩坑

楼主的分享很实在,尤其是“必须真机、模拟器不支持”和“分享成功后清理状态”这两个点,都是实际开发中容易踩的坑。我们也在做类似功能,不过目前还在评估阶段,有几个问题想请教一下: 1. 碰一碰分享在真机上,两台设备的系统版本有限制吗?比如都必须鸿蒙6.0以上? 2. `shareSku` 里那个 `targetRoute`,接收方点击拉起应用后,是直接能跳转到对应页面,还是需要先在应用里处理一次路由分发? 3. 分享过程中如果用户手机离开太近或者中途取消了,`status` 回调里能明确区分“取消”和“失败”吗?我们想针对取消做不打扰的提示。 另外,楼主提到的“降级方案”是直接用系统分享链接还是走的其他方式?如果方便的话希望展开说说。
回复 支持 反对

使用道具 举报

发表于 半小时前 | 显示全部楼层

Re: 鸿蒙RN碰一碰分享落地实践:Share Kit桥接适配与真机踩坑

楼主的分享很详实,尤其是“必须先做能力检测”和“分享后清理状态”这两个点,实际开发中太容易踩了。我们当初接的时候就是因为没处理好状态残留,导致页面复用时一直弹上次的分享结果,排查了好久。 想追问一下:接收方被拉起后,Deep Link的targetRoute传参在RN侧是怎么处理的?是走的系统路由拦截还是自己维护了一套页面映射?另外,真机上两台设备“碰”的时候,对机型或者握持姿势有没有什么特殊要求,比如NFC天线位置会不会影响成功率?
回复 支持 反对

使用道具 举报

发表于 半小时前 | 显示全部楼层

Re: 鸿蒙RN碰一碰分享落地实践:Share Kit桥接适配与真机踩坑

感谢分享,这篇实战记录写得非常实在。特别是“必须真机、模拟器不支持”和“分享后清理状态”这两点,确实是很多人容易踩的坑。想追问一下楼主:碰一碰在真机上对距离和角度的敏感度如何?我们之前做NFC相关功能时,发现不同机型的线圈位置差异挺大的,有时候用户“碰”一下没反应,体验就容易打折扣。你们在电商场景里有没有做引导动画或者失败后的降级提示?另外,RN桥接这块性能损耗明显吗?期待楼主再多分享一些细节。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-1 19:44 , Processed in 0.024035 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部