系列文章《鸿蒙开发从入门到专家》第 9 篇(高级篇)
前言
性能优化搞定了,应用在自己手机上跑得飞快。但鸿蒙真正的杀手锏不是单设备体验,而是"万物互联"。
想象一下这些场景:手机上看到一半的视频,随手一划流转到平板继续看;手机上编辑的文档,靠近电脑就自动同步过去;跑步时用手机听歌,回家切到音箱无缝衔接。这不是科幻,是鸿蒙分布式能力的日常。
分布式开发是鸿蒙区别于 Android 和 iOS 的标志性能力。本文从技术实现角度,讲解跨设备迁移和数据流转的完整开发流程。
一、分布式能力的技术底座
1.1 分布式软总线
鸿蒙设备之间通过"分布式软总线"自动发现、自动组网,开发者无需关心底层网络协议。只要设备登录同一华为账号、在同一局域网或开启蓝牙,它们就会自动识别彼此。
手机 ── WiFi/蓝牙 ── 平板
│ │
└── 分布式软总线 ────┘
│
智慧屏
1.2 三种分布式场景
二、跨设备迁移开发
2.1 启用迁移能力
首先在 module.json5 中声明分布式能力:
{
"module": {
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"continueType": [
{
"keyword": "video_playback"
}
]
}
]
}
}
continueType 用于标识迁移类型,系统会根据这个类型匹配可以接收迁移的设备。
2.2 保存与恢复迁移数据
// entryability/EntryAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'
import { window } from '@kit.ArkUI'
export default class EntryAbility extends UIAbility {
// 页面当前状态
private currentVideoId: string = ''
private currentProgress: number = 0
// 应用需要迁移时触发:保存当前状态
onContinue(wantParam: Record<string, object>): AbilityConstant.OnContinueResult {
console.info('准备迁移到另一台设备')
// 将要迁移的数据放入 wantParam
wantParam['videoId'] = this.currentVideoId
wantParam['progress'] = this.currentProgress
wantParam['timestamp'] = Date.now()
return AbilityConstant.OnContinueResult.AGREEMENT
}
// 应用在目标设备上恢复时触发
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 判断是否是迁移启动
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
let continueParams = want.parameters as Record<string, object>
this.currentVideoId = continueParams['videoId'] as string
this.currentProgress = continueParams['progress'] as number
console.info(`从其他设备迁移过来,恢复视频: ${this.currentVideoId}, 进度: ${this.currentProgress}`)
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err) => {
if (!err) {
// 通知页面恢复状态
AppStorage.setOrCreate('restoreVideoId', this.currentVideoId)
AppStorage.setOrCreate('restoreProgress', this.currentProgress)
}
})
}
}
2.3 页面中触发迁移
// pages/VideoPlayerPage.ets
import { continuationManager } from '@kit.AbilityKit'
@Entry
@Component
struct VideoPlayerPage {
@State videoId: string = ''
@State progress: number = 0
@State isPlaying: boolean = false
// 触发迁移到附近设备
async continueToOtherDevice() {
try {
// 获取可迁移的设备列表
let devices = await continuationManager.getContinuationDevices()
if (devices.length === 0) {
promptAction.showToast({ message: '附近没有可迁移的设备' })
return
}
// 选择第一个可用设备(实际应用应展示设备选择弹窗)
let targetDevice = devices[0]
// 发起迁移
await continuationManager.startContinuationDeviceManager(
targetDevice.deviceId,
{
videoId: this.videoId,
progress: this.progress
}
)
// 迁移成功后,本设备可以关闭或暂停
this.isPlaying = false
promptAction.showToast({ message: '已迁移到 ' + targetDevice.deviceName })
} catch (err) {
console.error('迁移失败:', err.message)
promptAction.showToast({ message: '迁移失败' })
}
}
build() {
Column() {
// 视频播放器组件
VideoPlayer({
videoId: this.videoId,
progress: this.progress,
isPlaying: this.isPlaying
})
Row({ space: 16 }) {
Button(this.isPlaying ? '暂停' : '播放')
.onClick(() => this.isPlaying = !this.isPlaying)
Button('迁移到平板')
.onClick(() => this.continueToOtherDevice())
}
}
}
}
2.4 API 26 迁移能力增强
HarmonyOS NEXT 5.0 对迁移能力做了多项增强:
同应用跨 Ability 迁移:不再局限于同一个 EntryAbility,可以迁移到应用内的其他 Ability
异步保存数据:
onContinue支持异步接口,可以保存更大量的数据立即启动字段:应用可配置
continueInstantly,在 pull 场景下提前启动目标应用
// 异步保存大量数据
async onContinue(wantParam: Record<string, object>): Promise<AbilityConstant.OnContinueResult> {
// 异步保存用户编辑内容
let content = await this.loadDocumentContent()
wantParam['documentContent'] = content
wantParam['documentVersion'] = this.version
return AbilityConstant.OnContinueResult.AGREEMENT
}
三、跨设备数据流转
迁移是"整个应用搬过去",而数据流转是"数据在多个设备间同步"。后者的应用场景更广泛。
3.1 分布式 KVStore 实现剪贴板同步
import { distributedKVStore } from '@kit.ArkData'
class CrossDeviceClipboard {
private kvStore: distributedKVStore.SingleKVStore | null = null
async init(): Promise<void> {
let context = getContext()
let kvManager = distributedKVStore.createKVManager({
context: context,
bundleName: context.applicationInfo.name
})
this.kvStore = await kvManager.getKVStore('clipboard_sync', {
createIfMissing: true,
encrypt: true, // 加密传输
autoSync: true, // 自动同步
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S2
})
// 监听远程数据变化
this.kvStore.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_REMOTE, (entries) => {
for (let entry of entries) {
if (entry.key === 'clipboard_content') {
let content = entry.value.value as string
console.info('收到其他设备的剪贴板内容:', content)
// 写入本机剪贴板
this.setLocalClipboard(content)
}
}
})
}
async syncClipboard(content: string): Promise<void> {
await this.kvStore?.put('clipboard_content', content)
await this.kvStore?.put('clipboard_timestamp', Date.now())
}
private setLocalClipboard(content: string): void {
// 调用系统剪贴板 API
// ...
}
}
3.2 分布式关系型数据库实现待办同步
import { relationalStore } from '@kit.ArkData'
class DistributedTodoDB {
private rdbStore: relationalStore.RdbStore | null = null
async init(): Promise<void> {
let context = getContext()
const config: relationalStore.StoreConfig = {
name: 'distributed_todo.db',
securityLevel: relationalStore.SecurityLevel.S2,
customDir: 'distributed' // 关键:启用分布式目录
}
this.rdbStore = await relationalStore.getRdbStore(context, config)
await this.rdbStore.executeSql(`
CREATE TABLE IF NOT EXISTS todos (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
completed INTEGER DEFAULT 0,
create_time INTEGER,
update_time INTEGER,
device_id TEXT
)
`)
// 订阅数据同步事件
this.rdbStore.on('dataChange', (event) => {
console.info('数据已同步,变更类型:', event)
// 通知 UI 刷新
AppStorage.setOrCreate('todoSyncTrigger', Date.now())
})
}
async addTodo(title: string): Promise<void> {
let uuid = generateUUID()
let values = {
id: uuid,
title: title,
completed: 0,
create_time: Date.now(),
update_time: Date.now(),
device_id: getDeviceId()
}
await this.rdbStore?.insert('todos', values)
// insert 后会自动触发分布式同步
}
async toggleTodo(id: string, completed: boolean): Promise<void> {
let values = {
completed: completed ? 1 : 0,
update_time: Date.now()
}
let predicates = new relationalStore.RdbPredicates('todos')
predicates.equalTo('id', id)
await this.rdbStore?.update(values, predicates)
}
async queryAllTodos(): Promise<TodoItem[]> {
let predicates = new relationalStore.RdbPredicates('todos')
predicates.orderByDesc('update_time')
let resultSet = await this.rdbStore?.query(predicates)
let todos: TodoItem[] = []
while (resultSet?.goToNextRow()) {
todos.push({
id: resultSet.getString(resultSet.getColumnIndex('id')),
title: resultSet.getString(resultSet.getColumnIndex('title')),
completed: resultSet.getLong(resultSet.getColumnIndex('completed')) === 1,
updateTime: resultSet.getLong(resultSet.getColumnIndex('update_time'))
})
}
resultSet?.close()
return todos
}
}
3.3 冲突解决策略
分布式数据同步难免遇到冲突——同一时刻两台设备修改了同一条数据。鸿蒙默认采用"后写入获胜"(Last-Write-Wins)策略,但复杂场景需要自定义:
// 自定义冲突解决
this.kvStore?.on('syncComplete', (results) => {
for (let result of results) {
if (result.errorMessage) {
console.error('同步出错:', result.errorMessage)
continue
}
// 检查是否有冲突
if (result.conflictCount > 0) {
this.resolveConflicts(result.storeId)
}
}
})
private async resolveConflicts(storeId: string): Promise<void> {
// 读取冲突数据
let conflicts = await this.kvStore?.getConflictList(storeId)
for (let conflict of conflicts || []) {
let localValue = conflict.localValue
let remoteValue = conflict.remoteValue
// 自定义合并策略:取 update_time 更大的
if (localValue.update_time > remoteValue.update_time) {
await this.kvStore?.put(conflict.key, localValue)
} else {
await this.kvStore?.put(conflict.key, remoteValue)
}
}
}
四、设备发现与选择 UI
让用户手动选择目标设备是更好的体验:
@CustomDialog
struct DevicePickerDialog {
@State devices: continuationManager.ContinuationDevice[] = []
private controller: CustomDialogController
private onDeviceSelected?: (device: continuationManager.ContinuationDevice) => void
aboutToAppear() {
this.loadDevices()
}
async loadDevices() {
try {
this.devices = await continuationManager.getContinuationDevices()
} catch (err) {
console.error('获取设备列表失败:', err)
}
}
build() {
Column({ space: 16 }) {
Text('选择设备')
.fontSize(18)
.fontWeight(FontWeight.Bold)
if (this.devices.length === 0) {
Text('附近没有可连接的设备')
.fontSize(14)
.fontColor('#999999')
} else {
List() {
ForEach(this.devices, (device) => {
ListItem() {
Row({ space: 12 }) {
Image(device.deviceIcon || $r('app.media.icon_device'))
.width(40)
.height(40)
Column({ space: 4 }) {
Text(device.deviceName)
.fontSize(16)
Text(device.deviceType)
.fontSize(12)
.fontColor('#999999')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
}
.width('100%')
.padding(12)
.onClick(() => {
if (this.onDeviceSelected) {
this.onDeviceSelected(device)
}
this.controller.close()
})
}
})
}
.width('100%')
.maxHeight(300)
}
}
.padding(24)
.backgroundColor(Color.White)
.borderRadius(16)
}
}
五、权限与安全
分布式能力需要额外的权限声明:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
},
{
"name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE"
}
]
}
}
安全注意事项:
敏感数据务必开启加密(
encrypt: true)分布式数据只在同一华为账号的设备间同步
涉及隐私的数据(如位置、通讯录)需用户二次确认
遵守各地区数据合规要求(GDPR、个人信息保护法等)
小结与下篇预告
本文覆盖了鸿蒙分布式开发的两个核心场景:
跨设备迁移:通过
onContinue保存状态,目标设备onCreate中恢复,适合视频播放、文档编辑等场景数据流转:通过分布式 KVStore 或 RDB 实现数据自动同步,适合待办、笔记、剪贴板等场景
分布式开发的核心心智模型是:设备之间是"自动发现、自动组网"的,开发者只需要关注"什么数据要同步"和"冲突怎么解决"。
下一篇(第 10 篇)是系列最后一篇,进入专家级内容:大型鸿蒙应用的架构设计。我们会讨论模块化拆分(HAP/HSP/HAR)、状态管理分层、MVVM 模式落地,以及工程化最佳实践。