HarmonyOS 碰一碰分享功能开发指南:从 0 到 1 完整实践
前言
碰一碰(Knock Share)是华为 HarmonyOS 提供的一项创新性近场分享能力,用户只需将两台华为设备轻轻触碰,即可快速分享图片、文件等内容。相比传统的蓝牙,碰一碰具有操作便捷、传输快速的特点。
本文将以实际项目为例,详细介绍如何在 HarmonyOS 应用中从零开始集成碰一碰分享功能,包括权限配置、API 使用、完整代码示例及常见问题处理。
一、功能概述
1.1 什么是碰一碰分享
碰一碰分享是华为基于近场通信技术实现的便捷分享方式。当两台支持该功能的华为设备触碰时,系统会自动触发分享回调,应用可以响应这个事件并执行分享操作。
1.2 使用场景
-
照片分享:快速分享相册中的图片
-
作品分享:分享用户创作的图画、文档等
-
名片交换:商务场景下的快速信息交换
-
文件传输:办公场景下的快速文件共享
1.3 技术优势
| 特性 | 碰一碰 | 蓝牙传输 | 二维码 |
|---|---|---|---|
| 操作便捷性 | 高 | 中 | 中 |
| 配对过程 | 无需配对 | 需要配对 | 需扫码 |
| 传输速度 | 快 | 中 | 慢(需网络) |
| 用户体验 | 自然直观 | 繁琐 | 一般 |
二、开发环境准备
2.1 开发环境要求
- 集成开发环境:DevEco Studio NEXT Beta1及以上版本。
-
支持的手机系统:HarmonyOS NEXT Release及以上版本,可使用canIUse判断系统能力是否支持。
2.2 核心依赖
碰一碰功能依赖于 @kit.ShareKit,该 Kit 包含两个核心模块:
-
systemShare:系统分享面板,用于构建分享数据 -
harmonyShare:华为分享能力,提供碰一碰监听接口
三、权限配置
3.1 配置 module.json5
在项目的 entry/src/main/module.json5 文件中,需要声明必要的权限。
如果您的项目存在从云端下载图片的场景,需要配置以下权限:
{
"module": {
"name": "entry",
"type": "entry",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:app_icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:app_icon",
"startWindowBackground": "$color:start_window_background",
"exported": true
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
四、核心代码实现
4.1 引入依赖模块
首先,在组件文件顶部引入必要的模块:
// 分享相关
import { systemShare, harmonyShare } from '@kit.ShareKit'
// 统一数据类型描述
import { uniformTypeDescriptor as utd } from '@kit.ArkData'
// 文件操作
import { fileUri } from '@kit.CoreFileKit'
// 错误处理
import { BusinessError } from '@kit.BasicServicesKit'
// 提示信息
import promptAction from '@ohos.promptAction'
4.2 定义碰一碰回调函数
在组件中定义碰一碰回调函数。注意:使用箭头函数可以避免 this 绑定问题。
@Component
export struct MyShareComponent {
// 使用箭头函数定义回调,避免 this 绑定问题
private knockShareCallbackFn = (sharableTarget: harmonyShare.SharableTarget) => {
this.knockShareCallback(sharableTarget)
}
// ... 其他代码
}
4.3 注册碰一碰监听
在组件的 aboutToAppear 生命周期中注册碰一碰监听:
@Component
export struct MyShareComponent {
// 回调函数定义
private knockShareCallbackFn = (sharableTarget: harmonyShare.SharableTarget) => {
this.knockShareCallback(sharableTarget)
}
/**
* 组件即将显示时调用
*/
aboutToAppear() {
// 初始化其他数据...
this.registerKnockShare()
}
/**
* 注册碰一碰分享监听
*/
registerKnockShare() {
try {
console.info('[MyComponent] 注册碰一碰分享监听')
// 使用 harmonyShare.on 注册监听
// 第一个参数固定为 'knockShare'
// 第二个参数为回调函数
harmonyShare.on('knockShare', this.knockShareCallbackFn)
console.info('[MyComponent] 碰一碰分享监听注册成功')
} catch (error) {
const err = error as BusinessError
console.error('[MyComponent] 碰一碰分享监听注册失败:', err.code, err.message)
}
}
}
4.4 注销碰一碰监听
在组件的 aboutToDisappear 生命周期中注销监听,避免内存泄漏:
@Component
export struct MyShareComponent {
// ... 前面的代码
/**
* 组件即将销毁时调用
*/
aboutToDisappear() {
this.unregisterKnockShare()
}
/**
* 注销碰一碰分享监听
*/
unregisterKnockShare() {
try {
console.info('[MyComponent] 注销碰一碰分享监听')
// 使用 harmonyShare.off 取消监听
// 必须传入与注册时相同的回调函数引用
harmonyShare.off('knockShare', this.knockShareCallbackFn)
console.info('[MyComponent] 碰一碰分享监听注销成功')
} catch (error) {
const err = error as BusinessError
console.error('[MyComponent] 碰一碰分享监听注销失败:', err.code, err.message)
}
}
}
4.5 实现碰一碰回调处理
当用户触发碰一碰时,系统会调用注册的回调函数。在回调中构建分享数据并执行分享:
@Component
export struct MyShareComponent {
// 假设有图片数据
@State imageList: string[] = [] // 存储图片文件路径
/**
* 碰一碰分享回调
* @param sharableTarget 分享目标对象,由系统提供
*/
knockShareCallback(sharableTarget: harmonyShare.SharableTarget) {
try {
console.info('[MyComponent] 触发碰一碰分享')
// 检查是否有可分享的内容
if (this.imageList.length === 0) {
console.info('[MyComponent] 没有可分享的内容')
promptAction.showToast({
message: '暂无可分享的内容',
duration: 2000
})
return
}
// 获取要分享的图片路径(这里以随机选择为例)
const randomIndex = Math.floor(Math.random() * this.imageList.length)
const imagePath = this.imageList[randomIndex]
// 如果路径包含 file:// 前缀,需要移除
let filePath = imagePath.replace('file://', '')
console.info('[MyComponent] 准备分享图片:', filePath)
// 构建分享数据
const shareData: systemShare.SharedData = new systemShare.SharedData({
// 数据类型:图片
utd: utd.UniformDataType.IMAGE,
// 文件 URI
uri: fileUri.getUriFromPath(filePath),
// 缩略图 URI(可选,用于预览)
thumbnailUri: fileUri.getUriFromPath(filePath)
})
console.info('[MyComponent] 碰一碰分享数据准备完成,开始分享')
// 调用 sharableTarget.share() 执行分享
sharableTarget.share(shareData)
console.info('[MyComponent] 碰一碰分享完成')
} catch (error) {
const err = error as BusinessError
console.error('[MyComponent] 碰一碰分享失败:', err.code, err.message)
promptAction.showToast({
message: '分享失败,请稍后重试',
duration: 2000
})
}
}
}
五、完整示例代码
以下是一个完整的、可直接使用的碰一碰分享组件示例:
/**
* 碰一碰分享示例组件
*
* 功能:
* - 展示图片列表
* - 支持碰一碰分享随机图片
* - 支持华为分享面板分享
*/
import { systemShare, harmonyShare } from '@kit.ShareKit'
import { uniformTypeDescriptor as utd } from '@kit.ArkData'
import { fileUri, fileIo } from '@kit.CoreFileKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { common } from '@kit.AbilityKit'
import promptAction from '@ohos.promptAction'
// 图片数据模型
interface ImageItem {
id: string
path: string // 完整文件路径
title: string // 图片标题
}
@Component
export struct KnockShareDemo {
// 图片列表数据
@State private imageList: ImageItem[] = []
// 碰一碰回调函数(使用箭头函数绑定 this)
private knockShareCallbackFn = (sharableTarget: harmonyShare.SharableTarget) => {
this.onKnockShare(sharableTarget)
}
/**
* 组件生命周期:即将显示
*/
aboutToAppear() {
console.info('[KnockShareDemo] aboutToAppear - 初始化组件')
// 加载图片数据
this.loadImageData()
// 注册碰一碰分享
this.registerKnockShare()
}
/**
* 组件生命周期:即将销毁
*/
aboutToDisappear() {
console.info('[KnockShareDemo] aboutToDisappear - 清理资源')
// 注销碰一碰分享
this.unregisterKnockShare()
}
/**
* 加载图片数据(示例方法,实际项目中根据需求实现)
*/
loadImageData() {
// 这里是示例数据,实际项目中从数据库或文件系统加载
this.imageList = [
{ id: '1', path: '/data/storage/el2/base/haps/entry/files/image1.jpg', title: '风景照片' },
{ id: '2', path: '/data/storage/el2/base/haps/entry/files/image2.jpg', title: '人物照片' }
]
console.info('[KnockShareDemo] 加载了', this.imageList.length, '张图片')
}
/**
* 注册碰一碰分享监听
*/
registerKnockShare() {
try {
console.info('[KnockShareDemo] 注册碰一碰分享监听')
// 核心 API:harmonyShare.on('knockShare', callback)
harmonyShare.on('knockShare', this.knockShareCallbackFn)
console.info('[KnockShareDemo] 碰一碰分享监听注册成功')
} catch (error) {
const err = error as BusinessError
console.error('[KnockShareDemo] 注册失败 - code:', err.code, 'message:', err.message)
}
}
/**
* 注销碰一碰分享监听
*/
unregisterKnockShare() {
try {
console.info('[KnockShareDemo] 注销碰一碰分享监听')
// 核心 API:harmonyShare.off('knockShare', callback)
// 注意:必须传入与注册时相同的回调函数引用
harmonyShare.off('knockShare', this.knockShareCallbackFn)
console.info('[KnockShareDemo] 碰一碰分享监听注销成功')
} catch (error) {
const err = error as BusinessError
console.error('[KnockShareDemo] 注销失败 - code:', err.code, 'message:', err.message)
}
}
/**
* 碰一碰分享触发时的回调处理
*/
onKnockShare(sharableTarget: harmonyShare.SharableTarget) {
try {
console.info('[KnockShareDemo] 碰一碰分享被触发')
// 步骤1:检查是否有可分享的内容
if (this.imageList.length === 0) {
promptAction.showToast({
message: '暂无可分享的图片',
duration: 2000
})
return
}
// 步骤2:选择要分享的内容(这里随机选择一张)
const randomIndex = Math.floor(Math.random() * this.imageList.length)
const selectedImage = this.imageList[randomIndex]
console.info('[KnockShareDemo] 选中图片:', selectedImage.title)
// 步骤3:构建分享数据
const shareData: systemShare.SharedData = new systemShare.SharedData({
// 指定数据类型为图片
utd: utd.UniformDataType.IMAGE,
// 图片文件的 URI
uri: fileUri.getUriFromPath(selectedImage.path),
// 缩略图 URI(用于在对方设备上显示预览)
thumbnailUri: fileUri.getUriFromPath(selectedImage.path)
})
// 步骤4:执行分享
sharableTarget.share(shareData)
console.info('[KnockShareDemo] 分享数据已发送')
// 可选:显示分享成功提示
promptAction.showToast({
message: `正在分享: ${selectedImage.title}`,
duration: 2000
})
} catch (error) {
const err = error as BusinessError
console.error('[KnockShareDemo] 分享失败 - code:', err.code, 'message:', err.message)
promptAction.showToast({
message: '分享失败,请重试',
duration: 2000
})
}
}
/**
* 华为分享面板分享(供按钮点击调用)
*/
async shareViaSystemPanel(image: ImageItem) {
try {
console.info('[KnockShareDemo] 打开系统分享面板')
// 获取 UIContext
const uiContext = this.getUIContext()
const context: common.UIAbilityContext = uiContext.getHostContext() as common.UIAbilityContext
// 获取精确的文件类型
const utdTypeId = utd.getUniformDataTypeByFilenameExtension('.jpg', utd.UniformDataType.IMAGE)
// 构建分享数据
const shareData: systemShare.SharedData = new systemShare.SharedData({
utd: utdTypeId,
uri: fileUri.getUriFromPath(image.path),
title: image.title,
description: '分享自我的应用'
})
// 创建分享控制器
const controller: systemShare.ShareController = new systemShare.ShareController(shareData)
// 显示分享面板
await controller.show(context, {
selectionMode: systemShare.SelectionMode.SINGLE,
previewMode: systemShare.SharePreviewMode.DETAIL
})
console.info('[KnockShareDemo] 分享面板显示成功')
} catch (error) {
const err = error as BusinessError
console.error('[KnockShareDemo] 系统分享失败:', err.code, err.message)
promptAction.showToast({
message: '分享失败',
duration: 2000
})
}
}
/**
* 构建 UI
*/
build() {
Column({ space: 16 }) {
// 标题
Text('碰一碰分享示例')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Center)
.padding(20)
// 提示信息
Text('将手机与另一台华为设备触碰即可分享图片')
.fontSize(14)
.fontColor('#666666')
.width('100%')
.textAlign(TextAlign.Center)
// 图片列表
ForEach(this.imageList, (image: ImageItem) => {
Row({ space: 12 }) {
// 图片预览
Image(fileUri.getUriFromPath(image.path))
.width(80)
.height(80)
.objectFit(ImageFit.Cover)
.borderRadius(8)
// 图片信息
Column({ space: 4 }) {
Text(image.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
Text(image.path)
.fontSize(12)
.fontColor('#999999')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
// 分享按钮
Button('分享')
.fontSize(14)
.height(32)
.onClick(() => {
this.shareViaSystemPanel(image)
})
}
.width('100%')
.padding(12)
.backgroundColor('#F5F5F5')
.borderRadius(12)
})
}
.width('100%')
.height('100%')
.padding(16)
}
}
六、分享不同类型的数据
6.1 分享图片
const shareData: systemShare.SharedData = new systemShare.SharedData({
utd: utd.UniformDataType.IMAGE,
uri: fileUri.getUriFromPath('/path/to/image.jpg'),
thumbnailUri: fileUri.getUriFromPath('/path/to/image.jpg')
})
6.2 分享文本
const shareData: systemShare.SharedData = new systemShare.SharedData({
utd: utd.UniformDataType.PLAIN_TEXT,
content: '这是要分享的文本内容'
})
6.3 分享文件
const shareData: systemShare.SharedData = new systemShare.SharedData({
utd: utd.UniformDataType.FILE,
uri: fileUri.getUriFromPath('/path/to/document.pdf'),
title: '文档标题',
description: '文档描述'
})
6.4 分享带标题和描述的图片
// 获取精确的文件类型
const utdTypeId = utd.getUniformDataTypeByFilenameExtension('.jpg', utd.UniformDataType.IMAGE)
const shareData: systemShare.SharedData = new systemShare.SharedData({
utd: utdTypeId,
uri: fileUri.getUriFromPath(filePath),
title: '图片标题',
description: '图片描述信息'
})
七、注意事项与最佳实践
7.1 回调函数的 this 绑定问题
使用类方法作为回调时,必须确保 this 正确绑定。推荐使用箭头函数:
// 推荐写法:使用箭头函数
private knockShareCallbackFn = (sharableTarget: harmonyShare.SharableTarget) => {
this.knockShareCallback(sharableTarget)
}
// 不推荐:直接传递方法引用可能导致 this 丢失
// harmonyShare.on('knockShare', this.knockShareCallback) // 可能出问题
7.2 注销监听时的函数引用
注销时必须传入与注册时完全相同的函数引用:
// 注册
harmonyShare.on('knockShare', this.knockShareCallbackFn)
// 注销 - 必须使用相同的引用
harmonyShare.off('knockShare', this.knockShareCallbackFn)
// 错误示例 - 新创建的函数不是同一个引用
// harmonyShare.off('knockShare', (target) => { ... }) // 无效!
7.3 文件路径处理
确保文件路径格式正确:
// 如果是 file:// 开头的 URI,需要去掉前缀
let filePath = imageUri.replace('file://', '')
// 使用 fileUri.getUriFromPath 转换为标准 URI
const uri = fileUri.getUriFromPath(filePath)
7.4 错误处理
务必添加完善的错误处理:
try {
// 执行分享操作
sharableTarget.share(shareData)
} catch (error) {
const err = error as BusinessError
console.error('分享失败 - code:', err.code, 'message:', err.message)
// 向用户展示友好的错误提示
promptAction.showToast({
message: '分享失败,请稍后重试',
duration: 2000
})
}
7.5 生命周期管理
确保在正确的生命周期注册和注销监听:
| 生命周期 | 操作 | 说明 |
|---|---|---|
| aboutToAppear | 注册监听 | 组件即将显示时注册 |
| aboutToDisappear | 注销监听 | 组件销毁前注销,防止内存泄漏 |
八、常见问题排查
Q1:碰一碰没有触发回调
可能原因及解决方案:
-
检查两台设备是否都开启了 NFC 功能
-
确认设备是否支持碰一碰功能(需要华为/荣耀设备)
-
检查是否正确注册了监听(查看日志是否有注册成功的输出)
-
确认应用是否在前台运行
Q2:分享失败
可能原因及解决方案:
-
检查文件路径是否正确存在
-
确认文件权限是否足够
-
检查 UTD 类型是否与实际文件类型匹配
-
查看错误码和错误信息,根据具体错误处理
Q3:注销监听无效
解决方案:
-
确保使用的是同一个函数引用
-
使用类成员变量保存回调函数,而不是每次创建新函数
九、总结
本文详细介绍了 HarmonyOS 碰一碰分享功能的完整开发流程:
-
通过
@kit.ShareKit引入分享相关 API -
使用
harmonyShare.on('knockShare', callback)注册监听 -
在回调中构建
systemShare.SharedData分享数据 -
调用
sharableTarget.share(shareData)执行分享 -
在组件销毁时使用
harmonyShare.off()注销监听
碰一碰分享为用户提供了便捷自然的文件传输体验,是提升应用易用性的优秀功能。希望本文能帮助开发者快速掌握这一能力的集成方法。
参考资料
-
HarmonyOS 开发者文档:Share Kit
-
HarmonyOS API 参考:@kit.ShareKit
-
HarmonyOS 开发者论坛











