openharmony之分布式相机开发:预览\拍照\编辑\同步\删除\分享教程

一、环境准备

  1. 硬件与系统要求
    • 设备 : 至少两台搭载摄像头的 OpenHarmony 设备,例如 RK3568、RK3566、RK3588 开发板等。真机调试是分布式功能开发的必要条件。
    • 系统版本 : OpenHarmony 5.0 Release (5.0.0.71) 或更高版本。不同版本间 API 可能有差异,请务必保持一致。
    • 网络 : 所有设备必须连接到 同一个局域网 (LAN) 内,这是设备发现和通信的基础。
  2. 开发工具
    • IDE : DevEco Studio 5.0.3.910 或更高版本。
    • SDK : Full SDK (API Version 12+)。相机、分布式硬件等系统级能力需要 Full SDK 支持,Public SDK 不包含这些接口。

二、分布式设备认证与连接

分布式操作的第一步是让设备之间相互发现并建立信任关系。

  1. 权限声明
    module.json5 文件中,必须声明以下权限,否则应用会因权限不足而崩溃。

    json 复制代码
    {
      "module": {
        // ...其他配置
        "requestPermissions": [
          {
            "name": "ohos.permission.DISTRIBUTED_DATASYNC", // 分布式数据管理核心权限
            "reason": "$string:permission_reason_distributed_datasync",
            "usedScene": {
              "abilities": ["EntryAbility"],
              "when": "inuse"
            }
          },
          {
            "name": "ohos.permission.CAMERA", // 使用相机
            "reason": "$string:permission_reason_camera",
            "usedScene": {
              "abilities": ["EntryAbility"],
              "when": "inuse"
            }
          },
          {
            "name": "ohos.permission.READ_MEDIA", // 读取媒体文件
            "reason": "$string:permission_reason_read_media",
            "usedScene": {
              "abilities": ["EntryAbility"],
              "when": "inuse"
            }
          },
          {
            "name": "ohos.permission.WRITE_MEDIA", // 写入媒体文件
            "reason": "$string:permission_reason_write_media",
            "usedScene": {
              "abilities": ["EntryAbility"],
              "when": "inuse"
            }
          },
          {
            "name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE", // 监听设备状态变化
            "usedScene": {
              "abilities": ["EntryAbility"],
              "when": "inuse"
            }
          }
        ]
      }
    }
  2. 设备发现与认证 (完整封装)
    我们创建一个 DeviceManager.ets 单例类来统一管理设备相关的逻辑。

    typescript 复制代码
    // common/DeviceManager.ets
    import deviceManager from '@ohos.distributedHardware.deviceManager';
    import hilog from '@ohos.hilog';
    import promptAction from '@ohos.promptAction';
    const TAG = 'DeviceManager';
    export interface DeviceInfo {
      deviceId: string;
      deviceName: string;
    }
    export class DeviceManagerInstance {
      private static instance: DeviceManagerInstance;
      private dmInstance: deviceManager.DeviceManager | null = null;
      private deviceList: DeviceInfo[] = [];
      private callback: ((devices: DeviceInfo[]) => void) | null = null;
      private constructor() {}
      public static getInstance(): DeviceManagerInstance {
        if (!DeviceManagerInstance.instance) {
          DeviceManagerInstance.instance = new DeviceManagerInstance();
        }
        return DeviceManagerInstance.instance;
      }
      // 初始化设备管理器
      public async init(bundleName: string): Promise<void> {
        if (this.dmInstance) {
          return;
        }
        try {
          this.dmInstance = await deviceManager.createDeviceManager(bundleName);
          hilog.info(0x0000, TAG, 'DeviceManager created successfully.');
          this.registerDeviceListCallback();
        } catch (err) {
          hilog.error(0x0000, TAG, `Failed to create DeviceManager. Code: ${err.code}, message: ${err.message}`);
        }
      }
      // 注册设备状态监听
      private registerDeviceListCallback(): void {
        if (!this.dmInstance) return;
        this.dmInstance.on('deviceStateChange', (data) => {
          hilog.info(0x0000, TAG, `Device state changed: ${JSON.stringify(data)}`);
          this.refreshDeviceList();
        });
        this.dmInstance.on('deviceFound', (data) => {
          hilog.info(0x0000, TAG, `Device found: ${JSON.stringify(data)}`);
          this.refreshDeviceList();
        });
        this.dmInstance.on('discoverFail', (data) => {
          hilog.error(0x0000, TAG, `Device discover failed: ${JSON.stringify(data)}`);
        });
        this.refreshDeviceList();
      }
      // 刷新设备列表
      private refreshDeviceList(): void {
        if (!this.dmInstance) return;
        try {
          const devices = this.dmInstance.getTrustedDeviceList();
          this.deviceList = devices.map(device => ({
            deviceId: device.networkId,
            deviceName: device.deviceName
          }));
          hilog.info(0x0000, TAG, `Current trusted devices: ${JSON.stringify(this.deviceList)}`);
          if (this.callback) {
            this.callback(this.deviceList);
          }
        } catch (err) {
          hilog.error(0x0000, TAG, `Failed to get trusted device list. Code: ${err.code}, message: ${err.message}`);
        }
      }
      // 开始设备发现
      public startDeviceDiscovery(): void {
        if (!this.dmInstance) {
          hilog.error(0x0000, TAG, 'DeviceManager not initialized.');
          return;
        }
        const extraInfo = {
          'targetPkgName': 'com.example.distributedcamera' // 替换为你的包名
        };
        this.dmInstance.startDeviceDiscovery(extraInfo);
        hilog.info(0x0000, TAG, 'Start device discovery.');
      }
      // 认证设备
      public async authenticateDevice(deviceId: string): Promise<void> {
        if (!this.dmInstance) return;
        try {
          const pinCode = '123456'; // 固定PIN码或动态生成
          await this.dmInstance.authenticateDevice(deviceId, pinCode);
          promptAction.showToast({ message: '认证请求已发送,请在对方设备上确认。' });
        } catch (err) {
          hilog.error(0x0000, TAG, `Authentication failed. Code: ${err.code}, message: ${err.message}`);
          promptAction.showToast({ message: '认证失败' });
        }
      }
      // 获取设备列表
      public getDeviceList(): DeviceInfo[] {
        return this.deviceList;
      }
      // 订阅设备列表变化
      public onDeviceListChange(callback: (devices: DeviceInfo[]) => void): void {
        this.callback = callback;
      }
    }

三、相机预览实现

这是相机的核心功能。在 OpenHarmony 5.0 中,必须使用 CaptureSession 来兼容分布式相机。

  1. 创建预览界面 (CameraPreview.ets)
    我们使用 XComponent 组件来承载相机预览流。

    typescript 复制代码
    // pages/CameraPreview.ets
    import camera from '@ohos.multimedia.camera';
    import image from '@ohos.multimedia.image';
    import hilog from '@ohos.hilog';
    import { DeviceManagerInstance, DeviceInfo } from '../common/DeviceManager';
    const TAG = 'CameraPreview';
    @Entry
    @Component
    struct CameraPreviewPage {
      @State surfaceId: string = '';
      @State isPreviewing: boolean = false;
      @State deviceList: DeviceInfo[] = [];
      @State selectedDeviceId: string = ''; // 要连接的远程设备ID,为空则使用本地相机
      private mXComponentController: XComponentController = new XComponentController();
      private cameraManager: camera.CameraManager | null = null;
      private cameraInput: camera.CameraInput | null = null;
      private captureSession: camera.CaptureSession | null = null;
      private previewOutput: camera.PreviewOutput | null = null;
      aboutToAppear(): void {
        // 初始化设备管理器
        DeviceManagerInstance.getInstance().init('com.example.distributedcamera').then(() => {
          DeviceManagerInstance.getInstance().onDeviceListChange((devices) => {
            this.deviceList = devices;
          });
          DeviceManagerInstance.getInstance().startDeviceDiscovery();
        });
      }
      aboutToDisappear(): void {
        this.releaseCamera();
      }
      // 初始化相机
      async initCamera(surfaceId: string) {
        this.releaseCamera(); // 先释放之前的会话
        try {
          this.cameraManager = camera.getCameraManager(getContext(this));
          // 获取相机设备,支持分布式
          const cameras = await this.cameraManager.getSupportedCameras(this.selectedDeviceId);
          if (cameras.length === 0) {
            hilog.error(0x0000, TAG, 'No camera found.');
            return;
          }
          this.cameraInput = this.cameraManager.createCameraInput(cameras[0]);
          await this.cameraInput.open();
          // 【关键】创建 CaptureSession
          this.captureSession = this.cameraManager.createCaptureSession();
          this.captureSession.beginConfig();
          // 添加输入
          this.captureSession.addInput(this.cameraInput);
          // 创建并添加预览输出
          const previewProfile = this.cameraManager.getSupportedPreviewOutputProfiles(cameras[0])[0];
          this.previewOutput = this.cameraManager.createPreviewOutput(previewProfile, surfaceId);
          this.captureSession.addOutput(this.previewOutput);
          await this.captureSession.commitConfig();
          await this.captureSession.start();
          this.isPreviewing = true;
          hilog.info(0x0000, TAG, 'Camera preview started successfully.');
        } catch (error) {
          hilog.error(0x0000, TAG, `Failed to init camera. Code: ${error.code}, message: ${error.message}`);
        }
      }
      // 释放相机资源
      releaseCamera() {
        if (this.isPreviewing && this.captureSession) {
          this.captureSession.stop();
        }
        if (this.captureSession) {
          this.captureSession.release();
          this.captureSession = null;
        }
        if (this.previewOutput) {
          this.previewOutput.release();
          this.previewOutput = null;
        }
        if (this.cameraInput) {
          this.cameraInput.close();
          this.cameraInput.release();
          this.cameraInput = null;
        }
        this.isPreviewing = false;
        hilog.info(0x0000, TAG, 'Camera resources released.');
      }
      build() {
        Column() {
          // 设备选择列表
          Row() {
            Text('选择相机设备:')
            Select(this.deviceList.map(d => d.deviceName))
              .selected(0)
              .value('本地相机')
              .font({ size: 16 })
              .onSelect((index: number) => {
                if (index === 0) {
                  this.selectedDeviceId = '';
                } else {
                  this.selectedDeviceId = this.deviceList[index - 1].deviceId;
                }
                hilog.info(0x0000, TAG, `Selected device: ${this.selectedDeviceId}`);
              })
          }.width('100%').justifyContent(FlexAlign.Start).padding(10)
          // 预览区域
          XComponent({
            type: 'surface',
            controller: this.mXComponentController
          })
            .onLoad(() => {
              this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
              hilog.info(0x0000, TAG, `XComponent surfaceId: ${this.surfaceId}`);
              this.initCamera(this.surfaceId);
            })
            .width('100%')
            .height('60%')
          // 控制按钮
          Row({ space: 20 }) {
            Button('拍照')
              .onClick(() => {
                // 拍照逻辑将在下一节实现
                hilog.info(0x0000, TAG, 'Take photo button clicked.');
              })
          }
        }
      }
    }

四、拍照与保存

  1. 集成拍照功能
    CameraPreview.ets 中,我们添加拍照输出和拍照逻辑。

    typescript 复制代码
    // 在 CameraPreview.ets 组件中添加状态和成员变量
    @State photoUri: string = ''; // 用于保存拍照后的URI
    private photoOutput: camera.PhotoOutput | null = null;
    // 修改 initCamera 方法,添加 PhotoOutput
    async initCamera(surfaceId: string) {
      // ... (前面的代码保持不变)
      this.captureSession.beginConfig();
      this.captureSession.addInput(this.cameraInput);
      this.captureSession.addOutput(this.previewOutput);
      // 【新增】创建并添加拍照输出
      const photoProfile = this.cameraManager.getSupportedPhotoOutputProfiles(cameras[0])[0];
      this.photoOutput = this.cameraManager.createPhotoOutput(photoProfile);
      this.captureSession.addOutput(this.photoOutput);
      await this.captureSession.commitConfig();
      await this.captureSession.start();
      // ... (后面的代码保持不变)
    }
    // 修改 releaseCamera 方法,释放 PhotoOutput
    releaseCamera() {
      // ... (前面的代码保持不变)
      if (this.photoOutput) {
        this.photoOutput.release();
        this.photoOutput = null;
      }
      // ... (后面的代码保持不变)
    }
    // 【新增】拍照函数
    takePicture() {
      if (!this.photoOutput) {
        hilog.error(0x0000, TAG, 'PhotoOutput is not initialized.');
        return;
      }
      const photoSettings: camera.PhotoCaptureSetting = {
        quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
        rotation: camera.ImageRotation.ROTATION_0
      };
      this.photoOutput.capture(photoSettings, (err, photo) => {
        if (err || !photo) {
          hilog.error(0x0000, TAG, `Failed to capture photo. Code: ${err.code}, message: ${err.message}`);
          return;
        }
        hilog.info(0x0000, TAG, 'Photo captured successfully.');
        // photo.main.uri 就是图片的URI,直接保存即可
        this.photoUri = photo.main.uri;
        hilog.info(0x0000, TAG, `Photo saved at: ${this.photoUri}`);
        // 这里可以触发一个Toast提示用户拍照成功
        promptAction.showToast({ message: '拍照成功!' });
      });
    }
    // 修改 Button 的 onClick 事件
    Button('拍照')
      .onClick(() => {
        this.takePicture();
      })
  2. 保存到分布式文件系统
    photoOutput.capture 回调中返回的 photo.main.uri 已经是一个指向分布式文件路径的 URI。OpenHarmony 的相机模块会自动将照片保存到应用的分布式目录下 (/data/storage/el2/distributedfiles/)。


五、图像编辑(修图)

我们创建一个新的页面 ImageEditor.ets 来编辑图片。

  1. 创建编辑页面
    从相机页面跳转过来,并传入图片 URI。

    typescript 复制代码
    // pages/ImageEditor.ets
    import image from '@ohos.multimedia.image';
    import fs from '@ohos.file.fs';
    import hilog from '@ohos.hilog';
    import promptAction from '@ohos.promptAction';
    const TAG = 'ImageEditor';
    @Entry
    @Component
    struct ImageEditorPage {
      @State imagePixelMap: image.PixelMap | undefined = undefined;
      @State editHistory: image.PixelMap[] = []; // 简单的编辑历史
      private currentHistoryIndex: number = -1;
      private imageUri: string = (router.getParams() as Record<string, string>)['uri'] ?? '';
      aboutToAppear(): void {
        this.loadImage();
      }
      async loadImage() {
        try {
          const imageSource = image.createImageSource(this.imageUri);
          this.imagePixelMap = await imageSource.createPixelMap();
          this.editHistory.push(this.imagePixelMap!);
          this.currentHistoryIndex = 0;
        } catch (error) {
          hilog.error(0x0000, TAG, `Failed to load image. Code: ${error.code}, message: ${error.message}`);
        }
      }
      // 应用编辑操作并保存到历史
      applyEdit(editFunction: (pixelMap: image.PixelMap) => void) {
        if (this.currentHistoryIndex < this.editHistory.length - 1) {
          this.editHistory = this.editHistory.slice(0, this.currentHistoryIndex + 1);
        }
        const newPixelMap = this.editHistory[this.currentHistoryIndex].clone();
        editFunction(newPixelMap);
        this.editHistory.push(newPixelMap);
        this.currentHistoryIndex++;
        this.imagePixelMap = newPixelMap;
      }
      // 保存编辑后的图片
      async saveEditedImage() {
        if (!this.imagePixelMap) return;
        try {
          const packOpts: image.PackingOption = { format: "image/jpeg", quality: 98 };
          const imagePacker = image.createImagePacker();
          const buffer = await imagePacker.packing(this.imagePixelMap, packOpts);
          const newUri = `${this.imageUri.split('.')[0]}_edited_${Date.now()}.jpg`;
          const file = fs.openSync(newUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
          fs.writeSync(file.fd, buffer);
          fs.closeSync(file);
          hilog.info(0x0000, TAG, `Edited image saved to: ${newUri}`);
          promptAction.showToast({ message: '图片已保存!' });
          router.back();
        } catch (error) {
          hilog.error(0x0000, TAG, `Failed to save edited image. Code: ${error.code}, message: ${error.message}`);
        }
      }
      build() {
        Column() {
          // 图片显示区域
          Image(this.imagePixelMap)
            .objectFit(ImageFit.Contain)
            .width('100%')
            .height('50%')
          // 编辑按钮
          Row({ space: 10 }) {
            Button('裁剪').onClick(() => {
              this.applyEdit((pm) => {
                pm.crop({ x: 50, y: 50, size: { height: 200, width: 200 } });
              });
            })
            Button('旋转90°').onClick(() => {
              this.applyEdit((pm) => {
                pm.rotate(90);
              });
            })
            Button('灰度').onClick(() => {
              this.applyEdit((pm) => {
                // 读取像素数据,处理,再写回
                const area = { x: 0, y: 0, size: { height: pm.getHeight(), width: pm.getWidth() } };
                const buffer = new ArrayBuffer(pm.getBytesNumberPerRow() * pm.getHeight());
                pm.readPixels(buffer, area);
                // ... 在这里进行灰度算法处理 ...
                pm.writePixels(buffer, area);
              });
            })
          }.margin({ top: 20, bottom: 20 })
          Row({ space: 10 }) {
            Button('撤销').enabled(this.currentHistoryIndex > 0).onClick(() => {
              this.currentHistoryIndex--;
              this.imagePixelMap = this.editHistory[this.currentHistoryIndex];
            })
            Button('重做').enabled(this.currentHistoryIndex < this.editHistory.length - 1).onClick(() => {
              this.currentHistoryIndex++;
              this.imagePixelMap = this.editHistory[this.currentHistoryIndex];
            })
            Button('保存').onClick(() => {
              this.saveEditedImage();
            })
          }
        }.padding(10)
      }
    }
  2. 从相机页面跳转
    CameraPreview.ets 中,拍照成功后可以跳转到编辑页面。

    typescript 复制代码
    // 在 takePicture 函数的回调中
    this.photoUri = photo.main.uri;
    promptAction.showToast({ message: '拍照成功!' });
    router.pushUrl({
      url: 'pages/ImageEditor',
      params: { uri: this.photoUri }
    });

六、分布式图库同步与删除

如前文所述,仅依赖分布式文件系统扫描是不可靠的。我们采用 分布式文件系统 + 分布式数据库 (RDB) 的混合架构。

  1. 创建 RDB 辅助类 (RDBHelper.ets)

    typescript 复制代码
    // common/RDBHelper.ets
    import relationalStore from '@ohos.data.relationalStore';
    import hilog from '@ohos.hilog';
    const TAG = 'RDBHelper';
    const STORE_CONFIG = {
      name: 'DistributedGallery.db',
      securityLevel: relationalStore.SecurityLevel.S1
    };
    const CREATE_TABLE_SQL = 'CREATE TABLE IF NOT EXISTS IMAGES (ID INTEGER PRIMARY KEY AUTOINCREMENT, URI TEXT NOT NULL, DEVICE_ID TEXT NOT NULL, TIMESTAMP INTEGER)';
    export class RDBHelper {
      private static instance: RDBHelper;
      private store: relationalStore.RdbStore | null = null;
      private constructor() {}
      public static getInstance(): RDBHelper {
        if (!RDBHelper.instance) {
          RDBHelper.instance = new RDBHelper();
        }
        return RDBHelper.instance;
      }
      public async init(context: Context): Promise<void> {
        if (this.store) return;
        this.store = await relationalStore.getRdbStore(context, STORE_CONFIG);
        await this.store.executeSql(CREATE_TABLE_SQL);
        hilog.info(0x0000, TAG, 'RDB store initialized and table created.');
      }
      // 插入图片信息
      public async insertImage(uri: string, deviceId: string): Promise<void> {
        if (!this.store) return;
        const valueBucket = {
          'URI': uri,
          'DEVICE_ID': deviceId,
          'TIMESTAMP': Date.now()
        };
        await this.store.insert('IMAGES', valueBucket);
        hilog.info(0x0000, TAG, `Inserted image info: ${uri} from ${deviceId}`);
      }
      // 查询所有图片信息(分布式同步)
      public async queryAllImages(): Promise<Array<{uri: string, deviceId: string}>> {
        if (!this.store) return [];
        const predicates = new relationalStore.RdbPredicates('IMAGES');
        const result = await this.store.query(predicates, ['URI', 'DEVICE_ID']);
        const images: Array<{uri: string, deviceId: string}> = [];
        while (result.goToNextRow()) {
          images.push({
            uri: result.getString(0),
            deviceId: result.getString(1)
          });
        }
        result.close();
        return images;
      }
      // 删除图片信息
      public async deleteImage(uri: string): Promise<void> {
        if (!this.store) return;
        const predicates = new relationalStore.RdbPredicates('IMAGES');
        predicates.equalTo('URI', uri);
        await this.store.delete(predicates);
        hilog.info(0x0000, TAG, `Deleted image info: ${uri}`);
      }
    }
  2. 创建图库页面 (Gallery.ets)

    typescript 复制代码
    // pages/Gallery.ets
    import { RDBHelper } from '../common/RDBHelper';
    import fs from '@ohos.file.fs';
    import hilog from '@ohos.hilog';
    import promptAction from '@ohos.promptAction';
    const TAG = 'Gallery';
    @Entry
    @Component
    struct GalleryPage {
      @State imageList: Array<{uri: string, deviceId: string}> = [];
      @State refreshing: boolean = false;
      aboutToAppear(): void {
        this.loadImages();
      }
      async loadImages() {
        this.refreshing = true;
        try {
          // 从分布式RDB中获取图片元数据列表
          this.imageList = await RDBHelper.getInstance().queryAllImages();
          hilog.info(0x0000, TAG, `Loaded ${this.imageList.length} images from RDB.`);
        } catch (error) {
          hilog.error(0x0000, TAG, `Failed to load images. Code: ${error.code}, message: ${error.message}`);
        } finally {
          this.refreshing = false;
        }
      }
      async deleteImage(uri: string) {
        try {
          // 1. 从分布式文件系统删除文件
          fs.unlink(uri);
          // 2. 从分布式RDB删除记录
          await RDBHelper.getInstance().deleteImage(uri);
          promptAction.showToast({ message: '图片已删除' });
          this.loadImages(); // 刷新列表
        } catch (error) {
          hilog.error(0x0000, TAG, `Failed to delete image. Code: ${error.code}, message: ${error.message}`);
          promptAction.showToast({ message: '删除失败' });
        }
      }
      build() {
        Column() {
          Text('分布式图库').fontSize(24).fontWeight(FontWeight.Bold).margin(10)
          Refresh({ refreshing: $$refreshing }) {
            Grid() {
              ForEach(this.imageList, (item) => {
                GridItem() {
                  Image(item.uri)
                    .objectFit(ImageFit.Cover)
                    .width('100%')
                    .height('100%')
                    .onClick(() => {
                      // 点击可以跳转到编辑或预览页面
                      router.pushUrl({ url: 'pages/ImageEditor', params: { uri: item.uri } });
                    })
                    .gesture(
                      LongPressGesture()
                        .onAction(() => {
                          // 长按弹出删除选项
                          AlertDialog.show({
                            title: '删除图片',
                            message: '确定要删除这张图片吗?',
                            primaryButton: {
                              value: '取消',
                              action: () => {}
                            },
                            secondaryButton: {
                              value: '删除',
                              fontColor: Color.Red,
                              action: () => {
                                this.deleteImage(item.uri);
                              }
                            }
                          })
                        })
                    )
                }
              }, (item) => item.uri)
            }
            .columnsTemplate('1fr 1fr 1fr')
            .columnsGap(8)
            .rowsGap(8)
            .padding(8)
          }
          .onRefreshing(() => {
            this.loadImages();
          })
        }
      }
    }
  3. 在拍照后同步到 RDB
    修改 CameraPreview.etstakePicture 函数,拍照成功后将信息存入 RDB。

    typescript 复制代码
    // 在 CameraPreview.ets 中
    import { RDBHelper } from '../common/RDBHelper';
    import deviceInfo from '@ohos.deviceInfo'; // 用于获取本机设备ID
    // ... 在 takePicture 的回调中
    this.photoUri = photo.main.uri;
    hilog.info(0x0000, TAG, `Photo saved at: ${this.photoUri}`);
    // 【新增】将图片信息同步到分布式RDB
    const localDeviceId = deviceInfo.networkId;
    await RDBHelper.getInstance().insertImage(this.photoUri, localDeviceId);
    promptAction.showToast({ message: '拍照成功!' });

七、分享图片

分享可以通过多种方式实现,这里我们使用分布式数据总线 (KV Store) 来发送一个临时的分享意图。

  1. 创建分享管理器 (ShareManager.ets)

    typescript 复制代码
    // common/ShareManager.ets
    import distributedData from '@ohos.data.distributedData';
    import hilog from '@ohos.hilog';
    const TAG = 'ShareManager';
    const KV_STORE_CONFIG = {
      userId: '0', // 用户ID,通常为'0'
      appId: 'com.example.distributedcamera', // 应用包名
      storeId: 'share_channel' // KV Store的唯一标识
    };
    export class ShareManager {
      private static instance: ShareManager;
      private kvManager: distributedData.KVManager | null = null;
      private kvStore: distributedData.KVStore | null = null;
      private constructor() {}
      public static getInstance(): ShareManager {
        if (!ShareManager.instance) {
          ShareManager.instance = new ShareManager();
        }
        return ShareManager.instance;
      }
      public async init(): Promise<void> {
        if (this.kvManager) return;
        this.kvManager = distributedData.createKVManager(KV_STORE_CONFIG);
        try {
          this.kvStore = await this.kvManager.getKVStore('default', { createIfMissing: true });
          hilog.info(0x0000, TAG, 'KV Store initialized for sharing.');
        } catch (e) {
          hilog.error(0x0000, TAG, `Failed to get KVStore. Code: ${e.code}, message: ${e.message}`);
        }
      }
      public async shareImage(uri: string): Promise<void> {
        if (!this.kvStore) {
          hilog.error(0x0000, TAG, 'KV Store is not initialized.');
          return;
        }
        const key = `share_${Date.now()}`;
        await this.kvStore.put(key, uri);
        hilog.info(0x0000, TAG, `Shared image with key: ${key}, uri: ${uri}`);
      }
      public subscribeToShares(callback: (uri: string) => void): void {
        if (!this.kvStore) return;
        this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
          if (data.insertEntries.length > 0) {
            const uri = data.insertEntries[0].value as string;
            hilog.info(0x0000, TAG, `Received shared image: ${uri}`);
            callback(uri);
          }
        });
      }
    }
  2. 在图库页面集成分享
    Gallery.ets 的长按菜单中添加分享按钮。

    typescript 复制代码
    // 在 Gallery.ets 的 LongPressGesture.onAction 中
    AlertDialog.show({
      title: '图片操作',
      message: '',
      buttons: [
        {
          text: '分享',
          color: Color.Blue,
          action: () => {
            ShareManager.getInstance().shareImage(item.uri);
            promptAction.showToast({ message: '分享成功!' });
          }
        },
        {
          text: '删除',
          color: Color.Red,
          action: () => {
            this.deleteImage(item.uri);
          }
        },
        {
          text: '取消',
          action: () => {}
        }
      ]
    })
  3. 接收分享
    在应用启动时(如 UIAbilityonCreate 或主页面 aboutToAppear)初始化并订阅分享事件。

    typescript 复制代码
    // 在 EntryAbility.ets 或主页面
    import { ShareManager } from '../common/ShareManager';
    import router from '@ohos.router';
    // ...
    ShareManager.getInstance().init().then(() => {
      ShareManager.getInstance().subscribeToShares((uri: string) => {
        // 收到分享,可以弹窗提示用户,或直接跳转到图片预览/编辑页
        promptAction.showDialog({
          title: '收到一张分享的图片',
          message: `是否查看?\n${uri}`,
          buttons: [
            { text: '查看', color: '#0099FF', action: () => {
              router.pushUrl({ url: 'pages/ImageEditor', params: { uri: uri } });
            }},
            { text: '取消', color: '#666666', action: () => {} }
          ]
        });
      });
    });
    // ...

八、完整流程与项目结构

  1. 应用启动流程

    • EntryAbility 启动,初始化 RDBHelperShareManager,并订阅分享事件。
    • 进入主页面(例如 Index.ets),提供进入相机和图库的入口。
  2. 相机流程

    • 进入 CameraPreview.ets,初始化 DeviceManager,发现并选择设备(本地或远程)。
    • 使用 CaptureSession 配置并启动预览。
    • 点击拍照,PhotoOutput 捕获图片,自动保存到分布式目录,并将 URI 和设备 ID 存入分布式 RDB。
    • 可选择跳转到 ImageEditor.ets 进行编辑,编辑后保存为新文件。
  3. 图库流程

    • 进入 Gallery.ets,从分布式 RDB 查询所有图片元数据列表并展示。
    • 点击图片可预览或编辑。
    • 长按图片可分享(通过 KV Store 发送 URI)或删除(同时删除文件和 RDB 记录)。
    • 其他设备上的应用会通过 KV Store 的订阅收到分享通知。
  4. 项目结构示例

    复制代码
    entry/src/main/
    ├── ets
    │   ├── entryability
    │   │   └── EntryAbility.ets
    │   ├── pages
    │   │   ├── Index.ets          // 主页
    │   │   ├── CameraPreview.ets  // 相机预览页
    │   │   ├── ImageEditor.ets    // 图片编辑页
    │   │   └── Gallery.ets        // 分布式图库页
    │   └── common
    │       ├── DeviceManager.ets  // 设备管理单例
    │       ├── RDBHelper.ets      // 分布式数据库辅助类
    │       └── ShareManager.ets   // 分享管理单例
    ├── module.json5                // 权限和模块配置
    └── resources

注意事项

  • 真机调试:分布式功能必须在真实设备上测试,模拟器无法模拟。
  • API 版本:本教程基于 API 12,请确保 DevEco Studio 和 SDK 版本匹配。
  • 错误处理:示例中的错误处理较为简单,生产级应用需要更健壮的异常捕获和用户提示。
  • 性能优化 :图库加载大量图片时,应考虑分页加载、图片缓存(Image 组件的 cached 属性)等优化手段。
相关推荐
爱笑的眼睛112 小时前
HarmonyOS通知消息分类管理的深度实践与架构解析
华为·harmonyos
Sirius Wu3 小时前
Rclone实战技巧
分布式
爱笑的眼睛113 小时前
HarmonyOS Menu组件深度自定义:突破默认样式的创新实践
华为·harmonyos
言之。5 小时前
TiDB分布式数据库技术架构概述
数据库·分布式·tidb
老夫的码又出BUG了5 小时前
分布式Web应用场景下存在的Session问题
前端·分布式·后端
杂家5 小时前
Hadoop完全分布式部署(超详细)
大数据·hadoop·分布式
BD_Marathon5 小时前
【Hadoop】hadoop3.3.1完全分布式配置
大数据·hadoop·分布式
Ryan ZX6 小时前
etcd 高可用分布式键值存储
数据库·分布式·etcd
大G的笔记本6 小时前
分布式答案解析
分布式
Tadas-Gao6 小时前
MySQL存储架构解析:从数据无序到索引艺术的演进
数据库·分布式·mysql·微服务·云原生·架构