鸿蒙 7 新特性实战③:ShareKit 全域碰一碰分享开发实录
碰一碰是鸿蒙近场分布式交互的标志性能力。到鸿蒙 7 时代,ShareKit 将碰一碰从「手机互传」升级为手机 / 平板 / PC / 2in1 全形态全域支持,同时新增触碰位置感知、沙箱直达接收、星闪链路原生加速等特性,底层依托分布式软总线 2.0 完成建连与传输,真正实现「轻贴即传、直达应用、无感流畅」的近场分享体验。
本文从新特性解析、前置配置、底层流程、完整双端代码、高级特性实战、避坑指南六个维度,带你从零完成一套可落地的全域碰一碰分享功能。
一、鸿蒙 7 核心升级:从「能传」到「精准全域传」
相比旧版本,鸿蒙 7 的碰一碰分享在覆盖范围、交互精度、传输性能三个维度完成了量级升级。
1. 全域设备覆盖
彻底打破设备形态限制,支持手机、平板、PC、2in1 设备之间的跨形态碰一碰:
- 手机碰手机:传统近场分享
- 手机碰平板:大屏接力、文件直传
- 手机碰 PC:文件直达应用沙箱、内容插入光标位置
- 所有形态复用同一套 API,一套代码全端生效
2. 触碰位置感知(SDK 26 新增)
接收端可获取触碰点在屏幕上的精确坐标,实现「碰哪里就插到哪里」的精准交互:
- 文档编辑器:碰一下直接把图片/文字插入光标位置
- 列表页面:碰对应区域直接把数据追加到对应条目
- 画布场景:碰触位置直接生成素材元素
- 完全由系统底层提供坐标,应用无需自行计算偏移
3. 沙箱直达接收
文件类分享不再经过系统下载目录中转,可直接写入目标应用的私有沙箱:
- 传输完成直接通知应用,无需用户手动选择保存位置
- 遵循沙箱安全规则,仅授权的文件可被访问
- 减少中间环节,传输速度更快、隐私性更强
4. 星闪 + 软总线 2.0 底座
近场场景下自动切换星闪链路传输:
- 空口延迟低至微秒级,大文件传输速度提升 2 倍以上
- 建连速度从秒级压缩到百毫秒内,用户无感知等待
- 多链路融合,Wi-Fi 信号差时自动切星闪保障传输稳定
二、前置准备与工程配置
2.1 开发与运行环境
- SDK 版本:API 26(HarmonyOS 7.0)及以上
- 工程模型:Stage 模型(FA 模型不支持完整碰一碰能力)
- 开发工具:DevEco Studio 5.0.1 及以上
- 硬件要求:两台带 NFC 模块的鸿蒙实体设备,模拟器无法调试
- 账号要求:双端登录同一华为账号,开启 NFC、蓝牙、华为分享
2.2 权限声明
在 module.json5 的 requestPermissions 中添加必要权限:
"requestPermissions": [
{
"name": "ohos.permission.NFC",
"reason": "$string:permission_nfc_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.READ_IMAGEVIDEO",
"reason": "$string:permission_storage_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
说明:纯文本/链接分享无需存储权限,分享文件/图片时按需申请对应媒体权限。
2.3 接收能力注册(接收端必填)
如果应用需要作为接收方,需要在 Ability 的 skills 中声明支持的分享意图与数据类型,系统才能正确匹配并拉起你的应用。
"abilities": [
{
"name": "EntryAbility",
"exported": true,
"skills": [
{
"actions": ["ohos.want.action.sendData"],
"uris": [
{
"scheme": "file",
"type": "image/*"
},
{
"scheme": "http",
"type": "text/plain"
}
]
}
]
}
]
actions固定为ohos.want.action.sendData,表示接收分享数据uris按需声明所有支持的数据类型,不匹配的类型不会拉起你的应用
2.4 重要说明:签名要求
碰一碰拉起目标应用依赖应用签名校验,DevEco Studio 自动调试签名无法正常拉起,必须使用与应用包名匹配的正式签名文件,否则会出现「碰了没反应」「无可用打开方式」的问题。
三、底层工作全流程
一次完整的碰一碰分享,从触碰触发到数据落地共经历 5 个阶段,全程由系统统一调度,应用无需关心底层通信细节。
- 触碰触发:两台设备 NFC 近场检测,交换设备 ID、账号指纹、能力信息
- 极速建连:零感双向认证,自动协商最优传输链路(Wi-Fi / 星闪),建立软总线通道
- 应用匹配:系统根据数据类型匹配目标应用,校验签名与权限
- 数据传输:系统统一完成数据传输,支持流式进度上报、断点续传
- 直达应用:拉起目标应用对应页面,将数据、触碰位置等信息通过 Want 传入
四、完整实战:双端碰一碰分享
下面实现一套完整的「图片 + 文本 + 链接」三类型碰一碰分享,包含发送端和接收端的完整代码。
4.1 发送端实现:构造数据 + 注册触发
发送端核心逻辑:选择分享内容 → 构造标准化分享数据 → 注册碰一碰监听 → 触碰后自动发送。
import { harmonyShare } from '@kit.ShareKit';
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
import { promptAction } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { PhotoViewPicker, photoAccessHelper } from '@kit.ArkUI';
const TAG = 'KnockShareSender';
const DOMAIN = 0x0000;
@Entry
@Component
struct KnockSharePage {
@State shareType: 'text' | 'url' | 'image' = 'text';
@State textContent: string = '这是碰一碰分享的测试文本';
@State urlContent: string = 'https://developer.huawei.com';
@State imageUri: string = '';
@State isSharingReady: boolean = false;
private photoPicker = new PhotoViewPicker();
aboutToAppear() {
this.initKnockShare();
}
aboutToDisappear() {
// 页面退出注销监听,避免资源泄漏
harmonyShare.off('knockShare');
}
/**
* 初始化碰一碰发送能力
*/
private initKnockShare() {
// 先检测设备是否支持碰一碰
if (!harmonyShare.isKnockShareSupported()) {
promptAction.showToast({ message: '当前设备不支持碰一碰' });
return;
}
// 注册碰一碰触发回调:用户轻碰设备时执行
harmonyShare.on('knockShare', () => {
hilog.info(DOMAIN, TAG, '碰一碰触发,开始发送');
this.doShare();
});
this.isSharingReady = true;
promptAction.showToast({ message: '碰一碰已就绪,轻触另一台设备即可分享' });
}
/**
* 执行分享:根据类型构造数据
*/
private doShare() {
let shareData: harmonyShare.ShareData;
switch (this.shareType) {
case 'text':
shareData = {
utd: utd.UniformDataType.TEXT,
data: this.textContent,
title: '文本分享',
description: '来自碰一碰的文本内容'
};
break;
case 'url':
shareData = {
utd: utd.UniformDataType.URL,
data: this.urlContent,
title: '链接分享',
thumbnailUri: '' // 可传入预览图,显示沉浸式大卡
};
break;
case 'image':
if (!this.imageUri) {
promptAction.showToast({ message: '请先选择图片' });
return;
}
shareData = {
utd: utd.UniformDataType.IMAGE,
uri: this.imageUri,
title: '图片分享',
thumbnailUri: this.imageUri // 图片预览
};
break;
}
// 发送分享数据
harmonyShare.sendShareData(shareData).then(() => {
hilog.info(DOMAIN, TAG, '分享发送成功');
promptAction.showToast({ message: '分享已发送' });
}).catch((err) => {
hilog.error(DOMAIN, TAG, `分享失败: ${err.message}`);
promptAction.showToast({ message: '分享失败,请重试' });
});
}
/**
* 选择分享图片
*/
private async pickImage() {
try {
const res = await this.photoPicker.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 1
});
if (res.photoUris && res.photoUris.length > 0) {
this.imageUri = res.photoUris[0];
}
} catch (e) {
hilog.error(DOMAIN, TAG, `选图失败: ${JSON.stringify(e)}`);
}
}
build() {
Column({ space: 20 }) {
Text('碰一碰分享发送端')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.width('90%')
.margin({ top: 20 });
// 类型选择
Flex({ wrap: FlexWrap.Wrap, gap: 10 }) {
['text', 'url', 'image'].forEach(type => {
Button(type === 'text' ? '文本' : type === 'url' ? '链接' : '图片')
.height(36)
.padding({ left: 16, right: 16 })
.borderRadius(18)
.backgroundColor(this.shareType === type ? '#0A59F7' : '#F5F5F5')
.fontColor(this.shareType === type ? '#FFFFFF' : '#333333')
.onClick(() => this.shareType = type as any);
});
}
.width('90%');
// 内容输入区
if (this.shareType === 'text') {
TextArea({ text: this.textContent })
.width('90%')
.height(120)
.borderRadius(8)
.padding(12)
.backgroundColor('#F5F5F5')
.onChange(val => this.textContent = val);
} else if (this.shareType === 'url') {
TextInput({ text: this.urlContent })
.width('90%')
.height(44)
.borderRadius(8)
.backgroundColor('#F5F5F5')
.onChange(val => this.urlContent = val);
} else {
Button('选择图片')
.width('90%')
.height(44)
.borderRadius(8)
.onClick(() => this.pickImage());
if (this.imageUri) {
Image(this.imageUri)
.width('90%')
.height(200)
.objectFit(ImageFit.Contain)
.borderRadius(8);
}
}
Blank();
Text(this.isSharingReady ? '✅ 碰一碰已就绪,将设备背部轻碰另一台设备' : '⏳ 初始化中...')
.fontSize(14)
.fontColor(this.isSharingReady ? '#00B578' : '#888888')
.margin({ bottom: 40 });
}
.width('100%')
.height('100%')
.backgroundColor('#F8F9FA');
}
}
4.2 接收端实现:接收数据 + 获取触碰位置
接收端分两步:首先在 Ability 中接收分享意图,然后在页面中处理数据并获取触碰位置。
第一步:Ability 中接收分享意图

import { UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = 'KnockShareReceiver';
const DOMAIN = 0x0000;
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/ReceivePage', (err) => {
if (err.code) {
hilog.error(DOMAIN, TAG, `页面加载失败: ${err.message}`);
}
});
}
// 碰一碰拉起应用时触发,获取分享数据
onNewWant(want: Want): void {
hilog.info(DOMAIN, TAG, `收到分享: ${JSON.stringify(want)}`);
// 将分享数据存入全局状态,通知页面更新
AppStorage.setOrCreate('shareWant', want);
}
}
第二步:页面处理分享数据 + 获取触碰位置

import { harmonyShare } from '@kit.ShareKit';
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
import { promptAction } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = 'ReceivePage';
const DOMAIN = 0x0000;
@Entry
@Component
struct ReceivePage {
@State receivedContent: string = '';
@State receivedType: string = '';
@State touchX: number = 0;
@State touchY: number = 0;
aboutToAppear() {
// 注册碰一碰接收回调
this.registerReceiver();
// 检查是否是分享拉起的
const want = AppStorage.get<Want>('shareWant');
if (want) {
this.handleShareData(want);
}
}
aboutToDisappear() {
harmonyShare.off('shareDataReceive');
}
private registerReceiver() {
if (!harmonyShare.isKnockShareSupported()) {
return;
}
// 注册数据接收回调
harmonyShare.on('shareDataReceive', (event: harmonyShare.ShareReceiveEvent) => {
hilog.info(DOMAIN, TAG, `收到分享数据: ${JSON.stringify(event)}`);
// ========== 鸿蒙7新特性:获取触碰点坐标 ==========
if (event.touchPosition) {
this.touchX = event.touchPosition.x;
this.touchY = event.touchPosition.y;
hilog.info(DOMAIN, TAG, `触碰位置: ${this.touchX}, ${this.touchY}`);
}
this.handleShareContent(event.data);
});
}
private handleShareContent(data: harmonyShare.ShareData) {
this.receivedType = data.utd;
switch (data.utd) {
case utd.UniformDataType.TEXT:
this.receivedContent = data.data as string;
break;
case utd.UniformDataType.URL:
this.receivedContent = data.data as string;
break;
case utd.UniformDataType.IMAGE:
this.receivedContent = data.uri || '';
break;
}
promptAction.showToast({ message: '分享接收成功' });
}
private handleShareData(want: Want) {
// 从Want中解析分享数据
// 实际项目中根据want参数解析对应内容
promptAction.showToast({ message: '应用被分享拉起' });
}
build() {
Column({ space: 20 }) {
Text('碰一碰分享接收端')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.width('90%')
.margin({ top: 20 });
if (this.touchX > 0) {
Row() {
Text('触碰位置:')
.fontSize(14)
.fontColor('#888888');
Text(`X: ${this.touchX.toFixed(0)}, Y: ${this.touchY.toFixed(0)}`)
.fontSize(14)
.fontFamily('monospace')
.fontColor('#0A59F7');
}
.width('90%')
.padding(12)
.backgroundColor('#F0F7FF')
.borderRadius(8);
}
// 接收内容展示
if (this.receivedContent) {
Column({ space: 10 }) {
Text(`接收类型:${this.receivedType}`)
.fontSize(14)
.fontColor('#888888')
.width('100%');
if (this.receivedType === utd.UniformDataType.IMAGE) {
Image(this.receivedContent)
.width('100%')
.height(200)
.objectFit(ImageFit.Contain)
.borderRadius(8);
} else {
Text(this.receivedContent)
.width('100%')
.padding(12)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.fontSize(14);
}
}
.width('90%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 4, color: '#1A000000' });
}
Blank();
Text('💡 将另一台设备轻触本机背部即可接收分享')
.fontSize(12)
.fontColor('#AAAAAA')
.margin({ bottom: 30 });
}
.width('100%')
.height('100%')
.backgroundColor('#F8F9FA');
}
}
五、高级特性实战:触碰位置精准插入
鸿蒙 7 最具交互价值的升级就是触碰位置感知,我们以「编辑器插入图片」为例,实现「碰哪里就插到哪里」的精准交互。
核心逻辑:
- 记录编辑器中每行的位置范围
- 收到分享时,根据触碰 Y 坐标计算对应的行索引
- 将分享内容插入到对应行的位置
// 根据触碰Y坐标计算插入行号
private calcInsertLine(touchY: number, lineHeight: number, topOffset: number): number {
const relativeY = touchY - topOffset;
const lineIndex = Math.floor(relativeY / lineHeight);
return Math.max(0, lineIndex);
}
// 接收分享后执行插入
private insertAtPosition(content: string, touchY: number) {
const lineIndex = this.calcInsertLine(touchY, 48, 100);
this.contentList.splice(lineIndex, 0, content);
// 插入动效反馈
this.highlightLine(lineIndex);
}
这种交互在文档协作、素材管理、多端编辑场景下体验提升非常明显,完全摆脱了「分享 → 复制 → 找位置粘贴」的繁琐流程。
六、调试验证步骤
- 配置签名:使用正式签名文件,分别编译发送端和接收端安装包
- 设备准备:两台设备登录同一华为账号,开启 NFC、蓝牙、华为分享,保持亮屏解锁
- 发送端操作:打开发送页面,选择分享类型,输入内容
- 触发分享:将两台设备背部轻贴(NFC 区域通常在摄像头附近)
- 验证结果:接收端自动弹出应用,显示分享内容与触碰位置坐标
- 多类型测试:分别测试文本、链接、图片三种类型,验证接收是否正常
七、高频踩坑避坑指南
1. 碰了完全没反应
排查优先级:
- 双端是否都开启 NFC、华为分享、蓝牙
- 是否登录同一华为账号,是否亮屏解锁
- 应用是否使用正式签名,包名与签名是否匹配
- 接收端 skills 配置是否正确,数据类型是否匹配
2. 提示「暂无可用打开方式」
- 接收端没有配置正确的
skills,系统找不到能处理该类型的应用 - 数据类型与声明的 MIME 类型不匹配
- 应用未安装,或签名不匹配被系统拦截
3. 触碰位置获取不到
- 确认 SDK 版本 ≥ API 26,低版本无该字段
- 只有碰一碰触发的分享才带位置信息,系统分享面板触发的没有
- 坐标是屏幕绝对坐标,需要减去页面顶部偏移和状态栏高度
4. 文件分享失败
- 单个文件大小不要超过系统限制,大文件建议走 ShareKit 文件专属通道
- 文件 URI 必须是系统认可的合法 URI,不能直接传沙箱路径
- 接收端需要对应文件类型的权限声明
5. 模拟器无法调试
碰一碰依赖 NFC 硬件与系统华为分享服务,模拟器不支持,必须使用真机调试。
八、最佳实践
- 卡片体验优化:根据内容类型选择对应卡片模板,配图 + 标题的沉浸式大卡体验远好于纯文本
- 优雅降级:不支持碰一碰的设备,降级为系统分享面板,保证功能可用
- 单向发送配置:如果双方都在发送页面,可设置
sendOnly: true避免双向冲突 - 进度反馈:大文件分享时展示进度条与取消按钮,不要让用户无意义等待
- 隐私合规:分享敏感数据前增加二次确认,避免误触泄露隐私
总结
ShareKit 全域碰一碰,是鸿蒙近场分布式体验的典型缩影:它把复杂的设备发现、安全认证、传输调度全部封装在系统底层,开发者只需要构造标准化的分享数据,就能获得全设备形态一致的近场分享体验。
鸿蒙 7 新增的触碰位置感知与沙箱直达能力,进一步把分享的精度和深度往前推了一步,从「能传过去」变成「精准传到指定位置、直接进入业务流程」,让近场协同的体验更加无感自然。
更多推荐



所有评论(0)