openharmony之分布式相机开发:预览\拍照\编辑\同步\删除\分享教程
一、环境准备
- 硬件与系统要求
- 设备: 至少两台搭载摄像头的 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
文件中,必须声明以下权限,否则应用会因权限不足而崩溃。{"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
单例类来统一管理设备相关的逻辑。// 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
组件来承载相机预览流。// 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();// 【关键】创建 CaptureSessionthis.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
中,我们添加拍照输出和拍照逻辑。// 在 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。// 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
中,拍照成功后可以跳转到编辑页面。// 在 takePicture 函数的回调中 this.photoUri = photo.main.uri; promptAction.showToast({ message: '拍照成功!' }); router.pushUrl({url: 'pages/ImageEditor',params: { uri: this.photoUri } });
六、分布式图库同步与删除
如前文所述,仅依赖分布式文件系统扫描是不可靠的。我们采用 分布式文件系统 + 分布式数据库 (RDB) 的混合架构。
- 创建 RDB 辅助类 (
RDBHelper.ets
)// 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
)// 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。// 在 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
)// 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
的长按菜单中添加分享按钮。// 在 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
)初始化并订阅分享事件。// 在 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
属性)等优化手段。