在这里插入图片描述
在这里插入图片描述

一、前置思考

分布式任务调度是鸿蒙"超级终端"中让任务在多设备间自由流动的核心能力。你在手机上编辑的文档,一键流转到平板上继续编辑;手机上运行的计算任务,自动迁移到PC上利用强力CPU执行——这些场景背后都是分布式任务调度在工作。

本文聚焦:

  • distributedMissionManager的底层调度机制
  • 跨设备任务迁移的完整生命周期(序列化→传输→恢复)
  • continuationManager的wantAgent工作原理
  • 任务栈序列化与异常恢复方案

真实痛点场景:

  1. 任务迁移失败:用户在手机上点击"流转到平板",平板弹窗但点击后闪退
  2. 状态丢失:迁移到新设备后,页面回到了初始状态而非之前的状态
  3. 负载均衡不智能:明明PC空闲,任务却还在手机上跑
  4. 并发迁移冲突:用户快速多次点击流转,导致状态混乱

二、核心原理

2.1 分布式任务调度架构

┌────────────────────────────────────────────┐
│              应用层                          │
│  onContinue() ← 源端序列化状态               │
│  onRestore()  ← 目标端恢复状态               │
├────────────────────────────────────────────┤
│         任务调度管理层                       │
│  ┌──────────────────────────────────────┐  │
│  │  distributedMissionManager           │  │
│  │  ├── 任务注册/发现                    │  │
│  │  ├── 任务迁移协调                      │  │
│  │  ├── 任务栈管理                       │  │
│  │  └── 负载均衡决策                      │  │
│  └──────────────────────────────────────┘  │
├────────────────────────────────────────────┤
│          continuationManager               │
│  ├── continuationFlag (源/目标标识)         │
│  ├── wantAgent 跳转代理                     │
│  └── wantParams 参数传递                    │
├────────────────────────────────────────────┤
│          软总线 (DSoftBus)                  │
│  ├── 设备发现                              │
│  ├── 安全传输信道                           │
│  └── 序列化数据通道                         │
└────────────────────────────────────────────┘

2.2 任务迁移生命周期

源设备 (手机)                        目标设备 (平板)
    │                                    │
    │ 1. 用户触发"流转"                    │
    │    调用continueAbility()             │
    │                                    │
    │ 2. onContinue(wantParams)          │
    │    序列化页面状态 → wantParams      │
    │                                    │
    │ 3. 将wantParams通过软总线发送 ────→│
    │                                    │ 4. 弹窗提示用户确认
    │                                    │    wantAgent触发
    │                                    │
    │                                    │ 5. onRestore(wantParams)
    │                                    │    反序列化页面状态
    │                                    │
    │                                    │ 6. 渲染目标页面
    │                                    │    (状态已恢复)
    │                                    │
    │ 7. 收到迁移完成确认 ←─────────────│
    │    onStop / onDestroy              │

2.3 关键API详解

continuationManager是流转的核心管理器:

import { continuationManager } from '@kit.AbilityKit';

// 注册流转能力
continuationManager.registerContinuation({
  // 支持的流转模式
  continuationModes: [
    continuationManager.ContinuationMode.COLLABORATION_SINGLE,  // 协作单实例
    continuationManager.ContinuationMode.COLLABORATION_MULTIPLE // 协作多实例
  ],

  // 源端回调:序列化状态
  onContinue: (wantParams: Record<string, Object>) => {
    // 将当前页面状态写入wantParams
    wantParams['scrollPosition'] = this.currentScrollY;
    wantParams['formData'] = this.formDataJson;
    wantParams['timestamp'] = Date.now();
    return true; // 返回true表示可以流转
  },

  // 目标端回调:恢复状态
  onRestore: (wantParams: Record<string, Object>) => {
    // 从wantParams恢复页面状态
    const scrollY: number = wantParams['scrollPosition'] as number;
    const formData: string = wantParams['formData'] as string;
    this.restoreState(scrollY, formData);
  }
});

distributedMissionManager负责任务在多设备间的管理:

import { distributedMissionManager } from '@kit.AbilityKit';

// 获取所有分布式任务
async function getDistributedMissions(): Promise<void> {
  const missions: distributedMissionManager.MissionDeviceInfo[] =
    await distributedMissionManager.getMissionInfos('', 100);

  for (let i: number = 0; i < missions.length; i++) {
    const m: distributedMissionManager.MissionDeviceInfo = missions[i];
    console.info(`[任务] ${m.deviceName} - ${m.bundleName}`);
  }
}

// 迁移指定任务到目标设备
async function migrateMission(missionId: number, targetDeviceId: string): Promise<void> {
  await distributedMissionManager.continueMission({
    srcDeviceId: '',       // 空表示当前设备
    dstDeviceId: targetDeviceId,
    missionId: missionId,
    wantParams: {
      'key': 'value'
    } as Record<string, Object>
  });
}

// 监听任务状态变化
distributedMissionManager.on('missionStateChange', (mission: distributedMissionManager.MissionEvent) => {
  switch (mission.event) {
    case distributedMissionManager.MissionEventType.ACTIVE:
      console.info('任务被激活: ' + mission.missionId);
      break;
    case distributedMissionManager.MissionEventType.INACTIVE:
      console.info('任务挂起: ' + mission.missionId);
      break;
    case distributedMissionManager.MissionEventType.TERMINATED:
      console.info('任务终止: ' + mission.missionId);
      break;
  }
});

2.4 任务栈序列化机制

流转的核心难点是状态序列化。不是所有状态都能直接传输:

// 可序列化的状态类型
type SerializableState = string | number | boolean | null |
  SerializableState[] | { [key: string]: SerializableState };

// 不可序列化的状态(需要特殊处理)
// - 函数/回调 → 目标端重新绑定
// - FileHandle → 传输文件路径而非句柄
// - UI组件引用 → 重新创建组件
// - 网络连接 → 断开后重连
// - 定时器 → 传输剩余时间,目标端重新设置

interface ContinuableState {
  // 安全可序列化的数据
  serializables: Record<string, SerializableState>;

  // 需要特殊处理的对象
  mediaPosition: number;     // 视频/音频播放位置
  scrollPosition: number;    // 滚动位置
  formData: string;          // 表单数据(JSON字符串)
  navigationStack: string;   // 导航栈状态(JSON字符串)
  authToken: string;         // 需要重新验证的令牌
  pendingTimers: TimerState[]; // 待恢复的定时器
}

interface TimerState {
  timerId: string;
  remainingMs: number;  // 剩余时间(ms),不含已过去的时间
  callbackType: string; // 目标端根据此字符串重新绑定回调
}

wantParams的数据量限制:单个wantParams包上限约为200KB,超过此限制需要使用distributedObjectdistributedKVStore传递大数据量。

三、实战:完整流转实现

3.1 源端实现(手机)

@Entry
@ComponentV2
struct DocumentEditor {
  @Local documentContent: string = '';
  @Local scrollY: number = 0;
  @Local cursorPosition: number = 0;

  // 注册流转能力
  aboutToAppear(): void {
    try {
      continuationManager.registerContinuation({
        continuationModes: [
          continuationManager.ContinuationMode.COLLABORATION_SINGLE
        ],
        onContinue: (wantParams: Record<string, Object>): boolean => {
          // 序列化编辑状态
          wantParams['documentContent'] = this.documentContent;
          wantParams['scrollY'] = this.scrollY;
          wantParams['cursorPosition'] = this.cursorPosition;
          wantParams['timestamp'] = Date.now();
          return true;
        }
      });
    } catch (e) {
      console.error('流转注册失败: ' + (e as Error).message);
    }
  }

  // 用户点击流转按钮
  private async startContinuation(): Promise<void> {
    try {
      await continuationManager.startContinuation({
        // 流转参数
        wantParams: {
          'documentContent': this.documentContent,
          'scrollY': this.scrollY,
          'cursorPosition': this.cursorPosition
        } as Record<string, Object>
      });
    } catch (e) {
      console.error('流转失败: ' + (e as Error).message);
    }
  }
}

3.2 目标端实现(平板)

@Entry
@ComponentV2
struct DocumentEditor {
  @Local documentContent: string = '';
  @Local scrollY: number = 0;
  @Local cursorPosition: number = 0;
  @Local isRestored: boolean = false;

  // 恢复流转状态
  onRestore(wantParams: Record<string, Object>): void {
    const content: string = wantParams['documentContent'] as string;
    const sY: number = wantParams['scrollY'] as number;
    const cursor: number = wantParams['cursorPosition'] as number;

    if (content !== undefined) {
      this.documentContent = content;
    }
    if (sY !== undefined) {
      this.scrollY = sY;
    }
    if (cursor !== undefined) {
      this.cursorPosition = cursor;
    }
    this.isRestored = true;

    console.info('[流转恢复] 内容长度:' + this.documentContent.length +
      ', 滚动位置:' + this.scrollY + ', 光标:' + this.cursorPosition);
  }

  build() {
    Column() {
      if (this.isRestored) {
        Text('✅ 从' + '源设备' + '恢复编辑状态')
          .fontSize(12)
          .fontColor('#69F0AE')
          .margin({ top: 8 })
      }
      // 编辑器UI...
    }
  }
}

3.3 负载均衡决策

分布式任务调度内置了简单的负载均衡算法:

// 设备负载信息
interface DeviceLoad {
  deviceId: string;
  cpuUsage: number;       // 0~100
  memoryUsage: number;    // 0~100
  batteryLevel: number;   // 0~100
  isCharging: boolean;
  networkQuality: number; // 0~100
}

// 负载均衡评分
function calculateDeviceScore(load: DeviceLoad): number {
  // 综合评分:CPU越低越好,电池越高越好,网络越好越好
  const cpuScore: number = (100 - load.cpuUsage);
  const memScore: number = (100 - load.memoryUsage);
  const battScore: number = load.isCharging ? 100 : load.batteryLevel;
  const netScore: number = load.networkQuality;

  // 加权综合
  return cpuScore * 0.35 + memScore * 0.25 + battScore * 0.25 + netScore * 0.15;
}

function selectBestDevice(devices: DeviceLoad[]): DeviceLoad | null {
  if (devices.length === 0) return null;

  let bestScore: number = -1;
  let best: DeviceLoad | null = null;

  for (let i: number = 0; i < devices.length; i++) {
    const score: number = calculateDeviceScore(devices[i]);
    if (score > bestScore) {
      bestScore = score;
      best = devices[i];
    }
  }
  return best;
}

四、完整代码架构

Demo中模拟的任务调度架构:

Layer 1: 任务管理
  ├── 任务创建&注册
  ├── 任务状态追踪 (PENDING/RUNNING/SUSPENDED/COMPLETED)
  └── 任务优先级管理

Layer 2: 调度引擎
  ├── 设备负载采集
  ├── 评分算法 (CPU 35% + Memory 25% + Battery 25% + Network 15%)
  └── 最优设备选择

Layer 3: 迁移协调
  ├── wantParams序列化
  ├── 跨设备传输(通过软总线)
  └── 目标端状态恢复

Layer 4: 异常处理
  ├── 迁移超时回滚
  ├── 网络中断重试
  └── 状态一致性校验

五、避坑速查

现象 原因 解决
wantParams过大 迁移失败,无明确错误 数据量超200KB限制 大对象用distributedKVStore传递,wantParams只传key
回调未重新绑定 目标设备按钮点击无反应 函数不可序列化 onRestore中重新注册所有事件监听
定时器状态丢失 流转后倒计时归零 定时器不在序列化范围 onContinue中记录remainingMs,onRestore中重新设置
网络请求中断 流转后页面空白 网络请求状态未序列化 缓存最近一次请求结果,目标端优先展示缓存
并发流转冲突 同一任务被多次流转 用户快速重复点击 加流转锁,isMigrating=true时禁用流转按钮
版本不兼容 新版本流转到旧版本闪退 wantParams key不一致 使用版本号+兼容映射表,旧key向后兼容
权限失效 目标设备操作被拒绝 权限未随任务传递 在wantParams中传递accessToken提示,目标端重新申请
资源句柄泄漏 流转后FileHandle未关闭 源端未正确释放资源 onStop/onDestroy中显式关闭文件、数据库连接
跨设备数据库 数据不同步 SQLite不跨设备 用distributedKVStore代替SQLite存储流转状态
wantParams类型丢失 JSON序列化后number变string wantParams本质是Record<string,Object> onRestore中显式类型转换(value as number)

六、总结

分布式任务调度本质上是一个状态捕获→序列化→传输→恢复的闭环:

  1. onContinue 是状态的"快照"——你要在此刻把所有需要的状态打包
  2. wantParams 是状态的"信封"——200KB限制,超出要用distributedKVStore
  3. onRestore 是状态的"还原"——不仅是数据还原,还包括回调重绑定、定时器重置
  4. 负载均衡 是调度的"大脑"——CPU/内存/电量/网络四维评分

一句话:流转不是传送页面,而是传送状态然后在目标设备重新渲染。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐