一、环境准备
- 硬件与系统要求
- 设备 : 至少两台搭载摄像头的 OpenHarmony 设备,例如 RK3568、RK3566、RK3588 开发板等。真机调试是分布式功能开发的必要条件。
- 系统版本 : OpenHarmony 5.0 Release (5.0.0.71) 或更高版本。不同版本间 API 可能有差异,请务必保持一致。
- 网络 : 所有设备必须连接到 同一个局域网 (LAN) 内,这是设备发现和通信的基础。
- 开发工具
- IDE : DevEco Studio 5.0.3.910 或更高版本。
- SDK : Full SDK (API Version 12+)。相机、分布式硬件等系统级能力需要 Full SDK 支持,Public SDK 不包含这些接口。
二、分布式设备认证与连接
分布式操作的第一步是让设备之间相互发现并建立信任关系。
-
权限声明
在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" } } ] } } -
设备发现与认证 (完整封装)
我们创建一个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 来兼容分布式相机。
-
创建预览界面 (
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.'); }) } } } }
四、拍照与保存
-
集成拍照功能
在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(); }) -
保存到分布式文件系统
photoOutput.capture回调中返回的photo.main.uri已经是一个指向分布式文件路径的 URI。OpenHarmony 的相机模块会自动将照片保存到应用的分布式目录下 (/data/storage/el2/distributedfiles/)。
五、图像编辑(修图)
我们创建一个新的页面 ImageEditor.ets 来编辑图片。
-
创建编辑页面
从相机页面跳转过来,并传入图片 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) } } -
从相机页面跳转
在CameraPreview.ets中,拍照成功后可以跳转到编辑页面。typescript// 在 takePicture 函数的回调中 this.photoUri = photo.main.uri; promptAction.showToast({ message: '拍照成功!' }); router.pushUrl({ url: 'pages/ImageEditor', params: { uri: this.photoUri } });
六、分布式图库同步与删除
如前文所述,仅依赖分布式文件系统扫描是不可靠的。我们采用 分布式文件系统 + 分布式数据库 (RDB) 的混合架构。
-
创建 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}`); } } -
创建图库页面 (
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(); }) } } } -
在拍照后同步到 RDB
修改CameraPreview.ets的takePicture函数,拍照成功后将信息存入 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) 来发送一个临时的分享意图。
-
创建分享管理器 (
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); } }); } } -
在图库页面集成分享
在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: () => {} } ] }) -
接收分享
在应用启动时(如UIAbility的onCreate或主页面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: () => {} } ] }); }); }); // ...
八、完整流程与项目结构
-
应用启动流程
EntryAbility启动,初始化RDBHelper和ShareManager,并订阅分享事件。- 进入主页面(例如
Index.ets),提供进入相机和图库的入口。
-
相机流程
- 进入
CameraPreview.ets,初始化DeviceManager,发现并选择设备(本地或远程)。 - 使用
CaptureSession配置并启动预览。 - 点击拍照,
PhotoOutput捕获图片,自动保存到分布式目录,并将 URI 和设备 ID 存入分布式 RDB。 - 可选择跳转到
ImageEditor.ets进行编辑,编辑后保存为新文件。
- 进入
-
图库流程
- 进入
Gallery.ets,从分布式 RDB 查询所有图片元数据列表并展示。 - 点击图片可预览或编辑。
- 长按图片可分享(通过 KV Store 发送 URI)或删除(同时删除文件和 RDB 记录)。
- 其他设备上的应用会通过 KV Store 的订阅收到分享通知。
- 进入
-
项目结构示例
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属性)等优化手段。