前六篇我们一路做下来,功能都有了。但真用起来呢?传到一半App退了怎么办?窗口关了怎么办?URI失效了怎么办?这一篇把这些问题收口。

文章封面

写到这里,CrossDrop已经有了:精准分享、触点识别、区域路由、多窗口、多文件、业务指令、多设备并发。

看起来挺完整。

但我自己测的时候,发现一堆问题:

传到一半,用户直接把App划掉了。
窗口关了,任务还在后台跑。
文件URI过期了,打开失败。
用户碰了两次,同一个任务传了两遍。
切到后台再回来,之前的任务状态全丢了。

这些问题不解决,Demo就是Demo,真用起来全是bug。

这一篇做工程收口。

一、TransferStateMachine:统一状态机

前面我们零散地定义了一些状态。这一篇统一起来。

一个任务从开始到结束,要经历这些状态:

IDLE → RECEIVED → VALIDATING → PROCESSING → COMPLETED

中间任何一步失败,都可能进入:
FAILED → RETRYING

用户取消,进入:
CANCELLED

关键是:状态迁移要合法。不能从COMPLETED再跳回PROCESSING,不能从IDLE直接跳到COMPLETED。

这段代码解决什么问题: 任务状态机。
文件: state/TransferStateMachine.ets
用途: 统一管理任务状态迁移
接入位置: 每个TransferTask持有

enum TransferState {
  IDLE = 'idle',
  RECEIVED = 'received',       // 已接收
  VALIDATING = 'validating',   // 校验中
  PROCESSING = 'processing',   // 处理中
  COMPLETED = 'completed',     // 完成
  FAILED = 'failed',           // 失败
  RETRYING = 'retrying',       // 重试中
  CANCELLED = 'cancelled'      // 已取消
}

class TransferStateMachine {
  private state: TransferState = TransferState.IDLE;
  
  // 合法状态迁移表
  private validTransitions: Record<TransferState, TransferState[]> = {
    [TransferState.IDLE]: [TransferState.RECEIVED, TransferState.CANCELLED],
    [TransferState.RECEIVED]: [TransferState.VALIDATING, TransferState.CANCELLED],
    [TransferState.VALIDATING]: [TransferState.PROCESSING, TransferState.FAILED, TransferState.CANCELLED],
    [TransferState.PROCESSING]: [TransferState.COMPLETED, TransferState.FAILED, TransferState.CANCELLED],
    [TransferState.FAILED]: [TransferState.RETRYING, TransferState.CANCELLED],
    [TransferState.RETRYING]: [TransferState.VALIDATING, TransferState.CANCELLED],
    [TransferState.COMPLETED]: [],
    [TransferState.CANCELLED]: []
  };
  
  canTransitionTo(newState: TransferState): boolean {
    return this.validTransitions[this.state].includes(newState);
  }
  
  transitionTo(newState: TransferState): boolean {
    if (this.canTransitionTo(newState)) {
      this.state = newState;
      return true;
    }
    console.warn('非法状态迁移:' + this.state + ' → ' + newState);
    return false;
  }
  
  getState(): TransferState {
    return this.state;
  }
}

二、任务持久化:App退了也能恢复

最大的问题是:App退了,任务状态全丢了。

解决办法:把任务状态持久化到本地。

每次状态变化,都存到RDB或Preferences里。App重新启动时,读出来,恢复未完成的任务。

这段代码解决什么问题: 任务持久化。
文件: persist/TaskRepository.ets
用途: 任务状态本地持久化
接入位置: 任务状态变化时调用

class TaskRepository {
  // 保存任务状态
  async saveTask(task: TransferTask) {
    let store = await relationalStore.getRdbStore(...);
    await store.executeSql(
      'INSERT OR REPLACE INTO tasks (id, uri, state, progress, retry_count) VALUES (?, ?, ?, ?, ?)',
      [task.id, task.uri, task.stateMachine.getState(), task.progress, task.retryCount]
    );
  }
  
  // 加载所有未完成任务
  async loadPendingTasks(): Promise<TransferTask[]> {
    let store = await relationalStore.getRdbStore(...);
    let result = await store.querySql('SELECT * FROM tasks WHERE state NOT IN ("completed", "cancelled")');
    let tasks: TransferTask[] = [];
    while (result.goToNextRow()) {
      let task = new TransferTask(result.getString(0), result.getString(1));
      task.progress = result.getDouble(3);
      task.retryCount = result.getLong(4);
      tasks.push(task);
    }
    return tasks;
  }
  
  // App重启后恢复
  async restoreOnStartup() {
    let pending = await this.loadPendingTasks();
    console.info('恢复未完成任务:' + pending.length + '个');
    for (let task of pending) {
      // 重新加入队列继续处理
      taskQueue.addTask(task);
    }
  }
}

三、资源释放:该清的都清掉

最后一个问题:资源泄漏。

Listener没注销、Session没关闭、Buffer没释放——这些都会慢慢耗内存。

App退出、窗口关闭、任务完成时,都要清理:

  • 注销所有状态监听
  • 关闭所有DeviceSession
  • 释放未使用的Buffer
  • 取消还在跑的异步任务

四、整个连载回顾

七篇下来,我们做了一条完整链路:

01 能传——搭好CrossDrop工程骨架,实现第一条完整链路。
02 知道碰哪里——屏幕坐标到应用坐标的转换。
03 知道碰哪个窗口——多窗口识别与路由。
04 能传很多——多文件任务队列,并发控制。
05 能传业务指令——传输层和业务层解耦。
06 能同时传多设备——每台设备独立Session。
07 能稳定运行——状态机、持久化、资源释放。

从"碰一下传文件"到"碰哪传哪、多窗口、多文件、多设备、稳定可靠",这就是完整的跨设备协作工作台。

协同工作台

任务恢复效果


最终总结:

做跨设备分享,看起来就是"传个文件"这么简单。真做下来,从坐标转换、窗口识别、任务队列、业务协议、多设备并发,到最后状态机和持久化,每一层都有工程问题。

这也是做系统级能力的乐趣:不是调个API就完了,而是要把传输、路由、状态、资源整个链路想清楚。

Logo

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

更多推荐