文章目录
-
- 每日一句正能量
- 导读
- 一、项目架构设计:三端协同的总体思路
-
- [1.1 功能分工](#1.1 功能分工)
- [1.2 技术架构](#1.2 技术架构)
- [1.3 数据模型](#1.3 数据模型)
- 二、分布式数据同步:三端一致的基石
-
- [2.1 同步服务封装](#2.1 同步服务封装)
- [2.2 冲突解决策略](#2.2 冲突解决策略)
- 三、任务接续:从手机速记到平板编辑
-
- [3.1 手机端发起接续](#3.1 手机端发起接续)
- [3.2 平板端恢复接续](#3.2 平板端恢复接续)
- [3.3 接续状态恢复 UI](#3.3 接续状态恢复 UI)
- 四、跨设备拖拽:从手机拖图片到平板
-
- [4.1 拖拽发起(手机端)](#4.1 拖拽发起(手机端))
- [4.2 拖拽接收(平板端)](#4.2 拖拽接收(平板端))
- 五、手表端:轻量提醒与语音速记
-
- [5.1 手表端 Ability](#5.1 手表端 Ability)
- [5.2 手表端列表与语音速记](#5.2 手表端列表与语音速记)
- 六、数据同步时序:一次完整的协同流程
- 七、测试验证要点
- 八、结语

每日一句正能量
人生的每一步都算数,你只需埋头播种,坐等秋后丰收。
付出不会消失,只是延迟显现。
导读
分布式能力是 HarmonyOS 最核心的差异化特性,也是学生开发者在毕业设计中最想展示的技术亮点。但"跨设备协同"不能只停留在概念层面------用户需要的是在手机上速记、在平板上深度编辑、在手表上查看提醒的无缝体验。本文将完整解析一个"多端协同笔记应用"的项目实现,涵盖手机端的富文本编辑、平板端的大屏深度处理、手表端的轻量提醒,以及三者之间的实时数据同步、任务接续和跨设备拖拽。所有代码基于 HarmonyOS 7.0 API 26 编写,可直接在 DevEco Studio 4.2 中运行。
一、项目架构设计:三端协同的总体思路
1.1 功能分工
| 设备 | 核心功能 | 交互形态 | 数据角色 |
|---|---|---|---|
| 手机 | 速记、拍照插入、语音转文字 | 单手握持、快速操作 | 主控端 + 数据发起 |
| 平板 | 长文编辑、手写批注、多窗口对照 | 大屏沉浸、精细操作 | 协同端 + 深度处理 |
| 手表 | 查看标题列表、语音速记、到期提醒 | 抬腕即用、极简交互 | 轻量端 + 提醒触达 |
1.2 技术架构
图1:三端协同笔记应用系统架构图
图片内容说明(中文):中心为"分布式数据层(UDDL)",内部包含"NoteSyncObject(笔记同步对象)"和"ConflictResolver(冲突解决器)"。中心向外连接三个设备节点:手机(包含NoteEditorAbility、CameraCapture、VoiceInput)、平板(包含NoteEditorAbility、HandwritingCanvas、SplitWindow)、手表(包含NoteListAbility、VoiceMemo、ReminderService)。三个设备之间用双向箭头标注"实时同步",箭头旁标注"sessionId: note_sync_group"。底部为"云端备份(可选)",用虚线连接中心数据层。
#mermaid-svg-2YD5I6xLqRHJQuGj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2YD5I6xLqRHJQuGj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2YD5I6xLqRHJQuGj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2YD5I6xLqRHJQuGj .error-icon{fill:#552222;}#mermaid-svg-2YD5I6xLqRHJQuGj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2YD5I6xLqRHJQuGj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2YD5I6xLqRHJQuGj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2YD5I6xLqRHJQuGj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2YD5I6xLqRHJQuGj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2YD5I6xLqRHJQuGj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2YD5I6xLqRHJQuGj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2YD5I6xLqRHJQuGj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2YD5I6xLqRHJQuGj .marker.cross{stroke:#333333;}#mermaid-svg-2YD5I6xLqRHJQuGj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2YD5I6xLqRHJQuGj p{margin:0;}#mermaid-svg-2YD5I6xLqRHJQuGj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2YD5I6xLqRHJQuGj .cluster-label text{fill:#333;}#mermaid-svg-2YD5I6xLqRHJQuGj .cluster-label span{color:#333;}#mermaid-svg-2YD5I6xLqRHJQuGj .cluster-label span p{background-color:transparent;}#mermaid-svg-2YD5I6xLqRHJQuGj .label text,#mermaid-svg-2YD5I6xLqRHJQuGj span{fill:#333;color:#333;}#mermaid-svg-2YD5I6xLqRHJQuGj .node rect,#mermaid-svg-2YD5I6xLqRHJQuGj .node circle,#mermaid-svg-2YD5I6xLqRHJQuGj .node ellipse,#mermaid-svg-2YD5I6xLqRHJQuGj .node polygon,#mermaid-svg-2YD5I6xLqRHJQuGj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2YD5I6xLqRHJQuGj .rough-node .label text,#mermaid-svg-2YD5I6xLqRHJQuGj .node .label text,#mermaid-svg-2YD5I6xLqRHJQuGj .image-shape .label,#mermaid-svg-2YD5I6xLqRHJQuGj .icon-shape .label{text-anchor:middle;}#mermaid-svg-2YD5I6xLqRHJQuGj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2YD5I6xLqRHJQuGj .rough-node .label,#mermaid-svg-2YD5I6xLqRHJQuGj .node .label,#mermaid-svg-2YD5I6xLqRHJQuGj .image-shape .label,#mermaid-svg-2YD5I6xLqRHJQuGj .icon-shape .label{text-align:center;}#mermaid-svg-2YD5I6xLqRHJQuGj .node.clickable{cursor:pointer;}#mermaid-svg-2YD5I6xLqRHJQuGj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2YD5I6xLqRHJQuGj .arrowheadPath{fill:#333333;}#mermaid-svg-2YD5I6xLqRHJQuGj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2YD5I6xLqRHJQuGj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2YD5I6xLqRHJQuGj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2YD5I6xLqRHJQuGj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2YD5I6xLqRHJQuGj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2YD5I6xLqRHJQuGj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2YD5I6xLqRHJQuGj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2YD5I6xLqRHJQuGj .cluster text{fill:#333;}#mermaid-svg-2YD5I6xLqRHJQuGj .cluster span{color:#333;}#mermaid-svg-2YD5I6xLqRHJQuGj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2YD5I6xLqRHJQuGj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2YD5I6xLqRHJQuGj rect.text{fill:none;stroke-width:0;}#mermaid-svg-2YD5I6xLqRHJQuGj .icon-shape,#mermaid-svg-2YD5I6xLqRHJQuGj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2YD5I6xLqRHJQuGj .icon-shape p,#mermaid-svg-2YD5I6xLqRHJQuGj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2YD5I6xLqRHJQuGj .icon-shape .label rect,#mermaid-svg-2YD5I6xLqRHJQuGj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2YD5I6xLqRHJQuGj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2YD5I6xLqRHJQuGj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2YD5I6xLqRHJQuGj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 手表端
平板端
手机端
分布式数据层
实时同步
实时同步
实时同步
UDDL 统一数据层
NoteSyncObject
笔记同步对象
ConflictResolver
LWW + 字段级合并
NoteEditorAbility
富文本编辑
CameraCapture
拍照插入
VoiceInput
语音转文字
NoteEditorAbility
长文编辑
HandwritingCanvas
手写批注
SplitWindow
多窗口对照
NoteListAbility
标题列表
VoiceMemo
语音速记
ReminderService
到期提醒
云端备份
可选
1.3 数据模型
typescript
// model/NoteModel.ets
export class NoteModel {
id: string = '';
title: string = '';
content: string = ''; // 富文本 HTML / MarkDown
handwritingData: ArrayBuffer | null = null; // 手写笔迹数据
voiceMemoUri: string = ''; // 语音备忘路径
tags: string[] = [];
createTime: number = 0;
updateTime: number = 0;
reminderTime: number = 0; // 提醒时间
syncVersion: number = 0; // 用于冲突解决
}
export class NoteChangeLog {
noteId: string = '';
field: string = ''; // 变更字段:title/content/handwriting/...
oldValue: string = '';
newValue: string = '';
timestamp: number = 0;
deviceId: string = '';
}
二、分布式数据同步:三端一致的基石
2.1 同步服务封装
typescript
// service/NoteSyncService.ets
import { distributed } from '@ohos.data.distributed';
import { NoteModel } from '../model/NoteModel';
export class NoteSyncService {
private syncObject: distributed.DistributedObject | null = null;
private sessionId: string = 'note_sync_group';
private listeners: Array<(notes: NoteModel[]) => void> = [];
async init(): Promise<void> {
// 7.0:创建分布式数据对象,启用实时同步
this.syncObject = distributed.createObject(this.sessionId, {
notes: [] as NoteModel[],
lastSyncTime: 0
}, {
qos: distributed.QoS.HIGH_RELIABILITY,
syncMode: distributed.SyncMode.REALTIME,
conflictResolution: distributed.ConflictResolution.LAST_WRITE_WINS
});
// 监听数据变更
this.syncObject.on('change', (sessionId: string, fields: Array<string>) => {
if (fields.includes('notes')) {
const notes = this.syncObject!.notes as NoteModel[];
this.notifyListeners(notes);
}
});
}
addNote(note: NoteModel): void {
if (!this.syncObject) return;
const notes = this.syncObject.notes as NoteModel[];
note.syncVersion = Date.now();
notes.push(note);
this.syncObject.batchUpdate({ notes });
}
updateNote(id: string, updates: Partial<NoteModel>): void {
if (!this.syncObject) return;
const notes = this.syncObject.notes as NoteModel[];
const idx = notes.findIndex(n => n.id === id);
if (idx >= 0) {
notes[idx] = { ...notes[idx], ...updates, syncVersion: Date.now() };
this.syncObject.batchUpdate({ notes });
}
}
deleteNote(id: string): void {
if (!this.syncObject) return;
const notes = (this.syncObject.notes as NoteModel[]).filter(n => n.id !== id);
this.syncObject.batchUpdate({ notes });
}
onNotesChange(callback: (notes: NoteModel[]) => void): void {
this.listeners.push(callback);
}
private notifyListeners(notes: NoteModel[]): void {
this.listeners.forEach(cb => cb(notes));
}
}
2.2 冲突解决策略
三端同时编辑同一笔记时,采用**字段级 LWW(Last Write Wins)**策略:
typescript
// service/ConflictResolver.ets
export class NoteConflictResolver {
static resolve(local: NoteModel, remote: NoteModel): NoteModel {
const merged = { ...local };
// 逐字段比较时间戳,取较新的值
if (remote.updateTime > local.updateTime) {
merged.title = remote.title;
merged.content = remote.content;
merged.tags = remote.tags;
}
// 手写数据和语音备忘独立判断(不同字段可能由不同设备更新)
if (remote.handwritingData && (!local.handwritingData || remote.syncVersion > local.syncVersion)) {
merged.handwritingData = remote.handwritingData;
}
if (remote.voiceMemoUri && (!local.voiceMemoUri || remote.syncVersion > local.syncVersion)) {
merged.voiceMemoUri = remote.voiceMemoUri;
}
merged.updateTime = Math.max(local.updateTime, remote.updateTime);
merged.syncVersion = Date.now();
return merged;
}
}
三、任务接续:从手机速记到平板编辑
3.1 手机端发起接续
typescript
// phone/PhoneNoteAbility.ets
import { UIAbility, Want } from '@ohos.app.ability.UIAbility';
import { distributedMissionManager } from '@ohos.distributedMissionManager';
export default class PhoneNoteAbility extends UIAbility {
private currentNoteId: string = '';
// 用户点击"在平板上继续编辑"
async continueToTablet(): Promise<void> {
const continuationDevices = await distributedMissionManager.getContinuationDevices();
if (continuationDevices.length === 0) {
// 提示用户先组网
return;
}
// 保存当前编辑状态到 want 参数
const want: Want = {
bundleName: 'com.example.crossnote',
abilityName: 'PadNoteAbility',
parameters: {
noteId: this.currentNoteId,
scrollPosition: AppStorage.get('scrollPosition') || 0,
cursorPosition: AppStorage.get('cursorPosition') || 0
}
};
await distributedMissionManager.continueMission({
srcDeviceId: '',
dstDeviceId: continuationDevices[0].deviceId,
missionId: this.context.missionId,
want: want
});
}
onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
// 手机端保存状态
wantParam['noteId'] = this.currentNoteId;
wantParam['lastEditTime'] = Date.now();
return AbilityConstant.OnContinueResult.AGREE;
}
}
3.2 平板端恢复接续
typescript
// pad/PadNoteAbility.ets
import { UIAbility, Want } from '@ohos.app.ability.UIAbility';
export default class PadNoteAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
// 从手机接续过来,恢复编辑状态
const noteId = want.parameters?.['noteId'] as string;
const scrollPosition = want.parameters?.['scrollPosition'] as number || 0;
const cursorPosition = want.parameters?.['cursorPosition'] as number || 0;
AppStorage.setOrCreate('continuedNoteId', noteId);
AppStorage.setOrCreate('continuedScrollPosition', scrollPosition);
AppStorage.setOrCreate('continuedCursorPosition', cursorPosition);
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pad/pages/PadEditor', (err) => {
if (err.code) {
console.error('加载平板编辑页失败:', err);
}
});
}
}
3.3 接续状态恢复 UI
typescript
// pad/pages/PadEditor.ets
@Entry
@Component
struct PadEditorPage {
@State note: NoteModel = new NoteModel();
@State scrollPosition: number = 0;
private syncService: NoteSyncService = new NoteSyncService();
aboutToAppear() {
this.syncService.init();
this.syncService.onNotesChange((notes) => {
const continuedId = AppStorage.get('continuedNoteId') as string;
const found = notes.find(n => n.id === continuedId);
if (found) {
this.note = found;
this.scrollPosition = AppStorage.get('continuedScrollPosition') as number || 0;
}
});
}
build() {
Column() {
Text('从手机接续编辑')
.fontSize(14)
.fontColor('#666')
.visibility(this.note.id ? Visibility.Visible : Visibility.None)
TextInput({ text: $$this.note.title })
.fontSize(24)
.fontWeight(FontWeight.Bold)
RichEditor({ text: $$this.note.content })
.layoutWeight(1)
// 手写批注区域(平板特有)
HandwritingCanvas({
data: this.note.handwritingData,
onChange: (data) => {
this.syncService.updateNote(this.note.id, { handwritingData: data });
}
})
}
.width('100%')
.height('100%')
}
}
四、跨设备拖拽:从手机拖图片到平板
4.1 拖拽发起(手机端)
typescript
// components/DraggableImage.ets
@Component
struct DraggableImage {
@Prop imageUri: string;
@Prop noteId: string;
build() {
Image(this.imageUri)
.width(100)
.height(100)
.borderRadius(8)
.draggable(true)
.onDragStart((event: DragEvent) => {
// 7.0:设置跨设备拖拽数据
event.setData({
uniformDataItems: [
{
uniformDataType: 'image',
uri: this.imageUri,
noteId: this.noteId
}
],
dragProperties: {
crossDevice: true, // 允许跨设备拖拽
supportedDevices: ['tablet'] // 目标设备类型
}
});
})
}
}
4.2 拖拽接收(平板端)
typescript
// pad/components/DropZone.ets
@Component
struct DropZone {
@State droppedImages: string[] = [];
private syncService: NoteSyncService = new NoteSyncService();
build() {
Column() {
Text('拖拽图片到此处')
.fontSize(16)
.fontColor('#999')
ForEach(this.droppedImages, (uri: string) => {
Image(uri).width(120).height(120)
})
}
.width('100%')
.height(200)
.border({ width: 2, color: '#DDD', style: BorderStyle.Dashed })
.backgroundColor('#F9F9F9')
.dropArea({ ... }) // 声明可接收拖拽
.onDrop((event: DragEvent, extraParams: string) => {
const dragData = event.getData();
const imageItem = dragData.uniformDataItems.find(
item => item.uniformDataType === 'image'
);
if (imageItem) {
// 接收跨设备图片
this.droppedImages.push(imageItem.uri);
// 同步到笔记中
const noteId = imageItem.noteId;
this.syncService.updateNote(noteId, {
content: ``
});
}
})
}
}
五、手表端:轻量提醒与语音速记
5.1 手表端 Ability
typescript
// watch/WatchNoteAbility.ets
import { UIAbility } from '@ohos.app.ability.UIAbility';
export default class WatchNoteAbility extends UIAbility {
onCreate(): void {
// 手表端仅显示标题列表和语音速记入口
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('watch/pages/WatchList');
}
}
5.2 手表端列表与语音速记
typescript
// watch/pages/WatchList.ets
@Entry
@Component
struct WatchListPage {
@State notes: NoteModel[] = [];
@State isRecording: boolean = false;
private syncService: NoteSyncService = new NoteSyncService();
aboutToAppear() {
this.syncService.init();
this.syncService.onNotesChange((allNotes) => {
// 手表端仅显示有提醒的笔记和最近 5 条
this.notes = allNotes
.filter(n => n.reminderTime > Date.now() || n.updateTime > Date.now() - 86400000)
.slice(0, 5);
});
}
build() {
Column() {
List() {
ForEach(this.notes, (note: NoteModel) => {
ListItem() {
Column() {
Text(note.title || '无标题')
.fontSize(16)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
if (note.reminderTime > Date.now()) {
Text(this.formatTime(note.reminderTime))
.fontSize(12)
.fontColor('#FF6B6B')
}
}
.width('100%')
.padding(8)
}
})
}
// 语音速记按钮
Button(this.isRecording ? '录音中...' : '语音速记')
.width(120)
.height(120)
.type(ButtonType.Circle)
.backgroundColor(this.isRecording ? '#FF6B6B' : '#4ECDC4')
.onClick(() => this.toggleRecording())
}
.width('100%')
.height('100%')
.padding(8)
}
async toggleRecording(): Promise<void> {
if (this.isRecording) {
// 停止录音,保存
const audioUri = await this.stopRecording();
const newNote = new NoteModel();
newNote.id = `voice_${Date.now()}`;
newNote.voiceMemoUri = audioUri;
newNote.title = '语音速记';
newNote.createTime = Date.now();
this.syncService.addNote(newNote);
this.isRecording = false;
} else {
// 开始录音
await this.startRecording();
this.isRecording = true;
}
}
private formatTime(timestamp: number): string {
const date = new Date(timestamp);
return `${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`;
}
}
六、数据同步时序:一次完整的协同流程
图2:三端协同笔记应用数据同步时序图
图片内容说明(中文):时序图,四个泳道从上到下为"手机端"、"平板端"、"手表端"、"分布式数据层"。流程:①手机端创建笔记→写入UDDL→②UDDL推送变更→平板端/手表端同步收到。③平板端编辑标题→写入UDDL→④手机端/手表端收到更新。⑤手表端语音速记→写入UDDL→⑥手机端/平板端收到新笔记。⑦手机端跨设备拖拽图片到平板→平板端接收并更新UDDL→⑧所有端同步显示图片。各操作旁标注时间戳和syncVersion。
分布式数据层 手表端 平板端 手机端 分布式数据层 手表端 平板端 手机端 #mermaid-svg-BHclOAddLccoZerY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BHclOAddLccoZerY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BHclOAddLccoZerY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BHclOAddLccoZerY .error-icon{fill:#552222;}#mermaid-svg-BHclOAddLccoZerY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BHclOAddLccoZerY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BHclOAddLccoZerY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BHclOAddLccoZerY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BHclOAddLccoZerY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BHclOAddLccoZerY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BHclOAddLccoZerY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BHclOAddLccoZerY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BHclOAddLccoZerY .marker.cross{stroke:#333333;}#mermaid-svg-BHclOAddLccoZerY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BHclOAddLccoZerY p{margin:0;}#mermaid-svg-BHclOAddLccoZerY .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BHclOAddLccoZerY text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-BHclOAddLccoZerY .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BHclOAddLccoZerY .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-BHclOAddLccoZerY .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-BHclOAddLccoZerY .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-BHclOAddLccoZerY #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-BHclOAddLccoZerY .sequenceNumber{fill:white;}#mermaid-svg-BHclOAddLccoZerY #sequencenumber{fill:#333;}#mermaid-svg-BHclOAddLccoZerY #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-BHclOAddLccoZerY .messageText{fill:#333;stroke:none;}#mermaid-svg-BHclOAddLccoZerY .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BHclOAddLccoZerY .labelText,#mermaid-svg-BHclOAddLccoZerY .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-BHclOAddLccoZerY .loopText,#mermaid-svg-BHclOAddLccoZerY .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-BHclOAddLccoZerY .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BHclOAddLccoZerY .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-BHclOAddLccoZerY .noteText,#mermaid-svg-BHclOAddLccoZerY .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-BHclOAddLccoZerY .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BHclOAddLccoZerY .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BHclOAddLccoZerY .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BHclOAddLccoZerY .actorPopupMenu{position:absolute;}#mermaid-svg-BHclOAddLccoZerY .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-BHclOAddLccoZerY .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BHclOAddLccoZerY .actor-man circle,#mermaid-svg-BHclOAddLccoZerY line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-BHclOAddLccoZerY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 收到新笔记 Note-A 标题更新 收到语音笔记 内容同步更新 创建笔记 Note-A syncVersion=1001 推送变更: notes\[\] 推送变更: notes\[\] 编辑标题 syncVersion=1002 推送变更 推送变更 语音速记 创建语音笔记 Note-B syncVersion=1003 推送变更 推送变更 跨设备拖拽图片 更新笔记内容(插入图片) syncVersion=1004 推送变更 推送变更
七、测试验证要点
| 测试场景 | 操作步骤 | 预期结果 |
|---|---|---|
| 实时同步 | 手机创建笔记 | 平板和手表 1 秒内显示新笔记 |
| 字段级冲突 | 手机改标题,平板改内容(同时) | 两端都保留:新标题 + 新内容 |
| 任务接续 | 手机编辑到一半,点击"平板继续" | 平板打开同一笔记,光标位置一致 |
| 跨设备拖拽 | 手机相册图片拖入平板编辑区 | 平板显示图片,笔记内容同步更新 |
| 离线恢复 | 手表断网后语音速记,恢复网络后 | 语音笔记自动同步到手机和平板 |
| 手表提醒 | 设置提醒时间,到达时间点 | 手表震动提醒,显示笔记标题 |
八、结语
跨设备协同不是"把同一个应用装到三个设备上",而是"让三个设备共同完成一个任务的不同片段"。手机适合速记和采集,平板适合深度编辑,手表适合提醒和轻量查看------各自发挥所长,数据实时贯通。
HarmonyOS 7.0 的分布式数据对象、任务接续和跨设备拖拽,为这种"场景化协同"提供了底层能力支撑。开发者不再需要关心设备之间如何组网、如何传输、如何解决冲突,只需关注"用户在每个设备上需要做什么"。
对于高校学生开发者,三端协同项目是一个极具展示价值的毕业设计选题。它既体现了对鸿蒙分布式能力的深入理解,又展现了完整的系统架构设计能力。当你在答辩现场演示"手机拍照→拖拽到平板→平板手写批注→手表提醒复习"的完整链路时,评委看到的不仅是一个 App,更是一个生态。
转载自:https://blog.csdn.net/u014727709/article/details/162933246
欢迎 👍点赞✍评论⭐收藏,欢迎指正