鸿蒙分布式文件同步助手开发方案

一、项目概述

本方案基于鸿蒙5.0分布式能力,实现跨设备文件列表查看与基础文件操作功能,主要特点包括:

  • 自动发现附近鸿蒙设备
  • 无感权限申请与管理
  • 分布式文件列表浏览
  • 基础文件操作能力

二、技术架构

graph TD
    A[主设备] -->|分布式软总线| B(附近设备1)
    A -->|分布式软总线| C(附近设备2)
    A -->|分布式数据管理| D[文件元数据同步]
    B -->|文件访问代理| E[设备1文件系统]
    C -->|文件访问代理| F[设备2文件系统]

三、核心代码实现

1. 设备发现模块

// DeviceManager.ets
import deviceManager from '@ohos.distributedDeviceManager';
import permission from '@ohos.permission';

export class DistributedDeviceManager {
  private deviceList: Array<DeviceInfo> = [];
  private discovery: deviceManager.DeviceDiscovery;
  
  // 初始化设备发现
  async initDeviceDiscovery() {
    try {
      // 申请必要权限
      await this.requestPermissions();
      
      // 创建设备发现实例
      this.discovery = deviceManager.createDeviceDiscovery({
        subscribeInfo: {
          mode: deviceManager.DiscoveryMode.DISCOVERY_MODE_ACTIVE,
          medium: deviceManager.ExchangeMedium.COMMUNICATION_MEDIUM_WIFI,
          freq: deviceManager.ExchangeFreq.HIGH,
          isSameAccount: false,
          isWakeRemote: true
        }
      });
      
      // 注册设备发现回调
      this.discovery.on('deviceFound', (data) => {
        this.handleDeviceFound(data);
      });
      
      // 开始发现设备
      this.discovery.startDiscovery();
    } catch (err) {
      console.error('设备发现初始化失败:', err);
    }
  }
  
  // 处理发现的设备
  private handleDeviceFound(data: deviceManager.DeviceDiscoveryData) {
    const existingIndex = this.deviceList.findIndex(d => d.deviceId === data.device.id);
    
    if (existingIndex === -1) {
      this.deviceList.push({
        deviceId: data.device.id,
        deviceName: data.device.name,
        deviceType: data.device.type,
        isTrusted: data.device.isTrusted,
        lastSeen: Date.now()
      });
      
      // 触发UI更新
      EventBus.emit('deviceListUpdated', this.deviceList);
    }
  }
  
  // 申请必要权限
  private async requestPermissions() {
    const permissions: Array<string> = [
      permission.DISTRIBUTED_DATASYNC,
      permission.DISTRIBUTED_DEVICE_STATE_CHANGE,
      permission.READ_MEDIA
    ];
    
    const result = await permission.requestPermissionsFromUser(permissions);
    if (result.grantResults.some(grant => grant !== 0)) {
      throw new Error('必要权限未全部授予');
    }
  }
  
  // 获取设备列表
  getDevices(): Array<DeviceInfo> {
    return this.deviceList.filter(device => 
      Date.now() - device.lastSeen < 300000 // 5分钟内活跃的设备
    );
  }
  
  // 停止设备发现
  stopDiscovery() {
    this.discovery?.off('deviceFound');
    this.discovery?.stopDiscovery();
  }
}

2. 文件访问模块

// FileAccessor.ets
import distributedFile from '@ohos.distributedFile';
import fileIO from '@ohos.fileio';

export class DistributedFileAccessor {
  private fileProxyMap: Map<string, distributedFile.FileProxy> = new Map();
  
  // 获取设备文件列表
  async getFileList(deviceId: string, path: string = '/'): Promise<Array<FileInfo>> {
    try {
      const proxy = await this.getFileProxy(deviceId);
      const dir = await proxy.openDir(path);
      const fileList: Array<FileInfo> = [];
      
      let entry;
      while ((entry = await dir.readNext()) !== null) {
        fileList.push({
          name: entry.name,
          path: entry.path,
          size: entry.size,
          isDirectory: entry.isDirectory,
          lastModified: entry.lastModified
        });
      }
      
      await dir.close();
      return fileList;
    } catch (err) {
      console.error(`获取设备${deviceId}文件列表失败:`, err);
      return [];
    }
  }
  
  // 获取文件代理
  private async getFileProxy(deviceId: string): Promise<distributedFile.FileProxy> {
    if (this.fileProxyMap.has(deviceId)) {
      return this.fileProxyMap.get(deviceId);
    }
    
    const proxy = await distributedFile.createFileProxy(deviceId);
    this.fileProxyMap.set(deviceId, proxy);
    return proxy;
  }
  
  // 获取文件内容
  async getFileContent(deviceId: string, filePath: string): Promise<Uint8Array> {
    const proxy = await this.getFileProxy(deviceId);
    const file = await proxy.openFile(filePath, distributedFile.OpenMode.READ);
    const content = await file.read();
    await file.close();
    return content;
  }
  
  // 获取文件缩略图
  async getFileThumbnail(deviceId: string, filePath: string): Promise<image.PixelMap> {
    const content = await this.getFileContent(deviceId, filePath);
    return image.createPixelMap(content.buffer);
  }
  
  // 释放资源
  async release() {
    for (const [_, proxy] of this.fileProxyMap) {
      await proxy.release();
    }
    this.fileProxyMap.clear();
  }
}

3. 分布式数据同步模块

// DataSyncManager.ets
import distributedData from '@ohos.data.distributedData';

const STORE_ID = "file_sync_store";
const FILE_LIST_KEY = "file_list_";

export class DataSyncManager {
  private kvManager: distributedData.KVManager;
  private kvStore: distributedData.KVStore;
  
  async init() {
    const config = {
      bundleName: "com.harmony.distributedfilesync",
      context: getContext(this)
    };
    
    this.kvManager = distributedData.createKVManager(config);
    this.kvStore = await this.kvManager.getKVStore(STORE_ID, {
      createIfMissing: true,
      encrypt: true,
      kvStoreType: distributedData.KVStoreType.SINGLE_VERSION
    });
    
    this.setupDataObserver();
  }
  
  // 同步文件列表
  async syncFileList(deviceId: string, fileList: Array<FileInfo>) {
    const key = `${FILE_LIST_KEY}${deviceId}`;
    try {
      await this.kvStore.put(key, JSON.stringify(fileList));
      
      const syncOptions = {
        devices: [deviceId],
        mode: distributedData.SyncMode.PUSH,
        delay: 0
      };
      await this.kvStore.sync(syncOptions);
    } catch (err) {
      console.error('文件列表同步失败:', err);
    }
  }
  
  // 监听数据变化
  private setupDataObserver() {
    this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, (changes) => {
      changes.insertData.concat(changes.updateData).forEach(item => {
        if (item.key.startsWith(FILE_LIST_KEY)) {
          const deviceId = item.key.substring(FILE_LIST_KEY.length);
          const fileList = JSON.parse(item.value) as Array<FileInfo>;
          AppStorage.setOrCreate(`fileList_${deviceId}`, fileList);
        }
      });
    });
  }
  
  // 获取同步状态
  async getSyncStatus(deviceId: string): Promise<SyncStatus> {
    const key = `${FILE_LIST_KEY}${deviceId}`;
    const status = await this.kvStore.getSyncStatus([deviceId]);
    return {
      lastSyncTime: status[0].lastSyncTime,
      isSyncing: status[0].isSyncing
    };
  }
}

四、完整应用实现

// DistributedFileApp.ets
import { DistributedDeviceManager } from './DeviceManager';
import { DistributedFileAccessor } from './FileAccessor';
import { DataSyncManager } from './DataSyncManager';

@Entry
@Component
struct DistributedFileApp {
  private deviceManager = new DistributedDeviceManager();
  private fileAccessor = new DistributedFileAccessor();
  private dataSync = new DataSyncManager();
  
  @State deviceList: Array<DeviceInfo> = [];
  @State currentDevice?: DeviceInfo;
  @State fileList: Array<FileInfo> = [];
  @State currentPath: string = '/';
  
  aboutToAppear() {
    this.deviceManager.initDeviceDiscovery();
    this.dataSync.init();
    
    // 监听设备列表更新
    EventBus.on('deviceListUpdated', (list) => {
      this.deviceList = list;
    });
    
    // 监听文件列表更新
    AppStorage.on('fileList_', (key, value) => {
      const deviceId = key.substring('fileList_'.length);
      if (this.currentDevice?.deviceId === deviceId) {
        this.fileList = value;
      }
    });
  }
  
  // 选择设备
  async selectDevice(device: DeviceInfo) {
    this.currentDevice = device;
    this.currentPath = '/';
    await this.refreshFileList();
  }
  
  // 刷新文件列表
  async refreshFileList() {
    if (!this.currentDevice) return;
    
    const list = await this.fileAccessor.getFileList(
      this.currentDevice.deviceId,
      this.currentPath
    );
    
    await this.dataSync.syncFileList(this.currentDevice.deviceId, list);
    this.fileList = list;
  }
  
  // 进入目录
  async enterDirectory(path: string) {
    this.currentPath = path;
    await this.refreshFileList();
  }
  
  // 返回上级目录
  async navigateUp() {
    if (this.currentPath === '/') return;
    
    const lastSlash = this.currentPath.lastIndexOf('/');
    this.currentPath = lastSlash === 0 ? '/' : 
      this.currentPath.substring(0, lastSlash);
    
    await this.refreshFileList();
  }
  
  build() {
    Column() {
      // 设备列表
      DeviceList({
        devices: this.deviceList,
        onSelect: (device) => this.selectDevice(device)
      })
      
      // 文件浏览器
      if (this.currentDevice) {
        FileBrowser({
          path: this.currentPath,
          files: this.fileList,
          onEnter: (path) => this.enterDirectory(path),
          onNavigateUp: () => this.navigateUp()
        })
      }
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

@Component
struct DeviceList {
  @Param devices: Array<DeviceInfo>
  @Param onSelect: (device: DeviceInfo) => void
  
  build() {
    List() {
      ForEach(this.devices, (device) => {
        ListItem() {
          Row() {
            Image(device.isTrusted ? 'trusted.png' : 'untrusted.png')
              .width(30)
              .height(30)
            
            Column() {
              Text(device.deviceName)
                .fontSize(18)
              Text(`${device.deviceType} - ${device.deviceId}`)
                .fontSize(12)
                .fontColor(Color.Gray)
            }
            .margin({ left: 10 })
          }
          .onClick(() => this.onSelect(device))
        }
      })
    }
    .height('30%')
    .divider({ strokeWidth: 1, color: '#f0f0f0' })
  }
}

@Component
struct FileBrowser {
  @Param path: string
  @Param files: Array<FileInfo>
  @Param onEnter: (path: string) => void
  @Param onNavigateUp: () => void
  
  build() {
    Column() {
      // 路径导航
      Row() {
        Button('←')
          .onClick(() => this.onNavigateUp())
        
        Text(this.path)
          .margin({ left: 10 })
      }
      
      // 文件列表
      List() {
        ForEach(this.files, (file) => {
          ListItem() {
            Row() {
              Image(file.isDirectory ? 'folder.png' : 'file.png')
                .width(30)
                .height(30)
              
              Column() {
                Text(file.name)
                if (!file.isDirectory) {
                  Text(`${(file.size / 1024).toFixed(1)}KB`)
                    .fontSize(12)
                    .fontColor(Color.Gray)
                }
              }
              .margin({ left: 10 })
            }
            .onClick(() => {
              if (file.isDirectory) {
                this.onEnter(file.path);
              }
            })
          }
        })
      }
      .height('70%')
    }
  }
}

五、关键优化点

1. 设备发现优化

// 智能设备发现频率控制
function optimizeDiscovery() {
  const hour = new Date().getHours();
  const isDaytime = hour >= 8 && hour <= 22;
  
  return {
    freq: isDaytime ? deviceManager.ExchangeFreq.HIGH : 
                     deviceManager.ExchangeFreq.LOW,
    duration: isDaytime ? 60000 : 30000 // 白天1分钟,夜间30秒
  };
}

2. 文件列表缓存策略

// 文件列表缓存管理
class FileListCache {
  private cache: Map<string, { timestamp: number, data: Array<FileInfo> }> = new Map();
  private static TTL = 300000; // 5分钟
  
  get(deviceId: string, path: string): Array<FileInfo> | null {
    const key = `${deviceId}:${path}`;
    const item = this.cache.get(key);
    
    if (item && Date.now() - item.timestamp < FileListCache.TTL) {
      return item.data;
    }
    return null;
  }
  
  set(deviceId: string, path: string, data: Array<FileInfo>) {
    const key = `${deviceId}:${path}`;
    this.cache.set(key, {
      timestamp: Date.now(),
      data: data
    });
  }
  
  clearExpired() {
    const now = Date.now();
    for (const [key, item] of this.cache) {
      if (now - item.timestamp > FileListCache.TTL) {
        this.cache.delete(key);
      }
    }
  }
}

3. 分布式数据同步优化

// 差异同步策略
async syncFileListDiff(deviceId: string, newList: Array<FileInfo>) {
  const oldList = await this.getCachedFileList(deviceId);
  const diff = this.calculateDiff(oldList, newList);
  
  if (diff.added.length > 0 || diff.removed.length > 0) {
    await this.kvStore.put(`${FILE_LIST_KEY}${deviceId}`, JSON.stringify(newList));
    await this.syncChanges(deviceId, diff);
  }
}

private calculateDiff(oldList: Array<FileInfo>, newList: Array<FileInfo>) {
  const oldMap = new Map(oldList.map(f => [f.path, f]));
  const newMap = new Map(newList.map(f => [f.path, f]));
  
  const added = newList.filter(f => !oldMap.has(f.path));
  const removed = oldList.filter(f => !newMap.has(f.path));
  const changed = newList.filter(f => {
    const oldFile = oldMap.get(f.path);
    return oldFile && (oldFile.size !== f.size || oldFile.lastModified !== f.lastModified);
  });
  
  return { added, removed, changed };
}

六、测试验证方案

1. 设备发现测试

// 模拟设备发现测试
function testDeviceDiscovery() {
  const mockDevices = [
    { id: 'device1', name: 'Test Device 1', type: 'phone' },
    { id: 'device2', name: 'Test Device 2', type: 'tablet' }
  ];
  
  deviceManager.simulateDiscovery(mockDevices);
  
  setTimeout(() => {
    const discovered = deviceManager.getDevices();
    console.assert(
      discovered.length === mockDevices.length,
      '设备发现数量不匹配'
    );
  }, 1000);
}

2. 文件同步测试

// 文件列表同步测试
async function testFileSync() {
  const testFiles = [
    { name: 'test1.txt', path: '/test1.txt', size: 1024 },
    { name: 'test2.jpg', path: '/test2.jpg', size: 2048 }
  ];
  
  await dataSync.syncFileList('testDevice', testFiles);
  const synced = await dataSync.getSyncedFileList('testDevice');
  
  console.assert(
    JSON.stringify(synced) === JSON.stringify(testFiles),
    '文件列表同步不一致'
  );
}

3. 权限测试

// 权限申请测试
async function testPermission() {
  try {
    await permission.requestPermissionsFromUser([
      'ohos.permission.DISTRIBUTED_DATASYNC'
    ]);
    
    const result = await permission.checkPermission(
      'ohos.permission.DISTRIBUTED_DATASYNC'
    );
    
    console.assert(
      result === permission.GrantStatus.PERMISSION_GRANTED,
      '权限未正确授予'
    );
  } catch (err) {
    console.error('权限测试失败:', err);
  }
}

七、项目扩展方向

1. 文件传输功能

// 跨设备文件传输
async transferFile(sourceDevice: string, targetDevice: string, filePath: string) {
  const content = await fileAccessor.getFileContent(sourceDevice, filePath);
  const targetProxy = await fileAccessor.getFileProxy(targetDevice);
  const targetFile = await targetProxy.openFile(filePath, distributedFile.OpenMode.WRITE);
  await targetFile.write(content);
  await targetFile.close();
}

2. 分布式搜索功能

// 跨设备文件搜索
async searchFiles(keyword: string) {
  const results: Array<{device: DeviceInfo, file: FileInfo}> = [];
  
  for (const device of deviceManager.getDevices()) {
    const files = await fileAccessor.search(device.deviceId, keyword);
    results.push(...files.map(file => ({ device, file })));
  }
  
  return results;
}

3. 安全沙箱增强

// 文件访问安全控制
class FileAccessSecurity {
  private static ALLOWED_TYPES = ['.jpg', '.png', '.txt', '.pdf'];
  
  static isAllowed(file: FileInfo): boolean {
    return this.ALLOWED_TYPES.some(ext => file.name.endsWith(ext));
  }
  
  static filterFileList(files: Array<FileInfo>): Array<FileInfo> {
    return files.filter(file => 
      file.isDirectory || this.isAllowed(file)
    );
  }
}

本方案充分利用鸿蒙分布式能力,实现了跨设备文件浏览的基础功能,通过优化的设备发现机制、智能缓存策略和差异同步技术,提供了流畅的用户体验。系统架构设计考虑了扩展性,可方便地添加文件传输、分布式搜索等高级功能,是鸿蒙生态中分布式文件管理的典型实现。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐