HarmonyOS 分布式开发实战:设备协同、数据共享与跨设备迁移## 一、前言分布式能力是 HarmonyOS 的核心差异化优势。通过分布式技术,应用可以在多设备间无缝流转,实现"一个应用,多端运行"。本文将以 HarmonyOS 5.0.0(API 12)为基础,讲解分布式软总线、分布式数据管理和跨设备迁移的核心技术。## 二、分布式权限配置{
"module": {
"requestPermissions": [
{ "name": "ohos.permission.DISTRIBUTED_DATASYNC" },
{ "name": "ohos.permission.DISTRIBUTED_SOFTBUS_CENTER" },
{ "name": "ohos.permission.ACCESS_SERVICE_DM" }
]
}
}
三、分布式软总线:设备发现### 3.1 设备扫描与发现import { deviceManager } from '@kit.DistributedServiceKit';
@Entry
@Component
struct DeviceDiscovery {
@State devices: deviceManager.DeviceBasicInfo\[\] = \[\];
@State status: string = '搜索设备中...';
@State isScanning: boolean = false;
private dmInstance?: deviceManager.DeviceManager;
private discoverTimeout?: number;
async aboutToAppear() {
await this.initDeviceManager();
await this.startDiscovery();
}
async initDeviceManager() {
try {
this.dmInstance = deviceManager.createDeviceManager('com.example.distributedapp');
this.dmInstance.on('deviceStateChange', (data) => {
console.info(`设备状态变化: ${data.deviceId}, action: ${data.action}`);
if (data.action === deviceManager.DeviceStateChangeAction.ONLINE) {
this.startDiscovery();
} else if (data.action === deviceManager.DeviceStateChangeAction.OFFLINE) {
this.devices = this.devices.filter(d => d.deviceId !== data.deviceId);
}
});
this.dmInstance.on('discoverSuccess', (data) => {
if (data && data.deviceList) {
for (const device of data.deviceList) {
if (!this.devices.find(d => d.deviceId === device.deviceId)) {
this.devices.push(device);
}
}
this.status = `发现 ${this.devices.length} 台设备`;
}
});
} catch (error) {
this.status = `初始化失败: ${JSON.stringify(error)}`;
}
}
async startDiscovery() {
if (!this.dmInstance || this.isScanning) return;
this.isScanning = true;
this.status = '正在扫描设备...';
try {
const filterOptions: deviceManager.DiscoverFilterOptions = {
availableStatus: deviceManager.DeviceAvailableStatus.ALL,
discoverTargetType: deviceManager.DiscoverTargetType.DISCOVER_ALL_DEVICES
};
await this.dmInstance.startDiscovering(filterOptions);
this.discoverTimeout = setTimeout(() => {
this.stopDiscovery();
this.status = this.devices.length > 0 ?
`发现 ${this.devices.length} 台设备` : '未发现设备';
}, 10000);
} catch (error) {
this.status = `扫描失败: ${error}`;
this.isScanning = false;
}
}
stopDiscovery() {
if (this.dmInstance && this.isScanning) {
this.dmInstance.stopDiscovering();
this.isScanning = false;
if (this.discoverTimeout) clearTimeout(this.discoverTimeout);
}
}
getDeviceTypeName(type: number): string {
switch (type) {
case 0x00: return '📱 手机';
case 0x0A: return '📺 智慧屏';
case 0x0B: return '💻 平板';
case 0x0C: return '⌚ 手表';
default: return '📦 设备';
}
}
build() {
Column({ space: 12 }) {
Text('🔗 设备发现').fontSize(24).fontWeight(FontWeight.Bold)
Text(this.status).fontSize(14).fontColor('#888')
Button(this.isScanning ? '扫描中...' : '重新扫描').fontSize(16)
.enabled(!this.isScanning).width('100%')
.onClick(() => { this.startDiscovery() })
Divider()
List({ space: 8 }) {
ForEach(this.devices, (device: deviceManager.DeviceBasicInfo) => {
ListItem() {
Row({ space: 12 }) {
Text(this.getDeviceTypeName(device.deviceType)).fontSize(32)
Column({ space: 4 }) {
Text(device.deviceName || '未知设备').fontSize(16).fontWeight(FontWeight.Medium)
Text(device.deviceId.substring(0, 16) + '...').fontSize(11).fontColor('#AAA').fontFamily('monospace')
}.layoutWeight(1).alignItems(HorizontalAlign.Start)
}.width('100%').padding(12).backgroundColor('#F0F8FF').borderRadius(8)
}
}, (device: deviceManager.DeviceBasicInfo) => device.deviceId)
}.layoutWeight(1).width('100%')
}.width('100%').height('100%').padding(20)
}
aboutToDisappear() { this.stopDiscovery(); this.dmInstance?.release() }
}
四、分布式数据同步### 4.1 分布式数据对象import { distributedDataObject } from '@kit.ArkData';
@Entry
@Component
struct DistributedDataSync {
@State remoteMessage: string = '等待同步...';
@State localMessage: string = '';
@State syncStatus: string = '未连接';
private dataObject?: distributedDataObject.DataObject;
async initDistributedData() {
try {
this.dataObject = distributedDataObject.create(getContext(this), 'collaborative_editor');
this.dataObject.on('change', (
sessionId: string,
changedData: Array<distributedDataObject.ChangedData>
) => {
for (const change of changedData) {
if (change.key === 'message') this.remoteMessage = change.value as string;
if (change.key === 'syncStatus') this.syncStatus = change.value as string;
}
});
this.dataObject.on('status', (
sessionId: string, networkId: string, status: 'online' | 'offline'
) => {
this.syncStatus = status === 'online'
? `设备 ${networkId.slice(0,8)}... 已连接`
: '设备已断开';
});
this.dataObject['message'] = 'Hello Distributed!';
this.dataObject['syncStatus'] = '数据已初始化';
} catch (error) { this.syncStatus = `初始化失败: ${error}` }
}
async updateMessage(message: string) {
if (!this.dataObject) return;
try {
this.dataObject'message' = message;
this.dataObject'syncStatus' = '消息已同步';
this.localMessage = '';
} catch (error) { console.error(同步失败: ${error}) }
}
build() {
Column({ space: 16 }) {
Text('🔄 分布式数据同步').fontSize(24).fontWeight(FontWeight.Bold)
Text(this.syncStatus).fontSize(14).fontColor('#666')
Divider()
Row({ space: 8 }) {
TextInput({ placeholder: '输入消息...' }).layoutWeight(1).height(44)
.onChange((value: string) => { this.localMessage = value })
Button('同步').fontSize(14)
.onClick(() => { if (this.localMessage.trim()) this.updateMessage(this.localMessage.trim()) })
}.width('100%')
Column({ space: 8 }) {
Text('接收到的消息:').fontSize(14).fontColor('#888')
Text(this.remoteMessage).fontSize(18).fontWeight(FontWeight.Medium)
.fontColor('#007AFF').padding(16).backgroundColor('#E3F2FD').borderRadius(8).width('100%')
}.width('100%')
}.width('100%').padding(20)
}
async aboutToAppear() { await this.initDistributedData() }
}
4.2 分布式数据库import { relationalStore } from '@kit.ArkData';
@Component
struct DistributedDatabase {
private rdbStore?: relationalStore.RdbStore;
async initDistributedDB() {
const storeConfig: relationalStore.StoreConfig = {
name: 'distributed_notes.db',
securityLevel: relationalStore.SecurityLevel.S1
};
this.rdbStore = await relationalStore.getRdbStore(getContext(this), storeConfig);
await this.rdbStore.executeSql(`
CREATE TABLE IF NOT EXISTS distributed_notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL, content TEXT,
updated_by TEXT, updated_at TEXT
)
`);
await this.rdbStore.setDistributedTables(['distributed_notes']);
await this.rdbStore.sync(
relationalStore.SyncMode.SYNC_MODE_PUSH_PULL,
relationalStore.SyncPredicates
);
console.info('分布式数据库初始化完成');
}
async collaborativeEdit(id: number, title: string, content: string, deviceName: string) {
if (!this.rdbStore) return;
const predicates = new relationalStore.RdbPredicates('distributed_notes');
predicates.equalTo('id', id);
await this.rdbStore.update({
'title': title, 'content': content,
'updated_by': deviceName, 'updated_at': new Date().toISOString()
}, predicates);
await this.rdbStore.sync(relationalStore.SyncMode.SYNC_MODE_PUSH_PULL, relationalStore.SyncPredicates);
}
}
五、跨设备迁移(Continuation)### 5.1 应用接续import { abilityContinuation } from '@kit.AbilityKit';
@Entry
@Component
struct ContinuationDemo {
@State currentPage: number = 1;
@State editContent: string = '';
// 保存状态用于接续
onContinue(): Object {
return { currentPage: this.currentPage, editContent: this.editContent };
}
// 恢复接续状态
async onRestore(wantParam: Record<string, Object>) {
if (wantParam.currentPage !== undefined) this.currentPage = wantParam.currentPage as number;
if (wantParam.editContent !== undefined) this.editContent = wantParam.editContent as string;
}
build() {
Column({ space: 16 }) {
Text('📱 跨设备迁移示例').fontSize(24).fontWeight(FontWeight.Bold)
Text('输入的内容会在设备切换时保留').fontSize(14).fontColor('#888')
Text(当前页面: ${this.currentPage}).fontSize(18).fontWeight(FontWeight.Medium)
Row({ space: 16 }) {
Button('页面 1').fontSize(14)
.backgroundColor(this.currentPage===1?'#007AFF':'#E0E0E0')
.fontColor(this.currentPage===1?'#FFF':'#333')
.onClick(()=>{this.currentPage=1})
Button('页面 2').fontSize(14)
.backgroundColor(this.currentPage===2?'#007AFF':'#E0E0E0')
.fontColor(this.currentPage===2?'#FFF':'#333')
.onClick(()=>{this.currentPage=2})
}
Divider()
if (this.currentPage === 1) {
Column({ space: 12 }) {
Text('页面 1 --- 笔记编辑').fontSize(20).fontWeight(FontWeight.Bold)
TextArea({ placeholder: '输入笔记内容...', text: this.editContent })
.height(200).width('100%').fontSize(16)
.onChange((value: string) => { this.editContent = value })
Text('换到另一台设备时可以继续编辑这段内容').fontSize(12).fontColor('#AAA')
}.width('100%')
} else {
Column({ space: 12 }) {
Text('页面 2 --- 阅读模式').fontSize(20).fontWeight(FontWeight.Bold)
Text('接续的内容展示:').fontSize(14).fontColor('#888')
Text(this.editContent || '暂无内容').fontSize(16).padding(16)
.backgroundColor('#F5F5F5').borderRadius(8).width('100%')
}.width('100%')
}
}.width('100%').padding(20)
}
}
六、完整实战:分布式画板import { distributedDataObject } from '@kit.ArkData';
interface DrawPoint {
x: number; y: number; color: string; size: number; deviceId: string;
}
@Component
struct DistributedWhiteboard {
@State points: DrawPoint\[\] = \[\];
@State currentColor: string = '#000000';
@State currentSize: number = 4;
@State connectedDevices: string\[\] = \[\];
private dataObject?: distributedDataObject.DataObject;
private deviceId: string = '';
async aboutToAppear() {
this.deviceId = device_${Date.now()};
await this.initDistributedCanvas();
}
async initDistributedCanvas() {
try {
this.dataObject = distributedDataObject.create(getContext(this), 'distributed_whiteboard');
this.dataObject.on('change', (
sessionId: string, changedData: Array<distributedDataObject.ChangedData>
) => {
for (const change of changedData) {
if (change.key === 'points') this.points = JSON.parse(change.value as string);
}
});
this.dataObject.on('status', (
sessionId: string, networkId: string, status: 'online' | 'offline'
) => {
if (status === 'online') {
if (!this.connectedDevices.includes(networkId)) this.connectedDevices.push(networkId);
} else {
this.connectedDevices = this.connectedDevices.filter(d => d !== networkId);
}
});
} catch (error) { console.error(`分布式画板初始化失败: ${error}`) }
}
addPoint(x: number, y: number) {
const point: DrawPoint = { x, y, color: this.currentColor, size: this.currentSize, deviceId: this.deviceId };
this.points.push(point);
if (this.dataObject) this.dataObject'points' = JSON.stringify(this.points);
}
clearCanvas() {
this.points = \[\];
if (this.dataObject) this.dataObject'points' = JSON.stringify(\[\]);
}
build() {
Column({ space: 8 }) {
Row() {
Text('🎨 分布式画板').fontSize(24).fontWeight(FontWeight.Bold)
Blank()
Text(已连接: ${this.connectedDevices.length}).fontSize(12)
.fontColor(this.connectedDevices.length > 0 ? '#4CAF50' : '#888')
}.width('100%')
Stack() {
Canvas(
this.points.length > 0
? (context: DrawingRenderingContext) => {
for (const point of this.points) {
context.fillStyle = point.color;
context.beginPath();
context.arc(point.x, point.y, point.size, 0, 2 * Math.PI);
context.fill();
}
}
: undefined
)
.width('100%').height(350).backgroundColor('#FFFFFF')
.border({ width: 1, color: '#E0E0E0' }).borderRadius(8)
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Down || event.type === TouchType.Move) {
const touch = event.touches[0];
this.addPoint(touch.x, touch.y);
}
})
if (this.points.length === 0) {
Text('在此区域绘制,内容会同步到其他设备').fontSize(14).fontColor('#AAA')
}
}.width('100%').height(350)
Row({ space: 12 }) {
Row({ space: 4 }) {
ForEach(['#000000', '#FF4444', '#4CAF50', '#007AFF', '#FF9800'],
(color: string) => {
Circle().width(24).height(24).fill(color)
.border({ width: this.currentColor === color ? 3 : 0, color: '#333' })
.onClick(() => { this.currentColor = color })
}
)
}.layoutWeight(1)
Button('清除').fontSize(14).backgroundColor('#FF4444')
.onClick(() => { this.clearCanvas() })
}.width('100%')
}.width('100%').height('100%').padding(12)
}
}