Harmony開發之跨設備調用——遠程啓動Ability

引入:打破設備邊界的應用能力

在傳統的移動應用開發中,每個應用都運行在獨立的設備上,設備間的應用能力無法共享。而HarmonyOS的跨設備調用能力,讓應用可以像調用本地Ability一樣,無縫調用其他設備上的Ability,真正實現了"一次開發,多端部署,跨端流轉"的分佈式體驗。

想象一下這樣的場景:你在手機上瀏覽一篇長文,走到客廳後可以繼續在智慧屏上閲讀;在平板上編輯文檔時,可以直接調用手機上的攝像頭拍照插入圖片。這些場景的實現,都依賴於HarmonyOS的跨設備調用能力。

一、跨設備調用核心概念

1.1 什麼是跨設備調用

跨設備調用(Cross-Device Call)是指一個設備上的應用能夠發現、連接並調用另一個設備上的Ability,就像調用本地Ability一樣簡單。這種能力基於HarmonyOS的分佈式軟總線技術,實現了設備間的能力共享和協同。

1.2 核心組件

Want:跨設備調用的核心載體,封裝了目標Ability的信息和調用意圖。

WantAgent:Want的封裝器,用於延遲執行Want,常用於通知、快捷方式等場景。

AbilityManager:提供跨設備調用的管理接口,包括啓動、連接、斷開等操作。

1.3 調用模式對比

調用模式

特點

適用場景

隱式啓動

通過action匹配目標Ability,系統自動選擇

通用功能調用(如拍照、分享)

顯式啓動

明確指定目標Ability的bundleName和abilityName

特定應用間的協同

帶返回值啓動

啓動Ability並等待返回結果

需要獲取返回數據的場景

連接模式

建立長連接,持續通信

跨設備數據同步、遠程控制

二、Want與WantAgent詳解

2.1 Want基本結構

Want是跨設備調用的核心數據結構,包含以下關鍵信息:

interface Want {
  deviceId?: string;           // 目標設備ID,空表示本地設備
  bundleName?: string;         // 目標應用包名
  abilityName?: string;        // 目標Ability名稱
  action?: string;             // 動作標識
  entities?: string[];         // 實體類型
  uri?: string;                // URI數據
  type?: string;               // MIME類型
  parameters?: { [key: string]: any }; // 參數數據
}

2.2 WantAgent工作流程

WantAgent是對Want的封裝,支持延遲執行和權限控制:

// WantAgent創建流程
const wantAgentInfo: WantAgent.WantAgentInfo = {
  wants: [want],               // 要執行的Want數組
  operationType: WantAgent.OperationType.START_ABILITIES, // 操作類型
  requestCode: 0,              // 請求碼,用於回調識別
  wantAgentFlags: [WantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG] // 標誌位
};

三、跨設備調用實戰開發

3.1 權限配置

module.json5中配置跨設備調用所需權限:

{
  "reqPermissions": [
    {
      "name": "ohos.permission.DISTRIBUTED_DATASYNC",
      "reason": "跨設備數據同步"
    },
    {
      "name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE",
      "reason": "監聽設備狀態變化"
    },
    {
      "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO",
      "reason": "獲取分佈式設備信息"
    }
  ]
}

3.2 隱式啓動遠程Ability

通過action匹配方式啓動遠程Ability:

import { AbilityConstant, common, UIAbility } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

async function startRemoteAbilityByAction(): Promise<void> {
  try {
    const want = {
      action: 'action.system.open', // 系統打開動作
      entities: ['entity.system.browser'], // 瀏覽器實體
      uri: 'https://www.example.com', // 要打開的URL
      type: 'text/html'
    };

    const context = getContext(this) as common.UIAbilityContext;
    await context.startAbility(want);
    hilog.info(0x0000, 'testTag', 'Remote ability started successfully');
  } catch (error) {
    hilog.error(0x0000, 'testTag', `Failed to start remote ability: ${JSON.stringify(error)}`);
  }
}

3.3 顯式啓動遠程Ability

明確指定目標Ability的詳細信息:

import { AbilityConstant, common, UIAbility } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

async function startRemoteAbilityExplicitly(): Promise<void> {
  try {
    const want = {
      deviceId: '1234567890', // 目標設備ID
      bundleName: 'com.example.remoteapp',
      abilityName: 'RemoteAbility',
      parameters: {
        key1: 'value1',
        key2: 100
      }
    };

    const context = getContext(this) as common.UIAbilityContext;
    await context.startAbility(want);
    hilog.info(0x0000, 'testTag', 'Remote ability started successfully');
  } catch (error) {
    hilog.error(0x0000, 'testTag', `Failed to start remote ability: ${JSON.stringify(error)}`);
  }
}

3.4 帶返回值啓動遠程Ability

啓動遠程Ability並等待返回結果:

import { AbilityConstant, common, UIAbility } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

async function startRemoteAbilityForResult(): Promise<void> {
  try {
    const want = {
      deviceId: '1234567890',
      bundleName: 'com.example.remoteapp',
      abilityName: 'RemoteAbility',
      parameters: {
        requestData: 'some data'
      }
    };

    const context = getContext(this) as common.UIAbilityContext;
    await context.startAbilityForResult(want, (error: BusinessError, data: AbilityConstant.AbilityResult) => {
      if (error) {
        hilog.error(0x0000, 'testTag', `Ability result error: ${JSON.stringify(error)}`);
        return;
      }
      
      if (data.resultCode === AbilityConstant.ResultCode.RESULT_OK) {
        const resultData = data.want?.parameters;
        hilog.info(0x0000, 'testTag', `Ability result: ${JSON.stringify(resultData)}`);
      } else {
        hilog.warn(0x0000, 'testTag', `Ability result canceled: ${data.resultCode}`);
      }
    });
  } catch (error) {
    hilog.error(0x0000, 'testTag', `Failed to start remote ability: ${JSON.stringify(error)}`);
  }
}

3.5 連接遠程Ability

建立與遠程Ability的長連接:

import { AbilityConstant, common, UIAbility } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

class RemoteConnection implements common.RemoteAbilityConnection {
  private connectionId: number = -1;
  private remoteProxy: common.RemoteProxy | null = null;

  // 連接建立回調
  onAbilityConnectDone(elementName: common.ElementName, remote: common.RemoteProxy, resultCode: number): void {
    if (resultCode === 0) {
      this.connectionId = elementName.abilityName.length;
      this.remoteProxy = remote;
      hilog.info(0x0000, 'testTag', 'Remote ability connected successfully');
      
      // 發送數據到遠程Ability
      this.sendDataToRemote();
    } else {
      hilog.error(0x0000, 'testTag', `Failed to connect remote ability: ${resultCode}`);
    }
  }

  // 連接斷開回調
  onAbilityDisconnectDone(elementName: common.ElementName, resultCode: number): void {
    hilog.info(0x0000, 'testTag', 'Remote ability disconnected');
    this.connectionId = -1;
    this.remoteProxy = null;
  }

  // 發送數據到遠程Ability
  async sendDataToRemote(): Promise<void> {
    if (!this.remoteProxy) {
      return;
    }

    try {
      const data = {
        message: 'Hello from local device',
        timestamp: Date.now()
      };
      
      await this.remoteProxy.sendRequest(1, data, {});
      hilog.info(0x0000, 'testTag', 'Data sent to remote ability');
    } catch (error) {
      hilog.error(0x0000, 'testTag', `Failed to send data: ${JSON.stringify(error)}`);
    }
  }

  // 建立連接
  async connectRemoteAbility(): Promise<void> {
    try {
      const want = {
        deviceId: '1234567890',
        bundleName: 'com.example.remoteapp',
        abilityName: 'RemoteServiceAbility'
      };

      const context = getContext(this) as common.UIAbilityContext;
      await context.connectAbility(want, this);
      hilog.info(0x0000, 'testTag', 'Connecting to remote ability...');
    } catch (error) {
      hilog.error(0x0000, 'testTag', `Failed to connect remote ability: ${JSON.stringify(error)}`);
    }
  }

  // 斷開連接
  async disconnectRemoteAbility(): Promise<void> {
    if (this.connectionId === -1) {
      return;
    }

    try {
      const context = getContext(this) as common.UIAbilityContext;
      await context.disconnectAbility(this);
      hilog.info(0x0000, 'testTag', 'Disconnecting from remote ability...');
    } catch (error) {
      hilog.error(0x0000, 'testTag', `Failed to disconnect: ${JSON.stringify(error)}`);
    }
  }
}

// 使用示例
const remoteConnection = new RemoteConnection();
await remoteConnection.connectRemoteAbility();

四、遠程Ability的實現

4.1 Service Ability實現

遠程Service Ability需要實現IAbilityConnection接口:

import { AbilityConstant, common, UIAbility } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

export default class RemoteServiceAbility extends UIAbility {
  private connection: common.RemoteAbilityConnection | null = null;

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', 'RemoteServiceAbility onCreate');
    
    // 註冊連接回調
    this.connection = {
      onAbilityConnectDone: this.onConnectDone.bind(this),
      onAbilityDisconnectDone: this.onDisconnectDone.bind(this)
    };
  }

  onConnectDone(elementName: common.ElementName, remote: common.RemoteProxy, resultCode: number): void {
    if (resultCode === 0) {
      hilog.info(0x0000, 'testTag', 'Remote client connected');
      
      // 設置消息處理回調
      remote.setReceiveRequestCallback(this.handleRequest.bind(this));
    }
  }

  onDisconnectDone(elementName: common.ElementName, resultCode: number): void {
    hilog.info(0x0000, 'testTag', 'Remote client disconnected');
  }

  // 處理來自客户端的請求
  handleRequest(code: number, data: any, reply: common.MessageParcel, option: common.MessageOption): void {
    hilog.info(0x0000, 'testTag', `Received request code: ${code}, data: ${JSON.stringify(data)}`);
    
    switch (code) {
      case 1: // 處理消息請求
        this.handleMessageRequest(data, reply);
        break;
      case 2: // 處理數據請求
        this.handleDataRequest(data, reply);
        break;
      default:
        hilog.warn(0x0000, 'testTag', `Unknown request code: ${code}`);
        reply.writeInt(AbilityConstant.ResultCode.RESULT_CANCELED);
    }
  }

  // 處理消息請求
  private handleMessageRequest(data: any, reply: common.MessageParcel): void {
    const message = data.message;
    hilog.info(0x0000, 'testTag', `Received message: ${message}`);
    
    // 處理消息並返回結果
    const result = {
      status: 'success',
      receivedAt: Date.now(),
      processedMessage: message.toUpperCase()
    };
    
    reply.writeInt(AbilityConstant.ResultCode.RESULT_OK);
    reply.writeString(JSON.stringify(result));
  }

  // 處理數據請求
  private handleDataRequest(data: any, reply: common.MessageParcel): void {
    const requestData = data.requestData;
    hilog.info(0x0000, 'testTag', `Processing data: ${JSON.stringify(requestData)}`);
    
    // 模擬數據處理
    const processedData = this.processData(requestData);
    
    reply.writeInt(AbilityConstant.ResultCode.RESULT_OK);
    reply.writeString(JSON.stringify(processedData));
  }

  // 數據處理邏輯
  private processData(data: any): any {
    return {
      original: data,
      processed: true,
      timestamp: Date.now(),
      result: 'Data processed successfully'
    };
  }
}

4.2 Page Ability實現

遠程Page Ability需要處理啓動參數和返回結果:

import { AbilityConstant, common, UIAbility } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

export default class RemotePageAbility extends UIAbility {
  private result: AbilityConstant.AbilityResult = {
    resultCode: AbilityConstant.ResultCode.RESULT_CANCELED,
    want: undefined
  };

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', 'RemotePageAbility onCreate');
    
    // 處理啓動參數
    const parameters = want.parameters;
    if (parameters) {
      hilog.info(0x0000, 'testTag', `Launch parameters: ${JSON.stringify(parameters)}`);
    }
  }

  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', 'RemotePageAbility onNewWant');
  }

  onDestroy(): void {
    hilog.info(0x0000, 'testTag', 'RemotePageAbility onDestroy');
  }

  // 設置返回結果
  setResult(resultCode: number, resultData?: any): void {
    this.result.resultCode = resultCode;
    if (resultData) {
      this.result.want = {
        parameters: resultData
      };
    }
  }

  // 返回結果給調用方
  terminateSelfWithResult(): void {
    const context = getContext(this) as common.UIAbilityContext;
    context.terminateSelfWithResult(this.result, (error: BusinessError) => {
      if (error) {
        hilog.error(0x0000, 'testTag', `Failed to terminate with result: ${JSON.stringify(error)}`);
      }
    });
  }
}

五、WantAgent高級用法

5.1 創建WantAgent

import { WantAgent } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

async function createWantAgent(): Promise<WantAgent.WantAgent> {
  try {
    const want = {
      action: 'action.system.open',
      entities: ['entity.system.browser'],
      uri: 'https://www.example.com'
    };

    const wantAgentInfo: WantAgent.WantAgentInfo = {
      wants: [want],
      operationType: WantAgent.OperationType.START_ABILITIES,
      requestCode: 1001,
      wantAgentFlags: [WantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
    };

    const wantAgent = await WantAgent.getWantAgent(wantAgentInfo);
    hilog.info(0x0000, 'testTag', 'WantAgent created successfully');
    return wantAgent;
  } catch (error) {
    hilog.error(0x0000, 'testTag', `Failed to create WantAgent: ${JSON.stringify(error)}`);
    throw error;
  }
}

5.2 觸發WantAgent

import { WantAgent } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

async function triggerWantAgent(wantAgent: WantAgent.WantAgent): Promise<void> {
  try {
    await WantAgent.trigger(wantAgent, undefined, {
      onCompleted: (code: number, result: any, want: Want) => {
        hilog.info(0x0000, 'testTag', `WantAgent triggered, code: ${code}`);
      }
    });
  } catch (error) {
    hilog.error(0x0000, 'testTag', `Failed to trigger WantAgent: ${JSON.stringify(error)}`);
  }
}

5.3 在通知中使用WantAgent

import { notificationManager } from '@kit.NotificationKit';
import { WantAgent } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

async function createNotificationWithWantAgent(): Promise<void> {
  try {
    // 創建WantAgent
    const want = {
      action: 'action.system.open',
      entities: ['entity.system.browser'],
      uri: 'https://www.example.com'
    };

    const wantAgentInfo: WantAgent.WantAgentInfo = {
      wants: [want],
      operationType: WantAgent.OperationType.START_ABILITIES,
      requestCode: 1002
    };

    const wantAgent = await WantAgent.getWantAgent(wantAgentInfo);

    // 創建通知
    const notificationRequest: notificationManager.NotificationRequest = {
      id: 1001,
      slotId: 'default_slot',
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: '跨設備通知',
          text: '點擊打開遠程瀏覽器'
        }
      },
      wantAgent: wantAgent
    };

    await notificationManager.publish(notificationRequest);
    hilog.info(0x0000, 'testTag', 'Notification with WantAgent published');
  } catch (error) {
    hilog.error(0x0000, 'testTag', `Failed to create notification: ${JSON.stringify(error)}`);
  }
}

六、完整實戰案例:跨設備文件共享

6.1 場景描述

實現一個跨設備文件共享應用,用户可以在手機端選擇文件,通過跨設備調用將文件發送到平板端,平板端接收文件並顯示傳輸進度。

6.2 手機端實現(發送方)

import { AbilityConstant, common, UIAbility } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { fileIo } from '@kit.FileKit';

class FileSenderAbility extends UIAbility {
  private remoteConnection: common.RemoteAbilityConnection | null = null;
  private filePath: string = '';
  private targetDeviceId: string = '';

  // 選擇文件併發送
  async selectAndSendFile(): Promise<void> {
    try {
      // 選擇文件(實際應用中需要文件選擇器)
      this.filePath = '/data/storage/el2/base/files/sample.txt';
      
      // 讀取文件內容
      const fileContent = await this.readFile(this.filePath);
      
      // 建立遠程連接
      await this.connectToReceiver();
      
      // 發送文件
      await this.sendFile(fileContent);
      
    } catch (error) {
      hilog.error(0x0000, 'testTag', `File send failed: ${JSON.stringify(error)}`);
    }
  }

  // 建立到接收方的連接
  async connectToReceiver(): Promise<void> {
    const want = {
      deviceId: this.targetDeviceId,
      bundleName: 'com.example.filereceiver',
      abilityName: 'FileReceiverServiceAbility'
    };

    this.remoteConnection = {
      onAbilityConnectDone: this.onConnectDone.bind(this),
      onAbilityDisconnectDone: this.onDisconnectDone.bind(this)
    };

    const context = getContext(this) as common.UIAbilityContext;
    await context.connectAbility(want, this.remoteConnection);
  }

  // 發送文件數據
  async sendFile(fileContent: string): Promise<void> {
    if (!this.remoteConnection) {
      throw new Error('Remote connection not established');
    }

    const data = {
      fileName: 'sample.txt',
      fileSize: fileContent.length,
      content: fileContent,
      timestamp: Date.now()
    };

    await this.remoteConnection.sendRequest(1, data, {});
    hilog.info(0x0000, 'testTag', 'File sent successfully');
  }

  // 讀取文件
  async readFile(filePath: string): Promise<string> {
    const file = await fileIo.open(filePath, fileIo.OpenMode.READ_ONLY);
    try {
      const stat = await fileIo.stat(filePath);
      const buffer = new ArrayBuffer(stat.size);
      await fileIo.read(file.fd, buffer, { position: 0 });
      return new TextDecoder().decode(buffer);
    } finally {
      await fileIo.close(file.fd);
    }
  }

  // 連接建立回調
  onConnectDone(elementName: common.ElementName, remote: common.RemoteProxy, resultCode: number): void {
    if (resultCode === 0) {
      hilog.info(0x0000, 'testTag', 'Connected to receiver');
    }
  }

  // 連接斷開回調
  onDisconnectDone(elementName: common.ElementName, resultCode: number): void {
    hilog.info(0x0000, 'testTag', 'Disconnected from receiver');
  }
}

6.3 平板端實現(接收方)

import { AbilityConstant, common, UIAbility } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { fileIo } from '@kit.FileKit';

export default class FileReceiverServiceAbility extends UIAbility {
  private connection: common.RemoteAbilityConnection | null = null;

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', 'FileReceiverServiceAbility onCreate');
    
    this.connection = {
      onAbilityConnectDone: this.onConnectDone.bind(this),
      onAbilityDisconnectDone: this.onDisconnectDone.bind(this)
    };
  }

  onConnectDone(elementName: common.ElementName, remote: common.RemoteProxy, resultCode: number): void {
    if (resultCode === 0) {
      hilog.info(0x0000, 'testTag', 'File sender connected');
      
      // 設置請求處理回調
      remote.setReceiveRequestCallback(this.handleFileRequest.bind(this));
    }
  }

  onDisconnectDone(elementName: common.ElementName, resultCode: number): void {
    hilog.info(0x0000, 'testTag', 'File sender disconnected');
  }

  // 處理文件傳輸請求
  async handleFileRequest(code: number, data: any, reply: common.MessageParcel, option: common.MessageOption): Promise<void> {
    if (code === 1) {
      await this.saveFile(data);
      reply.writeInt(AbilityConstant.ResultCode.RESULT_OK);
      reply.writeString('File received successfully');
    } else {
      reply.writeInt(AbilityConstant.ResultCode.RESULT_CANCELED);
    }
  }

  // 保存接收到的文件
  async saveFile(fileData: any): Promise<void> {
    const { fileName, content } = fileData;
    const savePath = `/data/storage/el2/base/files/received/${fileName}`;
    
    // 確保目錄存在
    await fileIo.mkdir('/data/storage/el2/base/files/received', fileIo.Mode.IRWXU);
    
    // 寫入文件
    const file = await fileIo.open(savePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
    try {
      const buffer = new TextEncoder().encode(content);
      await fileIo.write(file.fd, buffer);
      hilog.info(0x0000, 'testTag', `File saved: ${savePath}`);
    } finally {
      await fileIo.close(file.fd);
    }
  }
}

七、性能優化與最佳實踐

7.1 連接池管理

對於需要頻繁跨設備調用的應用,建議使用連接池管理連接:

class ConnectionPool {
  private connections: Map<string, common.RemoteAbilityConnection> = new Map();
  private maxConnections: number = 5;

  // 獲取連接
  async getConnection(deviceId: string, bundleName: string, abilityName: string): Promise<common.RemoteAbilityConnection> {
    const key = `${deviceId}_${bundleName}_${abilityName}`;
    
    if (this.connections.has(key)) {
      return this.connections.get(key)!;
    }

    // 清理過期的連接
    if (this.connections.size >= this.maxConnections) {
      this.cleanupIdleConnections();
    }

    const connection = await this.createConnection(deviceId, bundleName, abilityName);
    this.connections.set(key, connection);
    return connection;
  }

  // 創建新連接
  private async createConnection(deviceId: string, bundleName: string, abilityName: string): Promise<common.RemoteAbilityConnection> {
    const want = { deviceId, bundleName, abilityName };
    const context = getContext(this) as common.UIAbilityContext;
    
    return new Promise((resolve, reject) => {
      const connection: common.RemoteAbilityConnection = {
        onAbilityConnectDone: (elementName, remote, resultCode) => {
          if (resultCode === 0) {
            resolve(connection);
          } else {
            reject(new Error(`Connection failed: ${resultCode}`));
          }
        },
        onAbilityDisconnectDone: () => {
          this.connections.delete(`${deviceId}_${bundleName}_${abilityName}`);
        }
      };
      
      context.connectAbility(want, connection);
    });
  }

  // 清理空閒連接
  private cleanupIdleConnections(): void {
    // 實現連接清理邏輯
  }
}

7.2 錯誤處理與重試

class CrossDeviceCallManager {
  private maxRetries: number = 3;
  private retryDelay: number = 1000; // 1秒

  // 帶重試的跨設備調用
  async startAbilityWithRetry(want: Want, maxRetries: number = this.maxRetries): Promise<void> {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        const context = getContext(this) as common.UIAbilityContext;
        await context.startAbility(want);
        return; // 成功則返回
      } catch (error) {
        if (attempt === maxRetries) {
          throw error; // 最後一次嘗試仍然失敗
        }
        
        // 指數退避延遲
        const delay = this.retryDelay * Math.pow(2, attempt - 1);
        await this.delay(delay);
      }
    }
  }

  // 延遲函數
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

7.3 權限檢查與申請

import { abilityAccessCtrl } from '@kit.AbilityAccessCtrlKit';

async function checkAndRequestPermissions(): Promise<boolean> {
  try {
    const atManager = abilityAccessCtrl.createAtManager();
    
    // 檢查權限
    const permissions = [
      'ohos.permission.DISTRIBUTED_DATASYNC',
      'ohos.permission.GET_DISTRIBUTED_DEVICE_INFO'
    ];
    
    const grantStatus = await atManager.checkAccessToken(permissions);
    
    // 如果權限未授予,請求權限
    if (grantStatus.authList.some(item => item.grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_DENIED)) {
      await atManager.requestPermissionsFromUser(permissions);
      
      // 再次檢查權限
      const newGrantStatus = await atManager.checkAccessToken(permissions);
      return newGrantStatus.authList.every(item => item.grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED);
    }
    
    return true;
  } catch (error) {
    console.error('Permission check failed:', error);
    return false;
  }
}

八、常見問題與解決方案

8.1 設備發現失敗

問題:無法發現目標設備。

解決方案

  • 檢查設備是否在同一局域網
  • 確認目標設備已開啓分佈式能力
  • 檢查設備發現權限是否已授予

8.2 連接超時

問題:跨設備連接建立超時。

解決方案

  • 增加連接超時時間
  • 實現重試機制
  • 檢查網絡狀況

8.3 權限拒絕

問題:跨設備調用因權限不足失敗。

解決方案

  • module.json5中聲明所需權限
  • 運行時動態請求用户授權
  • 優雅處理權限拒絕的情況

8.4 數據序列化錯誤

問題:跨設備傳輸的數據無法正確序列化。

解決方案

  • 確保傳輸的數據是可序列化的JSON對象
  • 避免傳輸循環引用的對象
  • 對於複雜對象,使用自定義序列化方法

總結

跨設備調用是HarmonyOS分佈式能力的核心,它打破了設備間的物理邊界,讓應用能力可以在多設備間自由流轉。通過本文的學習,你應該掌握了:

核心要點回顧:

  1. Want與WantAgent:跨設備調用的核心數據結構和執行器
  2. 隱式與顯式啓動:兩種啓動遠程Ability的方式
  3. 連接模式:建立長連接進行持續通信
  4. 錯誤處理與重試:保證跨設備調用的可靠性

最佳實踐:

  • 合理使用連接池管理跨設備連接
  • 實現完善的錯誤處理和重試機制
  • 在適當的時機清理連接資源
  • 遵循最小權限原則申請權限

跨設備調用技術的正確運用,將為你的HarmonyOS應用帶來真正的分佈式體驗,讓用户在不同設備間無縫切換,享受一致的應用服務。