在这里插入图片描述

📖 引言

HarmonyOS 7的多设备协同能力是其核心亮点之一。通过分布式软总线、分布式数据管理和分布式任务调度,应用可以在多个设备之间实现无缝的协同体验。在《趣答》学习应用中,用户可以在手机上开始答题,在平板上继续,在智慧屏上进行大屏学习,实现真正的跨设备学习体验。

本文将深入探讨如何在HarmonyOS 7中实现多设备协同,为《趣答》应用添加跨端体验和交互创新功能。

通过本文,你将掌握:

  • 多设备协同的核心概念
  • 分布式任务调度的实现
  • 跨设备UI流转
  • 多设备协同场景的设计与实现

🎯 学习目标

完成本文后,你将能够:

  • ✅ 理解多设备协同的架构和工作原理
  • ✅ 实现分布式任务调度
  • ✅ 创建跨设备UI流转功能
  • ✅ 设计多设备协同场景
  • ✅ 处理多设备间的通信与数据同步

💡 需求分析

功能模块设计

模块 功能描述 技术要点
设备发现与连接 发现并连接周边设备 分布式软总线、DeviceManager
任务流转 将任务从一个设备流转到另一个设备 分布式任务调度、Ability迁移
跨端交互 在多个设备上协同完成任务 分布式UI、远程控制
数据同步 确保多设备数据一致性 分布式数据管理、实时同步

🛠️ 核心实现

步骤1: 设备发现与连接管理

功能说明

实现设备发现功能,让用户可以查看并连接周边的HarmonyOS设备。

完整代码
// entry/src/main/ets/services/DeviceDiscoveryService.ts
import { deviceManager } from '@ohos.distributedHardware.deviceManager';
import { hilog } from '@ohos.hilog';
import { BusinessError } from '@ohos.base';

const TAG = 'DeviceDiscoveryService';

export interface DeviceInfo {
  deviceId: string;
  deviceName: string;
  deviceType: string;
  isOnline: boolean;
}

export class DeviceDiscoveryService {
  private dm: deviceManager.DeviceManager | null = null;
  private devices: Array<DeviceInfo> = [];
  private listeners: Array<(devices: Array<DeviceInfo>) => void> = [];

  async init(context: any): Promise<void> {
    try {
      this.dm = await deviceManager.createDeviceManager('com.example.quda');
      hilog.info(0x0000, TAG, 'DeviceManager created successfully');

      this.dm.on('deviceFound', (data: deviceManager.DeviceInfo) => {
        this.handleDeviceFound(data);
      });

      this.dm.on('deviceStateChange', (data: deviceManager.DeviceInfo) => {
        this.handleDeviceStateChange(data);
      });

      this.dm.on('deviceServiceChange', (data: deviceManager.DeviceInfo) => {
        this.handleDeviceServiceChange(data);
      });

      await this.startDiscovery();
    } catch (err) {
      hilog.error(0x0000, TAG, 'Failed to initialize device discovery: %{public}s', 
        JSON.stringify(err));
    }
  }

  private async startDiscovery(): Promise<void> {
    if (!this.dm) {
      return;
    }
    try {
      await this.dm.startDeviceDiscovery(0);
      hilog.info(0x0000, TAG, 'Device discovery started');
    } catch (err) {
      hilog.error(0x0000, TAG, 'Failed to start device discovery: %{public}s', 
        JSON.stringify(err));
    }
  }

  private handleDeviceFound(data: deviceManager.DeviceInfo): void {
    const existingIndex = this.devices.findIndex(d => d.deviceId === data.deviceId);
    
    if (existingIndex === -1) {
      const device: DeviceInfo = {
        deviceId: data.deviceId,
        deviceName: data.deviceName,
        deviceType: this.getDeviceTypeName(data.deviceType),
        isOnline: true
      };
      this.devices.push(device);
      this.notifyListeners();
    }
    
    hilog.info(0x0000, TAG, 'Device found: %{public}s', data.deviceName);
  }

  private handleDeviceStateChange(data: deviceManager.DeviceInfo): void {
    const index = this.devices.findIndex(d => d.deviceId === data.deviceId);
    
    if (index !== -1) {
      this.devices[index].isOnline = data.state === deviceManager.DeviceState.ONLINE;
      this.notifyListeners();
    }
    
    hilog.info(0x0000, TAG, 'Device state changed: %{public}s - %{public}s', 
      data.deviceName, data.state);
  }

  private handleDeviceServiceChange(data: deviceManager.DeviceInfo): void {
    hilog.info(0x0000, TAG, 'Device service changed: %{public}s', data.deviceName);
  }

  private getDeviceTypeName(deviceType: number): string {
    const typeMap: Record<number, string> = {
      [deviceManager.DeviceType.TYPE_PHONE]: '手机',
      [deviceManager.DeviceType.TYPE_TABLET]: '平板',
      [deviceManager.DeviceType.TYPE_TV]: '智慧屏',
      [deviceManager.DeviceType.TYPE_WEARABLE]: '穿戴设备',
      [deviceManager.DeviceType.TYPE_CAR]: '车机',
      [deviceManager.DeviceType.TYPE_PC]: '电脑'
    };
    return typeMap[deviceType] || '未知设备';
  }

  getDevices(): Array<DeviceInfo> {
    return this.devices.filter(d => d.isOnline);
  }

  addListener(listener: (devices: Array<DeviceInfo>) => void): void {
    if (!this.listeners.includes(listener)) {
      this.listeners.push(listener);
    }
  }

  removeListener(listener: (devices: Array<DeviceInfo>) => void): void {
    const index = this.listeners.indexOf(listener);
    if (index !== -1) {
      this.listeners.splice(index, 1);
    }
  }

  private notifyListeners(): void {
    this.listeners.forEach(listener => {
      listener([...this.devices]);
    });
  }

  async connectDevice(deviceId: string): Promise<boolean> {
    if (!this.dm) {
      return false;
    }
    try {
      const result = await this.dm.authenticateDevice(deviceId, 1);
      hilog.info(0x0000, TAG, 'Device authentication result: %{public}s', result);
      return result === 0;
    } catch (err) {
      hilog.error(0x0000, TAG, 'Failed to connect device: %{public}s', JSON.stringify(err));
      return false;
    }
  }

  destroy(): void {
    if (this.dm) {
      this.dm.stopDeviceDiscovery();
      this.dm.destroy();
    }
    this.listeners = [];
    this.devices = [];
    hilog.info(0x0000, TAG, 'DeviceDiscoveryService destroyed');
  }
}
代码解析

1. DeviceManager初始化

this.dm = await deviceManager.createDeviceManager('com.example.quda');

原理/说明:

  • DeviceManager是设备管理的入口
  • 需要传入应用的bundleName
  • 所有设备使用相同的bundleName才能互相发现

2. 设备发现监听

this.dm.on('deviceFound', (data) => {
  this.handleDeviceFound(data);
});

this.dm.on('deviceStateChange', (data) => {
  this.handleDeviceStateChange(data);
});

原理/说明:

  • 监听设备发现事件
  • 监听设备状态变化(在线/离线)
  • 实时更新设备列表

3. 设备连接

async connectDevice(deviceId: string): Promise<boolean> {
  const result = await this.dm.authenticateDevice(deviceId, 1);
  return result === 0;
}

原理/说明:

  • 使用authenticateDevice进行设备认证
  • 返回认证结果,0表示成功

步骤2: 分布式任务调度

功能说明

实现分布式任务调度,将任务从一个设备流转到另一个设备。

完整代码
// entry/src/main/ets/services/DistributedTaskService.ts
import { distributedTask } from '@ohos.distributedTask';
import { hilog } from '@ohos.hilog';

const TAG = 'DistributedTaskService';

export interface TaskInfo {
  taskId: string;
  taskType: string;
  params: Record<string, any>;
  deviceId: string;
}

export class DistributedTaskService {
  private taskScheduler: distributedTask.TaskScheduler | null = null;

  async init(): Promise<void> {
    try {
      this.taskScheduler = distributedTask.createTaskScheduler();
      hilog.info(0x0000, TAG, 'TaskScheduler created successfully');
    } catch (err) {
      hilog.error(0x0000, TAG, 'Failed to initialize task scheduler: %{public}s', 
        JSON.stringify(err));
    }
  }

  async startTaskOnDevice(
    deviceId: string,
    taskInfo: TaskInfo
  ): Promise<string> {
    if (!this.taskScheduler) {
      throw new Error('TaskScheduler not initialized');
    }

    try {
      const options: distributedTask.TaskOptions = {
        targetDeviceId: deviceId,
        taskType: taskInfo.taskType,
        params: taskInfo.params,
        timeout: 30000,
        retryCount: 3
      };

      const taskId = await this.taskScheduler.scheduleTask(options);
      hilog.info(0x0000, TAG, 'Task scheduled on device: %{public}s, taskId: %{public}s', 
        deviceId, taskId);
      return taskId;
    } catch (err) {
      hilog.error(0x0000, TAG, 'Failed to schedule task: %{public}s', JSON.stringify(err));
      throw err;
    }
  }

  async transferQuizToDevice(
    deviceId: string,
    questionId: string,
    userId: string,
    currentProgress: number
  ): Promise<string> {
    const taskInfo: TaskInfo = {
      taskId: `quiz_transfer_${Date.now()}`,
      taskType: 'quiz_session',
      params: {
        questionId: questionId,
        userId: userId,
        currentProgress: currentProgress,
        timestamp: Date.now()
      },
      deviceId: deviceId
    };

    return await this.startTaskOnDevice(deviceId, taskInfo);
  }

  async getTaskStatus(taskId: string): Promise<distributedTask.TaskStatus> {
    if (!this.taskScheduler) {
      throw new Error('TaskScheduler not initialized');
    }

    try {
      const status = await this.taskScheduler.getTaskStatus(taskId);
      hilog.info(0x0000, TAG, 'Task status: %{public}s - %{public}s', 
        taskId, status);
      return status;
    } catch (err) {
      hilog.error(0x0000, TAG, 'Failed to get task status: %{public}s', 
        JSON.stringify(err));
      throw err;
    }
  }

  async cancelTask(taskId: string): Promise<void> {
    if (!this.taskScheduler) {
      return;
    }

    try {
      await this.taskScheduler.cancelTask(taskId);
      hilog.info(0x0000, TAG, 'Task cancelled: %{public}s', taskId);
    } catch (err) {
      hilog.error(0x0000, TAG, 'Failed to cancel task: %{public}s', JSON.stringify(err));
    }
  }

  registerTaskHandler(
    taskType: string,
    handler: (params: Record<string, any>) => Promise<any>
  ): void {
    if (!this.taskScheduler) {
      return;
    }

    this.taskScheduler.on(taskType, async (params: Record<string, any>) => {
      hilog.info(0x0000, TAG, 'Task received: %{public}s', taskType);
      try {
        const result = await handler(params);
        hilog.info(0x0000, TAG, 'Task completed: %{public}s', JSON.stringify(result));
        return result;
      } catch (err) {
        hilog.error(0x0000, TAG, 'Task failed: %{public}s', JSON.stringify(err));
        throw err;
      }
    });
  }

  destroy(): void {
    if (this.taskScheduler) {
      this.taskScheduler.destroy();
    }
    hilog.info(0x0000, TAG, 'DistributedTaskService destroyed');
  }
}
代码解析

1. 任务调度

async startTaskOnDevice(deviceId, taskInfo) {
  const options: distributedTask.TaskOptions = {
    targetDeviceId: deviceId,
    taskType: taskInfo.taskType,
    params: taskInfo.params,
    timeout: 30000,
    retryCount: 3
  };
  const taskId = await this.taskScheduler.scheduleTask(options);
  return taskId;
}

原理/说明:

  • 指定目标设备ID
  • 设置任务类型和参数
  • 配置超时时间和重试次数

2. 答题任务流转

async transferQuizToDevice(deviceId, questionId, userId, currentProgress) {
  const taskInfo: TaskInfo = {
    taskId: `quiz_transfer_${Date.now()}`,
    taskType: 'quiz_session',
    params: { questionId, userId, currentProgress, timestamp: Date.now() },
    deviceId
  };
  return await this.startTaskOnDevice(deviceId, taskInfo);
}

原理/说明:

  • 封装答题任务流转逻辑
  • 传递用户ID、题目ID和当前进度
  • 生成唯一的任务ID

3. 任务处理器注册

registerTaskHandler(taskType, handler) {
  this.taskScheduler.on(taskType, async (params) => {
    return await handler(params);
  });
}

原理/说明:

  • 注册任务类型对应的处理器
  • 当收到任务时自动调用处理器
  • 返回处理结果

步骤3: 跨设备UI流转

功能说明

实现跨设备UI流转,让用户可以将当前界面无缝迁移到其他设备。

完整代码
// entry/src/main/ets/services/CrossDeviceUIService.ts
import { distributedUI } from '@ohos.distributedUI';
import { hilog } from '@ohos.hilog';

const TAG = 'CrossDeviceUIService';

export class CrossDeviceUIService {
  private uiTransfer: distributedUI.TransferManager | null = null;

  async init(context: any): Promise<void> {
    try {
      this.uiTransfer = distributedUI.createTransferManager(context);
      hilog.info(0x0000, TAG, 'TransferManager created successfully');
    } catch (err) {
      hilog.error(0x0000, TAG, 'Failed to initialize UI transfer: %{public}s', 
        JSON.stringify(err));
    }
  }

  async transferCurrentPage(deviceId: string): Promise<void> {
    if (!this.uiTransfer) {
      throw new Error('TransferManager not initialized');
    }

    try {
      const options: distributedUI.TransferOptions = {
        targetDeviceId: deviceId,
        preserveState: true,
        animationEnabled: true,
        animationType: distributedUI.AnimationType.SLIDE
      };

      await this.uiTransfer.transfer(options);
      hilog.info(0x0000, TAG, 'UI transferred to device: %{public}s', deviceId);
    } catch (err) {
      hilog.error(0x0000, TAG, 'Failed to transfer UI: %{public}s', JSON.stringify(err));
      throw err;
    }
  }

  async transferWithData(
    deviceId: string,
    pageUrl: string,
    params: Record<string, any>
  ): Promise<void> {
    if (!this.uiTransfer) {
      throw new Error('TransferManager not initialized');
    }

    try {
      const options: distributedUI.TransferOptions = {
        targetDeviceId: deviceId,
        preserveState: false,
        animationEnabled: true,
        pageUrl: pageUrl,
        params: params
      };

      await this.uiTransfer.transfer(options);
      hilog.info(0x0000, TAG, 'UI transferred with data to device: %{public}s', deviceId);
    } catch (err) {
      hilog.error(0x0000, TAG, 'Failed to transfer UI with data: %{public}s', 
        JSON.stringify(err));
      throw err;
    }
  }

  async transferQuizToBigScreen(deviceId: string, quizData: Record<string, any>): Promise<void> {
    await this.transferWithData(deviceId, 'pages/Quiz', {
      questionId: quizData.questionId,
      userId: quizData.userId,
      mode: 'big_screen',
      timestamp: Date.now()
    });
  }

  isTransferSupported(): boolean {
    return this.uiTransfer !== null;
  }

  destroy(): void {
    if (this.uiTransfer) {
      this.uiTransfer.destroy();
    }
    hilog.info(0x0000, TAG, 'CrossDeviceUIService destroyed');
  }
}
代码解析

1. UI流转

async transferCurrentPage(deviceId: string) {
  const options: distributedUI.TransferOptions = {
    targetDeviceId: deviceId,
    preserveState: true,
    animationEnabled: true,
    animationType: distributedUI.AnimationType.SLIDE
  };
  await this.uiTransfer.transfer(options);
}

原理/说明:

  • preserveState: true保留当前页面状态
  • animationEnabled: true启用过渡动画
  • animationType设置动画类型

2. 带数据的UI流转

async transferWithData(deviceId, pageUrl, params) {
  const options: distributedUI.TransferOptions = {
    targetDeviceId: deviceId,
    pageUrl: pageUrl,
    params: params
  };
  await this.uiTransfer.transfer(options);
}

原理/说明:

  • 指定目标页面URL
  • 传递页面参数
  • 在目标设备上打开指定页面

步骤4: 创建多设备协同设置页面

功能说明

创建多设备协同设置页面,让用户可以管理设备连接和任务流转。

完整代码
// entry/src/main/ets/pages/CrossDeviceSettings.ets
import { DeviceDiscoveryService, DeviceInfo } from '../services/DeviceDiscoveryService';
import { CrossDeviceUIService } from '../services/CrossDeviceUIService';
import { State } from '@ohos.arkui.advanced';

@Entry
@Component
struct CrossDeviceSettings {
  @State devices: Array<DeviceInfo> = [];
  @State selectedDevice: string = '';
  @State isTransferring: boolean = false;
  @State transferStatus: string = '';

  private deviceService: DeviceDiscoveryService = new DeviceDiscoveryService();
  private uiService: CrossDeviceUIService = new CrossDeviceUIService();

  build() {
    Column() {
      Text('多设备协同')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor('#333333')
        .margin({ bottom: 20 })
        .padding({ top: 20, left: 20 })

      Text('已发现设备')
        .fontSize(16)
        .fontColor('#666666')
        .margin({ bottom: 12 })
        .padding({ left: 20 })

      if (this.devices.length === 0) {
        Text('暂无发现设备,请确保设备已开启蓝牙和分布式能力')
          .fontSize(14)
          .fontColor('#999999')
          .padding({ left: 20, right: 20 })
      } else {
        Column() {
          ForEach(this.devices, (device: DeviceInfo) => {
            Row() {
              Column() {
                Image($r('app.media.icon'))
                  .width(48)
                  .height(48)
                  .borderRadius(8)
                Text(device.deviceType)
                  .fontSize(12)
                  .fontColor('#999999')
                  .margin({ top: 4 })
              }
              .margin({ right: 16 })

              Column() {
                Text(device.deviceName)
                  .fontSize(16)
                  .fontColor('#333333')
                Text(device.deviceId.slice(0, 8) + '...')
                  .fontSize(12)
                  .fontColor('#999999')
              }
              .flexGrow(1)

              if (this.selectedDevice === device.deviceId) {
                Checkbox()
                  .select(true)
                  .selectedColor('#007DFF')
              } else {
                Checkbox()
                  .select(false)
                  .onChange((value: boolean) => {
                    this.selectedDevice = value ? device.deviceId : '';
                  })
              }
            }
            .width('100%')
            .padding(16)
            .backgroundColor('#FFFFFF')
            .borderRadius(12)
            .margin({ bottom: 8, left: 20, right: 20 })
          })
        }
      }

      Column() {
        Button(this.isTransferring ? '流转中...' : '流转当前页面')
          .width('100%')
          .height(56)
          .borderRadius(12)
          .backgroundColor('#007DFF')
          .fontColor('#FFFFFF')
          .enabled(!this.isTransferring && this.selectedDevice !== '')
          .margin({ bottom: 12 })
          .onClick(() => {
            this.transferPage();
          })

        Button('发送到智慧屏')
          .width('100%')
          .height(56)
          .borderRadius(12)
          .backgroundColor('#FFB74D')
          .fontColor('#FFFFFF')
          .enabled(!this.isTransferring)
          .onClick(() => {
            this.sendToBigScreen();
          })
      }
      .width('100%')
      .padding({ left: 20, right: 20, top: 20 })

      if (this.transferStatus !== '') {
        Text(this.transferStatus)
          .fontSize(14)
          .fontColor(this.transferStatus.includes('成功') ? '#00C853' : '#FF5252')
          .padding({ top: 16 })
          .textAlign(TextAlign.Center)
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .onAppear(() => {
      this.initServices();
    })
    .onDisappear(() => {
      this.deviceService.destroy();
      this.uiService.destroy();
    })
  }

  async initServices() {
    await this.deviceService.init(this.context);
    await this.uiService.init(this.context);

    this.deviceService.addListener((devices: Array<DeviceInfo>) => {
      this.devices = devices.filter(d => d.isOnline);
    });

    this.devices = this.deviceService.getDevices();
  }

  async transferPage() {
    if (!this.selectedDevice) {
      return;
    }

    this.isTransferring = true;
    this.transferStatus = '正在流转...';

    try {
      await this.uiService.transferCurrentPage(this.selectedDevice);
      this.transferStatus = '流转成功';
    } catch (err) {
      this.transferStatus = '流转失败: ' + (err as Error).message;
    } finally {
      this.isTransferring = false;
    }
  }

  async sendToBigScreen() {
    const tvDevice = this.devices.find(d => d.deviceType === '智慧屏');
    
    if (!tvDevice) {
      this.transferStatus = '未发现智慧屏设备';
      return;
    }

    this.isTransferring = true;
    this.transferStatus = '正在发送到智慧屏...';

    try {
      await this.uiService.transferQuizToBigScreen(tvDevice.deviceId, {
        questionId: 'q_demo',
        userId: 'user_001'
      });
      this.transferStatus = '发送成功';
    } catch (err) {
      this.transferStatus = '发送失败: ' + (err as Error).message;
    } finally {
      this.isTransferring = false;
    }
  }
}
代码解析

1. 设备列表展示

ForEach(this.devices, (device: DeviceInfo) => {
  Row() {
    Image($r('app.media.icon')).width(48).height(48)
    Column() {
      Text(device.deviceName).fontSize(16)
      Text(device.deviceId.slice(0, 8) + '...').fontSize(12)
    }
    Checkbox().select(this.selectedDevice === device.deviceId)
  }
})

原理/说明:

  • 展示设备图标、名称和ID
  • 使用Checkbox选择目标设备

2. 页面流转

async transferPage() {
  this.isTransferring = true;
  await this.uiService.transferCurrentPage(this.selectedDevice);
  this.isTransferring = false;
}

原理/说明:

  • 设置流转状态
  • 调用UI流转服务
  • 更新流转结果

步骤5: 创建大屏适配页面

功能说明

创建专门为智慧屏优化的答题页面,支持大屏交互。

完整代码
// entry/src/main/ets/pages/QuizBigScreen.ets
import { State } from '@ohos.arkui.advanced';
import { QuizService } from '../services/QuizService';

@Entry
@Component
struct QuizBigScreen {
  @State currentQuestion: any = null;
  @State selectedAnswer: number = -1;
  @State questionIndex: number = 0;
  @State totalQuestions: number = 5;
  @State score: number = 0;
  @State isFinished: boolean = false;

  private quizService: QuizService = new QuizService();

  build() {
    Column() {
      if (!this.isFinished) {
        this.buildQuizSection();
      } else {
        this.buildResultSection();
      }
    }
    .width('100%')
    .height('100%')
    .padding(40)
    .backgroundColor('#1a1a2e')
    .onAppear(() => {
      this.loadQuestion();
    })
  }

  buildQuizSection() {
    return Column() {
      Row() {
        Text(`${this.questionIndex + 1}/${this.totalQuestions}`)
          .fontSize(24)
          .fontColor('#FFFFFF')
          .margin({ bottom: 20 })
      }

      Text(this.currentQuestion?.question || '')
        .fontSize(36)
        .fontWeight(FontWeight.Bold)
        .fontColor('#FFFFFF')
        .margin({ bottom: 40 })
        .textAlign(TextAlign.Center)
        .maxLines(3)

      Grid() {
        ForEach(this.currentQuestion?.options || [], (option: string, index: number) => {
          GridItem() {
            Button(option)
              .width('100%')
              .height(100)
              .borderRadius(16)
              .backgroundColor(this.selectedAnswer === index ? '#007DFF' : '#16213e')
              .fontColor(this.selectedAnswer === index ? '#FFFFFF' : '#e94560')
              .fontSize(28)
              .fontWeight(FontWeight.Medium)
              .borderWidth(2)
              .borderColor(this.selectedAnswer === index ? '#007DFF' : '#0f3460')
              .onClick(() => {
                this.selectedAnswer = index;
              })
          }
        })
      }
      .width('100%')
      .columnsTemplate('1fr 1fr')
      .rowsGap(20)
      .columnsGap(20)

      Button('确认答案')
        .width(300)
        .height(80)
        .borderRadius(40)
        .backgroundColor(this.selectedAnswer !== -1 ? '#00C853' : '#666666')
        .fontColor('#FFFFFF')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .enabled(this.selectedAnswer !== -1)
        .margin({ top: 40 })
        .onClick(() => {
          this.submitAnswer();
        })
    }
  }

  buildResultSection() {
    return Column() {
      Text('挑战完成!')
        .fontSize(48)
        .fontWeight(FontWeight.Bold)
        .fontColor('#FFFFFF')
        .margin({ bottom: 30 })

      Text(`本次得分: ${this.score}`)
        .fontSize(40)
        .fontColor('#00C853')
        .margin({ bottom: 40 })

      Row() {
        Text(`正确率: ${Math.round((this.score / (this.totalQuestions * 20)) * 100)}%`)
          .fontSize(28)
          .fontColor('#FFB74D')
        Text(`用时: 5分钟`)
          .fontSize(28)
          .fontColor('#FFFFFF')
          .margin({ left: 40 })
      }
      .margin({ bottom: 40 })

      Button('再测一次')
        .width(300)
        .height(80)
        .borderRadius(40)
        .backgroundColor('#007DFF')
        .fontColor('#FFFFFF')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .onClick(() => {
          this.resetQuiz();
        })
    }
  }

  async loadQuestion() {
    this.currentQuestion = await this.quizService.getDailyQuestion(this.questionIndex);
  }

  async submitAnswer() {
    const isCorrect = await this.quizService.checkAnswer(
      this.currentQuestion.id,
      this.selectedAnswer
    );

    if (isCorrect) {
      this.score += 20;
    }

    this.questionIndex++;

    if (this.questionIndex >= this.totalQuestions) {
      this.isFinished = true;
    } else {
      this.currentQuestion = await this.quizService.getDailyQuestion(this.questionIndex);
      this.selectedAnswer = -1;
    }
  }

  resetQuiz() {
    this.questionIndex = 0;
    this.score = 0;
    this.selectedAnswer = -1;
    this.isFinished = false;
    this.loadQuestion();
  }
}
代码解析

1. 大屏布局

Grid() {
  ForEach(this.currentQuestion?.options || [], (option, index) => {
    GridItem() {
      Button(option)
        .width('100%')
        .height(100)
        .fontSize(28)
    }
  })
}
.columnsTemplate('1fr 1fr')
.rowsGap(20)
.columnsGap(20)

原理/说明:

  • 使用Grid布局实现两列显示
  • 增大按钮尺寸和字体大小
  • 适合遥控器操作

2. 深色主题

.backgroundColor('#1a1a2e')
.fontColor('#FFFFFF')

原理/说明:

  • 使用深色背景适合大屏显示
  • 高对比度文字提高可读性

⚠️ 常见问题与解决方案

问题1: 设备无法发现

现象:
设备发现服务启动后,无法发现周边设备。

原因:

  • 设备未登录同一华为账号
  • 蓝牙未开启
  • 设备不在同一网络环境

错误代码:

// ❌ 错误: 未检查设备状态
await this.dm.startDeviceDiscovery(0);

正确代码:

// ✅ 正确: 添加错误处理和状态检查
try {
  await this.dm.startDeviceDiscovery(0);
} catch (err) {
  hilog.error(0x0000, TAG, 'Discovery failed: %{public}s', JSON.stringify(err));
  // 提示用户检查设备设置
}

规则/建议:

  • 确保设备登录同一华为账号
  • 开启蓝牙和Wi-Fi
  • 在同一局域网内测试

问题2: 任务流转失败

现象:
任务调度后,目标设备无法接收到任务。

原因:

  • 设备未连接
  • 权限不足
  • 参数格式错误

错误代码:

// ❌ 错误: 未检查设备连接状态
await this.taskScheduler.scheduleTask(options);

正确代码:

// ✅ 正确: 先检查设备连接
const isConnected = await this.deviceService.connectDevice(deviceId);
if (!isConnected) {
  throw new Error('Device not connected');
}
await this.taskScheduler.scheduleTask(options);

规则/建议:

  • 在流转前检查设备连接状态
  • 确保目标设备上安装了相同应用
  • 验证参数格式

问题3: UI流转动画卡顿

现象:
UI流转时动画不流畅,有明显的卡顿。

原因:

  • 网络延迟
  • 页面状态过大
  • 动画配置不合理

错误代码:

// ❌ 错误: 动画配置不合理
animationType: distributedUI.AnimationType.COMPLEX

正确代码:

// ✅ 正确: 使用简单动画
animationType: distributedUI.AnimationType.SLIDE

规则/建议:

  • 使用简单的过渡动画
  • 减少页面状态数据量
  • 确保网络连接稳定

问题4: 大屏适配问题

现象:
在智慧屏上显示的内容布局错乱。

原因:

  • 没有针对大屏进行布局适配
  • 硬编码尺寸
  • 字体大小不合适

错误代码:

// ❌ 错误: 硬编码尺寸
.width(300)
.height(50)
.fontSize(16)

正确代码:

// ✅ 正确: 使用相对布局
.width('100%')
.height(100)
.fontSize(28)

规则/建议:

  • 使用相对尺寸(百分比)
  • 增大字体和按钮尺寸
  • 使用Grid布局优化大屏显示

问题5: 数据同步延迟

现象:
多设备间数据同步不及时。

原因:

  • 同步频率设置过低
  • 网络不稳定
  • 数据量过大

错误代码:

// ❌ 错误: 同步间隔过长
updateDuration: 1440

正确代码:

// ✅ 正确: 设置合理的同步间隔
updateDuration: 60

规则/建议:

  • 设置合理的同步间隔
  • 使用自动同步机制
  • 优化数据传输格式

📝 本章小结

核心知识点

本文详细讲解了HarmonyOS 7多设备协同的实现,主要包括:

1. 设备发现

  • DeviceManager设备管理
  • 设备发现和状态监听
  • 设备认证与连接

2. 任务调度

  • TaskScheduler任务调度器
  • 任务创建和分发
  • 任务状态管理

3. UI流转

  • TransferManager UI流转管理
  • 页面状态保留
  • 过渡动画

4. 大屏适配

  • Grid布局优化
  • 深色主题设计
  • 大字体和按钮

最佳实践总结

设备发现

dm.on('deviceFound', (data) => { /* 处理设备发现 */ });
dm.on('deviceStateChange', (data) => { /* 处理状态变化 */ });

任务调度

const options = {
  targetDeviceId,
  taskType,
  params,
  timeout: 30000,
  retryCount: 3
};
const taskId = await taskScheduler.scheduleTask(options);

UI流转

const options = {
  targetDeviceId,
  preserveState: true,
  animationEnabled: true
};
await uiTransfer.transfer(options);

大屏适配

Grid()
  .columnsTemplate('1fr 1fr')
  .width('100%')

下一步预告

在下一篇文章中,我们将:

  • 📱 系统能力开放:服务卡片与快捷操作
  • 🎴 创建丰富的服务卡片
  • ⚡ 实现快捷操作入口

🔗 相关链接


💡 提示: 建议结合项目源码阅读,动手实践效果更好!

Logo

一站式 AI 云服务平台

更多推荐