HarmonyOS開發之分佈式硬件共享——使用虛擬設備

第一部分:引入

想象一下這樣的場景:你在用平板電腦參加視頻會議,但平板的攝像頭像素不夠高,畫質模糊;或者你在智能手錶上想拍照記錄運動瞬間,但手錶攝像頭性能有限。傳統解決方案是手動切換到手機拍照,再傳回平板或手錶,過程繁瑣且體驗割裂。

HarmonyOS的分佈式硬件共享技術徹底改變了這一局面。它通過設備虛擬化技術,將網絡中多個物理設備的硬件資源(攝像頭、麥克風、傳感器等)抽象為統一的資源池,應用可以像調用本地硬件一樣調用遠程設備的硬件能力。這就像給你的設備裝上了"分身術",讓手機的高清攝像頭、平板的麥克風、手錶的傳感器都能被其他設備按需調用,真正實現了"硬件互助、資源共享"的超級終端體驗。

第二部分:講解

一、分佈式硬件共享的核心原理

1.1 技術架構

HarmonyOS的分佈式硬件共享基於分佈式軟總線設備虛擬化兩大核心技術:

分佈式軟總線:相當於設備間的"隱形高速公路",負責設備發現、連接認證和數據傳輸。它自動選擇最優通信路徑(Wi-Fi、藍牙等),屏蔽底層網絡差異,讓跨設備通信像本地調用一樣簡單。

設備虛擬化:將遠程設備的硬件能力抽象為本地虛擬驅動。當應用調用攝像頭時,系統會:

  • 發現並連接擁有攝像頭的可信設備
  • 在該設備上創建虛擬攝像頭驅動
  • 將虛擬驅動註冊到本地設備管理器
  • 應用通過標準Camera API調用,無需感知硬件位置
1.2 工作流程
// 文件:src/main/ets/entryability/EntryAbility.ts
import deviceManager from '@ohos.distributedHardware.deviceManager';
import camera from '@ohos.multimedia.camera';

@Component
export class DistributedCameraService {
  private deviceManager: deviceManager.DeviceManager | null = null;
  private remoteDeviceId: string | null = null;
  private remoteCamera: camera.CameraDevice | null = null;

  // 初始化設備管理
  async initDeviceManager(): Promise<void> {
    try {
      // 創建設備管理實例
      this.deviceManager = await deviceManager.createDeviceManager('com.example.cameraapp');
      
      // 監聽設備狀態變化
      this.deviceManager.on('deviceStateChange', (data) => {
        this.handleDeviceStateChange(data);
      });
      
      // 開始設備發現
      this.discoverDevices();
    } catch (error) {
      console.error('設備管理初始化失敗:', error);
    }
  }

  // 發現附近設備
  private async discoverDevices(): Promise<void> {
    if (!this.deviceManager) return;
    
    try {
      const devices = await this.deviceManager.getTrustedDeviceList();
      
      // 優先選擇有攝像頭的手機設備
      const phoneDevice = devices.find(device => 
        device.deviceType === 'phone' && device.deviceName.includes('Phone')
      );
      
      if (phoneDevice) {
        this.remoteDeviceId = phoneDevice.deviceId;
        console.info('發現可用手機設備:', phoneDevice.deviceName);
      }
    } catch (error) {
      console.error('設備發現失敗:', error);
    }
  }
}

二、核心代碼實現

2.1 權限配置

在項目配置文件中聲明必要的分佈式權限:

// 文件:module.json5
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.DISTRIBUTED_DATASYNC",
        "reason": "用於設備間數據傳輸",
        "usedScene": {
          "abilities": ["MainAbility"],
          "when": "always"
        }
      },
      {
        "name": "ohos.permission.CAMERA",
        "reason": "調用攝像頭拍照",
        "usedScene": {
          "abilities": ["MainAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.DISTRIBUTED_DEVICE_STATE",
        "reason": "發現並連接附近設備",
        "usedScene": {
          "abilities": ["MainAbility"],
          "when": "always"
        }
      }
    ]
  }
}
2.2 獲取分佈式攝像頭列表
// 文件:src/main/ets/entryability/DistributedCamera.ts
import camera from '@ohos.multimedia.camera';

@Component
export class DistributedCamera {
  private cameraService: DistributedCameraService;
  private remoteCamera: camera.CameraDevice | null = null;

  // 獲取分佈式攝像頭列表
  async getDistributedCameras(): Promise<camera.CameraDevice[]> {
    try {
      this.cameraService = new DistributedCameraService();
      await this.cameraService.initDeviceManager();
      
      const cameraManager = camera.getCameraManager(getContext(this));
      const cameras = await cameraManager.getCameras();
      
      // 篩選分佈式攝像頭
      const distributedCameras = cameras.filter(cameraDevice => {
        return cameraDevice.connectionType === camera.ConnectionType.CAMERA_CONNECTION_REMOTE;
      });
      
      return distributedCameras;
    } catch (error) {
      console.error('獲取攝像頭列表失敗:', error);
      return [];
    }
  }
}
2.3 連接遠程攝像頭並拍照
// 文件:src/main/ets/entryability/DistributedCamera.ts
import image from '@ohos.multimedia.image';

async takePhotoWithRemoteCamera(): Promise<image.PixelMap> {
  try {
    const distributedCameras = await this.getDistributedCameras();
    if (distributedCameras.length === 0) {
      throw new Error('未找到可用的分佈式攝像頭');
    }

    // 選擇第一個遠程攝像頭
    this.remoteCamera = distributedCameras[0];
    const cameraInput = await camera.createCameraInput(this.remoteCamera);

    // 創建拍照會話
    const photoSession = await camera.createPhotoSession(getContext(this));
    await photoSession.beginConfig();
    await photoSession.addInput(cameraInput);

    // 配置拍照參數
    const photoOutput = await camera.createPhotoOutput(getContext(this));
    await photoSession.addOutput(photoOutput);
    await photoSession.commitConfig();
    await photoSession.start();

    // 執行拍照
    const photo = await photoOutput.capture();
    console.info('拍照成功');

    // 清理資源
    await photoSession.stop();
    await cameraInput.release();
    
    return photo;
  } catch (error) {
    console.error('拍照失敗:', error);
    throw error;
  }
}

三、關鍵API參數説明

API接口

參數説明

返回值

使用場景

deviceManager.createDeviceManager()

context: 應用上下文

DeviceManager實例

創建設備管理實例

deviceManager.getTrustedDeviceList()


DeviceInfo[]設備列表

獲取可信設備列表

camera.getCameraManager()

context: 應用上下文

CameraManager實例

獲取攝像頭管理器

cameraManager.getCameras()


CameraDevice[]攝像頭列表

獲取所有攝像頭(含分佈式)

camera.createCameraInput()

cameraDevice: 攝像頭設備

CameraInput實例

創建攝像頭輸入流

camera.createPhotoSession()

context: 應用上下文

PhotoSession實例

創建拍照會話

photoSession.addInput()

cameraInput: 攝像頭輸入


添加攝像頭輸入到會話

photoOutput.capture()


PixelMap圖像數據

執行拍照並返回圖像

四、實戰場景:手錶調用手機攝像頭

4.1 場景描述

智能手錶屏幕小、攝像頭性能有限,但用户希望在手錶上直接拍照並查看。通過分佈式硬件共享,手錶可以調用手機的攝像頭進行拍照,照片自動同步到手錶面面顯示。

4.2 完整代碼實現
// 文件:src/main/ets/entryability/WatchCameraApp.ts
@Entry
@Component
struct WatchCameraApp {
  @State isConnected: boolean = false;
  @State isTakingPhoto: boolean = false;
  @State photoData: image.PixelMap | null = null;
  @State deviceStatus: string = '搜索設備中...';
  
  private distributedCamera: DistributedCamera = new DistributedCamera();

  aboutToAppear(): void {
    this.checkDeviceConnection();
  }

  // 檢查設備連接狀態
  async checkDeviceConnection(): Promise<void> {
    try {
      const cameras = await this.distributedCamera.getDistributedCameras();
      this.isConnected = cameras.length > 0;
      this.deviceStatus = this.isConnected ? '設備已連接' : '未發現可用設備';
    } catch (error) {
      this.deviceStatus = '設備連接失敗';
      console.error('設備連接檢查失敗:', error);
    }
  }

  // 拍照處理
  async takePhoto(): Promise<void> {
    if (!this.isConnected || this.isTakingPhoto) return;
    
    this.isTakingPhoto = true;
    try {
      this.photoData = await this.distributedCamera.takePhotoWithRemoteCamera();
      console.info('照片獲取成功');
    } catch (error) {
      console.error('拍照過程失敗:', error);
      this.deviceStatus = '拍照失敗,請重試';
    } finally {
      this.isTakingPhoto = false;
    }
  }

  build() {
    Column({ space: 20 }) {
      // 狀態顯示
      Text(this.deviceStatus)
        .fontSize(16)
        .fontColor(this.isConnected ? '#00ff00' : '#ff0000')
        .margin({ bottom: 20 })

      // 拍照按鈕
      Button(this.isTakingPhoto ? '拍照中...' : '拍照')
        .width(120)
        .height(120)
        .backgroundColor('#007DFF')
        .fontColor('#FFFFFF')
        .fontSize(18)
        .onClick(() => {
          this.takePhoto();
        })
        .enabled(this.isConnected && !this.isTakingPhoto)

      // 照片預覽
      if (this.photoData) {
        Image(this.photoData)
          .width(200)
          .height(200)
          .objectFit(ImageFit.Contain)
          .border({ width: 1, color: '#CCCCCC' })
      }
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

五、注意事項與最佳實踐

5.1 常見問題及解決方案

問題1:設備連接不穩定

  • 原因:網絡波動或設備離線
  • 解決方案:實現重連機制,監聽設備狀態變化
// 監聽設備狀態變化
deviceManager.on('deviceStateChange', (data) => {
  if (data.action === 'offline' && data.device.deviceId === this.remoteDeviceId) {
    this.isConnected = false;
    this.deviceStatus = '設備已斷開連接';
    // 自動重連
    setTimeout(() => this.checkDeviceConnection(), 3000);
  } else if (data.action === 'online') {
    this.checkDeviceConnection();
  }
});

問題2:權限申請失敗

  • 原因:用户拒絕授權或權限配置錯誤
  • 解決方案:檢查權限配置,提示用户手動開啓
// 主動申請權限
async requestPermissions(): Promise<void> {
  try {
    await requestPermissionsFromUser([
      'ohos.permission.DISTRIBUTED_DATASYNC',
      'ohos.permission.CAMERA'
    ]);
  } catch (error) {
    console.error('權限申請失敗:', error);
    // 提示用户手動開啓權限
    prompt.showDialog({
      title: '權限申請',
      message: '請前往設置中開啓相機和跨設備數據同步權限',
      buttons: [{ text: '確定' }]
    });
  }
}

問題3:拍照延遲高

  • 原因:網絡延遲或設備性能差異
  • 解決方案:優化網絡連接,使用設備能力查詢接口
// 選擇性能最優的設備
async selectBestCamera(): Promise<string | null> {
  const devices = await deviceManager.getTrustedDeviceList();
  let bestDevice: deviceManager.DeviceInfo | null = null;
  
  for (const device of devices) {
    const capabilities = await deviceManager.getDeviceCapabilities(device.deviceId);
    if (capabilities.camera && capabilities.camera.resolution > (bestDevice?.capabilities?.camera?.resolution || 0)) {
      bestDevice = device;
    }
  }
  
  return bestDevice?.deviceId || null;
}
5.2 性能優化建議
  1. 設備發現優化:緩存設備列表,避免頻繁發現
  2. 資源釋放:及時釋放攝像頭、會話等資源
  3. 網絡預連接:提前建立設備連接,減少拍照延遲
  4. 數據壓縮:傳輸前壓縮圖像數據,減少網絡負載

第三部分:總結

核心要點回顧

  1. 設備虛擬化是基礎:通過分佈式軟總線將遠程硬件抽象為本地虛擬驅動,應用調用方式與本地硬件一致
  2. 權限配置是關鍵:必須正確聲明DISTRIBUTED_DATASYNCCAMERA等權限,並在運行時主動申請
  3. 設備發現與連接:通過DeviceManager發現可信設備,監聽設備狀態變化,實現自動重連
  4. 資源管理要規範:及時釋放攝像頭、會話等資源,避免內存泄漏

行動建議

  1. 開發階段:使用DevEco Studio的分佈式模擬器進行多設備聯調,模擬不同網絡環境
  2. 測試階段:覆蓋設備離線、網絡切換、權限拒絕等異常場景,確保應用健壯性
  3. 上線前:在真機環境進行充分測試,驗證不同設備型號的兼容性

下篇預告

下一篇我們將進入多端協同案例:分佈式購物車的實戰開發。通過一個完整的電商購物車案例,學習如何實現商品數據在多設備間的實時同步、跨設備購物車遷移、以及分佈式狀態管理等高級技術。你將掌握分佈式應用開發的完整流程,為後續的綜合實戰項目打下堅實基礎。