前言
随着鸿蒙(HarmonyOS)生态的快速发展,碰一碰分享作为一项创新的近场交互功能,为用户提供了更加便捷的内容分享体验。本文将详细介绍如何在 UniApp 项目中集成鸿蒙原生碰一碰分享功能,实现设备间的快速图片分享。本文将以我开发的APP《BabyOne》为例,感兴趣的朋友可以直接下载体验~
什么是碰一碰分享?
碰一碰分享是鸿蒙系统提供的一种基于近场通信的分享方式,用户只需将两台设备轻轻碰触,即可快速分享内容。相比传统的分享方式,碰一碰分享具有以下优势:
- 🚀 操作简单:无需复杂的配对过程,轻碰即可分享
- ⚡ 速度快:基于近场通信,传输速度快
- 🔒 安全可靠:点对点传输,数据更安全
- 📱 无需安装:接收方无需安装应用即可接收内容
- 🎯 体验流畅:原生支持,用户体验优秀
应用场景
碰一碰分享适用于多种场景:
- 👶 儿童成长记录:分享宝宝照片、成长卡片
- 📸 照片分享:快速分享活动照片、合影
- 🎨 创意作品:分享绘画作品、手工制作
- 📇 名片交换:快速交换电子名片
- 🎁 优惠券分享:分享优惠券、活动海报
jack-knock-share 插件介绍
为了简化在 UniApp 中使用鸿蒙碰一碰分享的开发流程,我封装了 jack-knock-share 插件。这是一个基于 UTS(UniApp TypeScript)开发的原生插件,完全开源。
平台支持
- ✅ HarmonyOS:使用 @kit.ShareKit 原生 API
核心特性
为什么要封装这个插件?
在开发儿童成长记录(BabyOne)应用时,我需要实现宝宝名片的快速分享功能。直接使用鸿蒙原生 API 存在以下问题:
因此,我将功能封装成了 jack-knock-share 插件,大大简化了使用流程。
技术架构
插件目录结构
uni_modules/jack-knock-share/
├── utssdk/
│ ├── interface.uts # 接口定义
│ └── app-harmony/ # 鸿蒙平台实现
│ ├── index.uts # 主实现文件
│ └── native.uts # 原生分享实现
├── package.json
└── readme.md
核心 API 设计
插件提供了 2 个核心 API:
| uni.registerKnockShare(options) | 注册碰一碰分享监听 | imagePath, success, fail, complete |
| uni.unregisterKnockShare() | 注销碰一碰分享监听 | 无 |
插件完整源码
为了方便大家使用,这里提供 jack-knock-share 插件的完整源代码,可以直接复制到你的项目中使用。
1. utssdk/interface.uts(接口定义)
/**
* 碰一碰分享插件接口定义
*/
/**
* 注册碰一碰分享参数
*/
export type RegisterKnockShareOptions = {
/**
* 要分享的图片路径(本地临时文件路径)
*/
imagePath : string
/**
* 接口调用成功的回调函数
* @defaultValue null
*/
success ?: RegisterKnockShareSuccessCallback | null
/**
* 接口调用失败的回调函数
* @defaultValue null
*/
fail ?: RegisterKnockShareFailCallback | null
/**
* 接口调用结束的回调函数(调用成功、失败都会执行)
* @defaultValue null
*/
complete ?: RegisterKnockShareCompleteCallback | null
}
/**
* 注册成功回调结果
*/
export type RegisterKnockShareSuccess = {
/**
* 成功信息
*/
errMsg : string
}
/**
* 注册失败回调结果
*/
export type RegisterKnockShareFail = {
/**
* 错误码
*/
errCode : number
/**
* 错误信息
*/
errMsg : string
}
/**
* 注册完成回调结果
*/
export type RegisterKnockShareComplete = {
/**
* 信息
*/
errMsg : string
}
/**
* 注册成功回调函数
*/
export type RegisterKnockShareSuccessCallback = (result : RegisterKnockShareSuccess) => void
/**
* 注册失败回调函数
*/
export type RegisterKnockShareFailCallback = (result : RegisterKnockShareFail) => void
/**
* 注册完成回调函数
*/
export type RegisterKnockShareCompleteCallback = (result : RegisterKnockShareComplete) => void
/**
* 注册碰一碰分享函数类型
*/
export type RegisterKnockShare = (options : RegisterKnockShareOptions) => void
/**
* 注销碰一碰分享函数类型
*/
export type UnregisterKnockShare = () => void
/**
* 扩展 uni 全局对象
*/
export interface Uni {
/**
* registerKnockShare()
* @description
* 注册碰一碰分享监听,当用户触发碰一碰时自动分享指定图片
* @param {RegisterKnockShareOptions} options
* @return {void}
* @example
```typescript
uni.registerKnockShare({
imagePath: '/path/to/image.jpg',
success: (res) => {
console.log('注册成功:', res)
},
fail: (err) => {
console.error('注册失败:', err)
}
});
```
*/
registerKnockShare(options : RegisterKnockShareOptions) : void
/**
* unregisterKnockShare()
* @description
* 注销碰一碰分享监听
* @return {void}
* @example
```typescript
uni.unregisterKnockShare();
```
*/
unregisterKnockShare() : void
}
2. utssdk/app-harmony/index.uts(主实现文件)
/**
* 碰一碰分享插件 – HarmonyOS 实现
*/
import {
RegisterKnockShare,
RegisterKnockShareOptions,
RegisterKnockShareSuccess,
RegisterKnockShareFail,
RegisterKnockShareComplete,
UnregisterKnockShare
} from '../interface.uts'
// 导出类型定义
export {
RegisterKnockShare,
RegisterKnockShareOptions,
RegisterKnockShareSuccess,
RegisterKnockShareFail,
RegisterKnockShareComplete,
UnregisterKnockShare
}
// 导入 HarmonyOS 原生 API
import { harmonyShare } from '@kit.ShareKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { shareImageNative, ShareResult } from './native.uts'
/**
* 碰一碰回调函数引用
*/
let knockShareCallback : ((target : harmonyShare.SharableTarget) => void) | null = null
/**
* 当前要分享的图片路径
*/
let currentImagePath : string = ''
/**
* 当前注册的选项(用于回调)
*/
let currentOptions : RegisterKnockShareOptions | null = null
/**
* 注册碰一碰分享
*/
export const registerKnockShare : RegisterKnockShare = function (options : RegisterKnockShareOptions) {
try {
console.log('[KnockShare] 开始注册碰一碰分享')
console.log('[KnockShare] 图片路径:', options.imagePath)
// 保存图片路径和选项
currentImagePath = options.imagePath
currentOptions = options
// 定义碰一碰回调函数
knockShareCallback = (sharableTarget : harmonyShare.SharableTarget) => {
// 调用原生分享函数
const result : ShareResult = shareImageNative(sharableTarget, currentImagePath)
if (result.success) {
console.log('[KnockShare] 分享完成')
if (currentOptions != null) {
const successResult : RegisterKnockShareSuccess = {
errMsg: '分享成功'
}
const completeResult : RegisterKnockShareComplete = {
errMsg: '分享成功'
}
currentOptions.success?.(successResult)
currentOptions.complete?.(completeResult)
}
} else {
console.error('[KnockShare] 分享失败:', result.message)
if (currentOptions != null) {
const failResult : RegisterKnockShareFail = {
errCode: result.code ?? 0,
errMsg: result.message
}
const completeResult : RegisterKnockShareComplete = {
errMsg: result.message
}
currentOptions.fail?.(failResult)
currentOptions.complete?.(completeResult)
}
}
}
// 注册碰一碰监听
harmonyShare.on('knockShare', knockShareCallback)
console.log('[KnockShare] 碰一碰监听注册成功')
// 调用成功回调(注册成功)
const successResult : RegisterKnockShareSuccess = {
errMsg: '注册成功'
}
const completeResult : RegisterKnockShareComplete = {
errMsg: '注册成功'
}
options.success?.(successResult)
options.complete?.(completeResult)
} catch (error) {
console.error('[KnockShare] 注册失败:', error)
const err = error as BusinessError
// 调用失败回调
const failResult : RegisterKnockShareFail = {
errCode: err.code,
errMsg: err.message ?? '注册失败'
}
const completeResult : RegisterKnockShareComplete = {
errMsg: err.message ?? '注册失败'
}
options.fail?.(failResult)
options.complete?.(completeResult)
}
}
/**
* 注销碰一碰分享
*/
export const unregisterKnockShare : UnregisterKnockShare = function () {
try {
console.log('[KnockShare] 开始注销碰一碰分享')
if (knockShareCallback != null) {
// 注销监听
harmonyShare.off('knockShare', knockShareCallback)
// 清理变量
knockShareCallback = null
currentImagePath = ''
currentOptions = null
console.log('[KnockShare] 碰一碰监听注销成功')
} else {
console.log('[KnockShare] 没有需要注销的监听')
}
} catch (error) {
console.error('[KnockShare] 注销失败:', error)
}
}
3. utssdk/app-harmony/native.uts(原生分享实现)
/**
* 原生分享实现
*/
import { harmonyShare, systemShare } from '@kit.ShareKit'
import { uniformTypeDescriptor as utd } from '@kit.ArkData'
import { fileUri } from '@kit.CoreFileKit'
import { BusinessError } from '@kit.BasicServicesKit'
/**
* 分享数据接口(匹配 SharedRecord)
*/
interface ShareDataRecord {
utd: string
uri: string
thumbnailUri?: string
}
/**
* 分享结果接口
*/
export interface ShareResult {
success: boolean
code?: number
message: string
}
/**
* 分享图片(原生实现)
*/
export function shareImageNative(
sharableTarget : harmonyShare.SharableTarget,
imagePath : string
) : ShareResult {
try {
console.log('[Native] 碰一碰触发,开始分享')
console.log('[Native] 分享图片:', imagePath)
// 处理文件路径
let filePath = imagePath
if (filePath.startsWith('file://')) {
filePath = filePath.substring(7)
}
console.log('[Native] 处理后的路径:', filePath)
// 获取文件 URI
const imageUri = fileUri.getUriFromPath(filePath)
console.log('[Native] 文件 URI:', imageUri)
// 构建分享数据对象
const shareDataRecord : ShareDataRecord = {
utd: utd.UniformDataType.IMAGE,
uri: imageUri,
thumbnailUri: imageUri
}
// 创建 SharedData
const shareData = new systemShare.SharedData(shareDataRecord as systemShare.SharedRecord)
console.log('[Native] 分享数据构建完成')
// 执行分享
sharableTarget.share(shareData)
console.log('[Native] 分享成功')
return {
success: true,
message: '分享成功'
}
} catch (error) {
console.error('[Native] 分享失败:', error)
const err = error as BusinessError
return {
success: false,
code: err.code,
message: err.message ?? '分享失败'
}
}
}
如何使用插件源码
鸿蒙平台实现原理
1. 引入鸿蒙 SDK
import { harmonyShare, systemShare } from '@kit.ShareKit'
import { uniformTypeDescriptor as utd } from '@kit.ArkData'
import { fileUri } from '@kit.CoreFileKit'
import { BusinessError } from '@kit.BasicServicesKit'
2. 注册碰一碰监听
鸿蒙平台使用 harmonyShare.on('knockShare', callback) 注册碰一碰事件监听:
// 定义回调函数
const knockShareCallback = (sharableTarget : harmonyShare.SharableTarget) => {
// 当用户触发碰一碰时,这里会被调用
// sharableTarget 是分享目标对象
}
// 注册监听
harmonyShare.on('knockShare', knockShareCallback)
3. 构建分享数据
使用 systemShare.SharedData 构建分享数据:
// 1. 处理文件路径
let filePath = imagePath
if (filePath.startsWith('file://')) {
filePath = filePath.substring(7)
}
// 2. 获取文件 URI
const imageUri = fileUri.getUriFromPath(filePath)
// 3. 构建分享数据
const shareDataRecord = {
utd: utd.UniformDataType.IMAGE, // 数据类型:图片
uri: imageUri, // 文件 URI
thumbnailUri: imageUri // 缩略图 URI
}
// 4. 创建 SharedData
const shareData = new systemShare.SharedData(shareDataRecord)
4. 执行分享
// 调用 share 方法执行分享
sharableTarget.share(shareData)
5. 注销监听
// 注销碰一碰监听
harmonyShare.off('knockShare', knockShareCallback)
快速开始
第一步:安装插件
方式一:手动创建(推荐)
在你的 UniApp 项目根目录下创建以下目录结构:
uni_modules/jack-knock-share/
├── utssdk/
│ ├── app-harmony/
将上面"技术架构"章节中的完整源码,按照文件路径复制到对应位置
确保所有文件都已创建:
- uni_modules/jack-knock-share/utssdk/interface.uts
- uni_modules/jack-knock-share/utssdk/app-harmony/index.uts
- uni_modules/jack-knock-share/utssdk/app-harmony/native.uts
方式二:通过 UniApp 插件市场安装
访问 UniApp 插件市场,搜索 鸿蒙碰一碰分享插件 直接安装。
第二步:在页面中使用
1. 导入插件
<script>
// 使用条件编译,仅在鸿蒙平台导入
// #ifdef APP-HARMONY
import '@/uni_modules/jack-knock-share'
// #endif
export default {
data() {
return {
shareImage: '',
isSharing: false
}
}
}
</script>
2. 生成要分享的图片
使用 Canvas 生成图片:
methods: {
async generateShareImage() {
return new Promise((resolve, reject) => {
const ctx = uni.createCanvasContext('shareCanvas', this)
// 绘制背景
ctx.setFillStyle('#667eea')
ctx.fillRect(0, 0, 375, 500)
// 绘制文字
ctx.setFillStyle('#ffffff')
ctx.setFontSize(32)
ctx.setTextAlign('center')
ctx.fillText('宝宝成长卡片', 187.5, 250)
// 导出图片
ctx.draw(false, () => {
setTimeout(() => {
uni.canvasToTempFilePath({
canvasId: 'shareCanvas',
success: (res) => {
resolve(res.tempFilePath)
},
fail: reject
}, this)
}, 500)
})
})
}
}
3. 注册碰一碰分享
async startKnockShare() {
uni.showLoading({ title: '生成中…' })
try {
// 1. 生成图片
const imagePath = await this.generateShareImage()
this.shareImage = imagePath
uni.hideLoading()
// 2. 注册碰一碰分享
// #ifdef APP-HARMONY
uni.registerKnockShare({
imagePath: imagePath,
success: (res) => {
console.log('注册成功:', res)
this.isSharing = true
uni.showToast({
title: '请轻碰设备进行分享',
icon: 'none',
duration: 3000
})
},
fail: (err) => {
console.error('注册失败:', err)
uni.showToast({
title: '注册失败: ' + err.errMsg,
icon: 'none'
})
},
complete: (res) => {
console.log('完成:', res)
}
})
// #endif
} catch (error) {
uni.hideLoading()
console.error('生成失败:', error)
uni.showToast({
title: '生成失败',
icon: 'none'
})
}
}
4. 停止分享
stopKnockShare() {
// #ifdef APP-HARMONY
uni.unregisterKnockShare()
// #endif
this.isSharing = false
uni.showToast({
title: '已关闭分享',
icon: 'success'
})
}
5. 页面生命周期管理
onHide() {
// 页面隐藏时自动关闭分享
if (this.isSharing) {
// #ifdef APP-HARMONY
uni.unregisterKnockShare()
// #endif
this.isSharing = false
}
},
onUnload() {
// 页面卸载时关闭分享
if (this.isSharing) {
// #ifdef APP-HARMONY
uni.unregisterKnockShare()
// #endif
}
}
完整示例:宝宝名片分享
下面是一个完整的宝宝名片分享示例页面:
<template>
<view class="container">
<!– 预览区域 –>
<view class="preview-section">
<view class="preview-card" v-if="shareImage">
<image :src="shareImage" mode="aspectFit" class="preview-image"></image>
</view>
<view class="preview-placeholder" v-else>
<text class="placeholder-text">点击下方按钮生成名片</text>
</view>
</view>
<!– 操作按钮 –>
<view class="action-section">
<button
@click="generateAndShare"
class="btn-primary"
:disabled="isSharing"
>
{{ isSharing ? '🔄 分享中…' : '✨ 生成并开启碰一碰分享' }}
</button>
<button
@click="stopShare"
class="btn-secondary"
v-if="isSharing"
>
❌ 关闭分享
</button>
</view>
<!– 提示信息 –>
<view class="tips-section" v-if="isSharing">
<view class="tips-card">
<text class="tips-icon">💡</text>
<text class="tips-text">将设备轻碰另一台设备即可分享名片</text>
</view>
</view>
<!– 隐藏的 Canvas 用于生成图片 –>
<canvas canvas-id="shareCanvas" class="hidden-canvas"></canvas>
</view>
</template>
<script>
// 引入碰一碰分享插件
// #ifdef APP-HARMONY
import '@/uni_modules/jack-knock-share'
// #endif
export default {
data() {
return {
shareImage: '',
isSharing: false,
babyInfo: {
name: '小宝贝',
age: '1岁3个月',
birthday: '2023-10-15',
weight: '10.5kg',
height: '78cm'
}
}
},
methods: {
/**
* 生成并开启碰一碰分享
*/
async generateAndShare() {
uni.showLoading({ title: '生成中…' })
try {
// 1. 生成名片图片
const imagePath = await this.generateBabyCard()
this.shareImage = imagePath
uni.hideLoading()
// 2. 注册碰一碰分享
// #ifdef APP-HARMONY
uni.registerKnockShare({
imagePath: imagePath,
success: (res) => {
console.log('[分享] 注册成功:', res)
this.isSharing = true
uni.showToast({
title: '请轻碰设备进行分享',
icon: 'none',
duration: 3000
})
},
fail: (err) => {
console.error('[分享] 注册失败:', err)
uni.showToast({
title: '注册失败: ' + err.errMsg,
icon: 'none'
})
},
complete: (res) => {
console.log('[分享] 完成:', res)
}
})
// #endif
// #ifndef APP-HARMONY
uni.showToast({
title: '当前平台不支持碰一碰分享',
icon: 'none'
})
// #endif
} catch (error) {
uni.hideLoading()
console.error('[分享] 生成失败:', error)
uni.showToast({
title: '生成失败',
icon: 'none'
})
}
},
/**
* 生成宝宝名片图片
*/
async generateBabyCard() {
return new Promise((resolve, reject) => {
const ctx = uni.createCanvasContext('shareCanvas', this)
const canvasWidth = 375
const canvasHeight = 500
// 绘制渐变背景
const gradient = ctx.createLinearGradient(0, 0, canvasWidth, canvasHeight)
gradient.addColorStop(0, '#667eea')
gradient.addColorStop(1, '#764ba2')
ctx.setFillStyle(gradient)
ctx.fillRect(0, 0, canvasWidth, canvasHeight)
// 绘制白色卡片
ctx.setFillStyle('#ffffff')
ctx.setShadow(0, 10, 30, 'rgba(0, 0, 0, 0.1)')
ctx.fillRect(30, 80, canvasWidth – 60, canvasHeight – 160)
// 绘制标题
ctx.setFillStyle('#333333')
ctx.setFontSize(36)
ctx.setTextAlign('center')
ctx.fillText('👶 宝宝成长卡片', canvasWidth / 2, 140)
// 绘制分割线
ctx.setStrokeStyle('#eeeeee')
ctx.setLineWidth(2)
ctx.beginPath()
ctx.moveTo(60, 170)
ctx.lineTo(canvasWidth – 60, 170)
ctx.stroke()
// 绘制宝宝信息
const info = this.babyInfo
const startY = 220
const lineHeight = 50
ctx.setFillStyle('#666666')
ctx.setFontSize(28)
ctx.setTextAlign('left')
ctx.fillText(`姓名: ${info.name}`, 70, startY)
ctx.fillText(`年龄: ${info.age}`, 70, startY + lineHeight)
ctx.fillText(`生日: ${info.birthday}`, 70, startY + lineHeight * 2)
ctx.fillText(`体重: ${info.weight}`, 70, startY + lineHeight * 3)
ctx.fillText(`身高: ${info.height}`, 70, startY + lineHeight * 4)
// 绘制底部提示
ctx.setFillStyle('#999999')
ctx.setFontSize(20)
ctx.setTextAlign('center')
ctx.fillText('碰一碰分享给好友', canvasWidth / 2, canvasHeight – 40)
// 导出图片
ctx.draw(false, () => {
setTimeout(() => {
uni.canvasToTempFilePath({
canvasId: 'shareCanvas',
success: (res) => {
console.log('[Canvas] 图片生成成功:', res.tempFilePath)
resolve(res.tempFilePath)
},
fail: (err) => {
console.error('[Canvas] 图片生成失败:', err)
reject(err)
}
}, this)
}, 500)
})
})
},
/**
* 停止分享
*/
stopShare() {
// #ifdef APP-HARMONY
uni.unregisterKnockShare()
console.log('[分享] 已注销监听')
// #endif
this.isSharing = false
uni.showToast({
title: '已关闭分享',
icon: 'success'
})
}
},
onHide() {
// 页面隐藏时自动关闭分享
if (this.isSharing) {
// #ifdef APP-HARMONY
uni.unregisterKnockShare()
// #endif
this.isSharing = false
console.log('[生命周期] 页面隐藏,已关闭分享')
}
},
onUnload() {
// 页面卸载时关闭分享
if (this.isSharing) {
// #ifdef APP-HARMONY
uni.unregisterKnockShare()
// #endif
console.log('[生命周期] 页面卸载,已关闭分享')
}
}
}
</script>
<style scoped>
.container {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40rpx;
}
.preview-section {
margin-bottom: 60rpx;
}
.preview-card {
background: white;
border-radius: 30rpx;
overflow: hidden;
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.15);
}
.preview-image {
width: 100%;
height: 1000rpx;
}
.preview-placeholder {
background: rgba(255, 255, 255, 0.2);
border-radius: 30rpx;
height: 1000rpx;
display: flex;
align-items: center;
justify-content: center;
border: 4rpx dashed rgba(255, 255, 255, 0.5);
}
.placeholder-text {
color: white;
font-size: 32rpx;
}
.action-section {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.btn-primary,
.btn-secondary {
width: 100%;
height: 96rpx;
border-radius: 48rpx;
font-size: 32rpx;
font-weight: bold;
border: none;
}
.btn-primary {
background: white;
color: #667eea;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
}
.btn-primary[disabled] {
opacity: 0.6;
}
.btn-secondary {
background: rgba(255, 255, 255, 0.2);
color: white;
border: 2rpx solid white;
}
.tips-section {
margin-top: 40rpx;
}
.tips-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20rpx;
padding: 32rpx;
display: flex;
align-items: center;
gap: 20rpx;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
}
.tips-icon {
font-size: 48rpx;
}
.tips-text {
flex: 1;
font-size: 28rpx;
color: #666;
line-height: 40rpx;
}
.hidden-canvas {
position: fixed;
left: -9999rpx;
top: -9999rpx;
width: 750rpx;
height: 1000rpx;
}
</style>
核心技术要点
1. 文件路径处理
鸿蒙系统需要使用 fileUri.getUriFromPath() 将文件路径转换为 URI:
// 处理 file:// 前缀
let filePath = imagePath
if (filePath.startsWith('file://')) {
filePath = filePath.substring(7)
}
// 转换为 URI
const imageUri = fileUri.getUriFromPath(filePath)
2. 数据类型定义
使用 uniformTypeDescriptor 定义分享数据类型:
import { uniformTypeDescriptor as utd } from '@kit.ArkData'
const shareDataRecord = {
utd: utd.UniformDataType.IMAGE, // 图片类型
uri: imageUri,
thumbnailUri: imageUri
}
3. 监听器管理
使用全局变量管理监听器,避免重复注册:
let knockShareCallback : ((target : harmonyShare.SharableTarget) => void) | null = null
// 注册前检查
if (knockShareCallback != null) {
// 先注销旧的监听器
harmonyShare.off('knockShare', knockShareCallback)
}
// 注册新的监听器
harmonyShare.on('knockShare', knockShareCallback)
4. 错误处理
完善的错误处理机制:
try {
// 执行分享逻辑
sharableTarget.share(shareData)
return {
success: true,
message: '分享成功'
}
} catch (error) {
const err = error as BusinessError
return {
success: false,
code: err.code,
message: err.message ?? '分享失败'
}
}
5. 生命周期管理
在页面生命周期中管理分享状态:
// 页面隐藏时注销
onHide() {
if (this.isSharing) {
uni.unregisterKnockShare()
this.isSharing = false
}
}
// 页面卸载时注销
onUnload() {
if (this.isSharing) {
uni.unregisterKnockShare()
}
}
最佳实践
1. 条件编译
使用条件编译确保代码只在鸿蒙平台运行:
// #ifdef APP-HARMONY
uni.registerKnockShare({
imagePath: imagePath,
success: (res) => {
console.log('注册成功')
}
})
// #endif
// #ifndef APP-HARMONY
uni.showToast({
title: '当前平台不支持碰一碰分享',
icon: 'none'
})
// #endif
2. 图片生成优化
使用 Canvas 生成图片时的注意事项:
// 1. 延迟导出,确保绘制完成
ctx.draw(false, () => {
setTimeout(() => {
uni.canvasToTempFilePath({
canvasId: 'shareCanvas',
success: (res) => {
resolve(res.tempFilePath)
}
}, this)
}, 500) // 延迟 500ms
})
// 2. 使用高质量设置
uni.canvasToTempFilePath({
canvasId: 'shareCanvas',
quality: 1, // 最高质量
fileType: 'jpg', // 文件类型
success: (res) => {
// …
}
}, this)
3. 用户体验优化
提供清晰的视觉反馈:
<template>
<button
@click="generateAndShare"
:disabled="isSharing"
:class="{ 'btn-loading': isSharing }"
>
{{ isSharing ? '🔄 分享中…' : '✨ 开启分享' }}
</button>
<view class="tips" v-if="isSharing">
<text>💡 将设备轻碰另一台设备即可分享</text>
</view>
</template>
<style>
.btn-loading {
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
</style>
4. 错误提示
提供友好的错误提示:
uni.registerKnockShare({
imagePath: imagePath,
success: (res) => {
uni.showToast({
title: '请轻碰设备进行分享',
icon: 'none',
duration: 3000
})
},
fail: (err) => {
let errorMsg = '注册失败'
if (err.errCode === 401) {
errorMsg = '权限不足'
} else if (err.errCode === 404) {
errorMsg = '文件不存在'
}
uni.showToast({
title: errorMsg,
icon: 'none'
})
}
})
5. 状态管理
使用状态标志管理分享状态:
data() {
return {
shareState: {
isGenerating: false, // 是否正在生成
isSharing: false, // 是否正在分享
shareImage: '', // 分享图片路径
error: null // 错误信息
}
}
},
methods: {
updateShareState(updates) {
this.shareState = { …this.shareState, …updates }
}
}
常见问题 FAQ
Q1: 碰一碰没有触发分享?
可能原因:
解决方案:
- 检查设备是否支持 HarmonyOS 碰一碰功能(需要 HarmonyOS 6.0+)
- 查看控制台日志确认注册状态
- 确保图片路径是本地临时文件路径
- 检查 harmonyShare.on() 是否成功调用
// 添加详细日志
uni.registerKnockShare({
imagePath: imagePath,
success: (res) => {
console.log('[调试] 注册成功:', res)
},
fail: (err) => {
console.error('[调试] 注册失败:', err.errCode, err.errMsg)
}
})
Q2: 分享失败,提示文件不存在?
可能原因:
- Canvas 生成图片失败
- 图片路径格式错误
- 文件已被清理
解决方案:
// 1. 检查图片是否生成成功
const imagePath = await this.generateShareImage()
console.log('生成的图片路径:', imagePath)
// 2. 验证文件是否存在
uni.getFileInfo({
filePath: imagePath,
success: (res) => {
console.log('文件大小:', res.size)
// 文件存在,可以分享
},
fail: (err) => {
console.error('文件不存在:', err)
}
})
// 3. 确保路径格式正确
let filePath = imagePath
if (filePath.startsWith('file://')) {
filePath = filePath.substring(7)
}
Q3: 对方需要安装应用吗?
答:不需要。 碰一碰分享的是图片文件,对方设备会直接接收图片到相册或文件管理器,无需安装应用。
Q4: 可以分享其他类型的文件吗?
答:目前插件只支持图片分享。 如需分享其他类型文件,需要修改 native.uts 中的数据类型:
// 分享文本
const shareDataRecord = {
utd: utd.UniformDataType.TEXT,
uri: textUri
}
// 分享视频
const shareDataRecord = {
utd: utd.UniformDataType.VIDEO,
uri: videoUri
}
// 分享文件
const shareDataRecord = {
utd: utd.UniformDataType.FILE,
uri: fileUri
}
Q5: 如何在多个页面使用?
答:每个页面独立注册和注销。 建议封装成全局方法:
// utils/knock-share.js
export function startKnockShare(imagePath) {
return new Promise((resolve, reject) => {
// #ifdef APP-HARMONY
uni.registerKnockShare({
imagePath: imagePath,
success: resolve,
fail: reject
})
// #endif
})
}
export function stopKnockShare() {
// #ifdef APP-HARMONY
uni.unregisterKnockShare()
// #endif
}
// 在页面中使用
import { startKnockShare, stopKnockShare } from '@/utils/knock-share.js'
methods: {
async handleShare() {
try {
await startKnockShare(this.imagePath)
uni.showToast({ title: '请轻碰设备' })
} catch (error) {
uni.showToast({ title: '注册失败' })
}
}
}
Q6: 页面切换后分享还有效吗?
答:有效,但不推荐。 建议在页面隐藏时注销监听:
onHide() {
// 页面隐藏时注销,避免在其他页面触发
if (this.isSharing) {
uni.unregisterKnockShare()
this.isSharing = false
}
}
Q7: 如何知道分享是否成功?
答:通过回调函数。 在 registerKnockShare 的回调中处理:
uni.registerKnockShare({
imagePath: imagePath,
success: (res) => {
// 注册成功
console.log('监听已注册')
},
fail: (err) => {
// 注册失败
console.error('注册失败:', err)
}
})
// 注意:碰一碰触发后的分享结果在 native.uts 中处理
// 如需获取分享结果,可以在 knockShareCallback 中添加回调
Q8: Canvas 生成图片模糊怎么办?
解决方案:
// 1. 使用更大的 Canvas 尺寸
const scale = 2 // 放大倍数
const canvasWidth = 375 * scale
const canvasHeight = 500 * scale
// 2. 设置高质量导出
uni.canvasToTempFilePath({
canvasId: 'shareCanvas',
quality: 1, // 最高质量
destWidth: canvasWidth,
destHeight: canvasHeight,
success: (res) => {
// …
}
}, this)
// 3. 使用 Canvas 2D API (推荐)
const canvas = await uni.createOffscreenCanvas({
type: '2d',
width: canvasWidth,
height: canvasHeight
})
进阶技巧
1. 动态生成不同类型的名片
// 根据类型生成不同的名片
async generateCard(type) {
switch (type) {
case 'baby':
return await this.generateBabyCard()
case 'family':
return await this.generateFamilyCard()
case 'event':
return await this.generateEventCard()
default:
return await this.generateDefaultCard()
}
}
2. 添加二维码
// 使用第三方库生成二维码
import QRCode from '@/utils/qrcode.js'
methods: {
async generateCardWithQRCode() {
const ctx = uni.createCanvasContext('shareCanvas', this)
// 绘制基础内容
// …
// 生成二维码
const qrCodeData = QRCode.generate('https://example.com/baby/123')
// 绘制二维码
ctx.drawImage(qrCodeData, 250, 350, 100, 100)
// 导出
ctx.draw(false, () => {
// …
})
}
}
3. 批量分享
// 生成多张图片并依次分享
async batchShare(imageList) {
for (let i = 0; i < imageList.length; i++) {
const imagePath = imageList[i]
// 注册分享
await new Promise((resolve) => {
uni.registerKnockShare({
imagePath: imagePath,
success: () => {
uni.showToast({
title: `第 ${i + 1}/${imageList.length} 张`,
icon: 'none'
})
resolve()
}
})
})
// 等待用户碰一碰
await this.waitForKnock()
// 注销当前分享
uni.unregisterKnockShare()
}
}
4. 分享统计
data() {
return {
shareStats: {
totalShares: 0,
successCount: 0,
failCount: 0
}
}
},
methods: {
trackShare(success) {
this.shareStats.totalShares++
if (success) {
this.shareStats.successCount++
} else {
this.shareStats.failCount++
}
// 上报统计数据
this.reportStats()
},
reportStats() {
// 上报到服务器
uni.request({
url: 'https://api.example.com/stats',
method: 'POST',
data: this.shareStats
})
}
}
性能优化建议
1. 图片缓存
避免重复生成图片:
data() {
return {
imageCache: new Map() // 缓存生成的图片
}
},
methods: {
async getOrGenerateImage(key, generator) {
// 检查缓存
if (this.imageCache.has(key)) {
console.log('使用缓存图片')
return this.imageCache.get(key)
}
// 生成新图片
const imagePath = await generator()
this.imageCache.set(key, imagePath)
return imagePath
},
async shareWithCache() {
const cacheKey = `baby_${this.babyInfo.id}`
const imagePath = await this.getOrGenerateImage(
cacheKey,
() => this.generateBabyCard()
)
// 注册分享
uni.registerKnockShare({ imagePath })
}
}
2. 异步加载
使用异步加载优化用户体验:
async generateAndShare() {
// 显示加载动画
this.showLoading()
try {
// 使用 Promise.all 并行处理
const [imagePath, userInfo] = await Promise.all([
this.generateShareImage(),
this.fetchUserInfo()
])
// 注册分享
await this.registerShare(imagePath)
} catch (error) {
this.handleError(error)
} finally {
this.hideLoading()
}
}
3. 内存管理
及时清理不需要的资源:
onUnload() {
// 注销监听
if (this.isSharing) {
uni.unregisterKnockShare()
}
// 清理缓存
this.imageCache.clear()
// 清理临时文件
this.cleanTempFiles()
},
methods: {
cleanTempFiles() {
// 删除临时图片文件
if (this.shareImage) {
uni.removeSavedFile({
filePath: this.shareImage,
success: () => {
console.log('临时文件已清理')
}
})
}
}
}
4. 防抖处理
避免用户快速点击:
methods: {
generateAndShare: debounce(function() {
// 分享逻辑
}, 1000),
// 或使用标志位
async generateAndShare() {
if (this.isProcessing) {
return
}
this.isProcessing = true
try {
// 分享逻辑
} finally {
setTimeout(() => {
this.isProcessing = false
}, 1000)
}
}
}
源码解析:核心实现
1. 接口设计
插件使用 TypeScript 定义了清晰的接口:
// 参数类型
export type RegisterKnockShareOptions = {
imagePath : string // 必填
success ?: RegisterKnockShareSuccessCallback | null // 可选
fail ?: RegisterKnockShareFailCallback | null
complete ?: RegisterKnockShareCompleteCallback | null
}
// 回调类型
export type RegisterKnockShareSuccessCallback = (result : RegisterKnockShareSuccess) => void
这种设计的优势:
- 类型安全:编译时检查参数类型
- 可选回调:灵活的回调机制
- 清晰的文档:类型即文档
2. 状态管理
使用模块级变量管理状态:
let knockShareCallback : ((target : harmonyShare.SharableTarget) => void) | null = null
let currentImagePath : string = ''
let currentOptions : RegisterKnockShareOptions | null = null
这种设计确保:
- 单例模式:全局只有一个监听器
- 状态隔离:避免多次注册冲突
- 内存安全:注销时清理所有引用
3. 错误处理
完善的错误处理机制:
try {
// 执行操作
harmonyShare.on('knockShare', knockShareCallback)
// 成功回调
options.success?.(successResult)
options.complete?.(completeResult)
} catch (error) {
const err = error as BusinessError
// 失败回调
const failResult : RegisterKnockShareFail = {
errCode: err.code,
errMsg: err.message ?? '注册失败'
}
options.fail?.(failResult)
options.complete?.(completeResult)
}
4. 回调链
巧妙的回调链设计:
// 1. 注册时的回调
registerKnockShare({
success: () => {
// 注册成功
}
})
// 2. 碰一碰触发时的回调
knockShareCallback = (sharableTarget) => {
const result = shareImageNative(sharableTarget, imagePath)
if (result.success) {
// 分享成功回调
currentOptions.success?.(successResult)
} else {
// 分享失败回调
currentOptions.fail?.(failResult)
}
}
这种设计实现了:
- 注册回调:告知注册是否成功
- 分享回调:告知分享是否成功
- 完整的生命周期:覆盖所有状态
插件发布
UniApp 插件市场
jack-knock-share 插件已发布到 UniApp 插件市场,可以直接安装使用:
插件地址: https://ext.dcloud.net.cn/plugin?name=jack-knock-share
安装方式:
插件特点:
- ✅ 完全免费
- ✅ 开源代码
- ✅ 持续维护
- ✅ 技术支持
本地使用
如果不想通过插件市场安装,也可以直接复制本文提供的完整源码到项目中使用。
总结
通过本文,我详细介绍了如何在 UniApp 项目中集成鸿蒙原生碰一碰分享功能。jack-knock-share 插件具有以下优势:
✅ 简单易用:仅需两个 API,快速集成 ✅ 自动触发:注册后自动监听碰一碰事件 ✅ 完善的回调:支持成功、失败、完成回调 ✅ 生命周期管理:支持注册和注销监听 ✅ 完全开源:本文提供完整源码 ✅ 插件市场发布:可直接安装使用
碰一碰分享作为鸿蒙系统的特色功能,为用户提供了更加便捷的内容分享体验。无论是开发儿童成长记录应用、照片分享应用,还是社交类应用,jack-knock-share 都能满足你的需求。
希望本文能帮助你快速掌握在 UniApp 中使用鸿蒙碰一碰分享的方法!
参考资料
- HarmonyOS ShareKit 官方文档
- HarmonyOS 碰一碰分享指南
- UniApp UTS 插件开发指南
- UniApp 条件编译
- UniApp 插件市场
如有问题或建议,欢迎在评论区交流讨论!
💡 提示:本文示例代码已在 HarmonyOS NEXT 上测试通过。如果你在使用过程中遇到问题,欢迎在评论区留言!
⭐ 如果本文对你有帮助,欢迎点赞、收藏、关注!也欢迎分享给更多需要的开发者!
🔗 插件地址: https://ext.dcloud.net.cn/plugin?name=jack-knock-share





