HarmonyOS开发中离线优先策略:本地数据优先

举报
Jack20 发表于 2026/06/25 21:26:43 2026/06/25
【摘要】 离线优先策略:本地数据优先网络不可怕,没网才可怕——离线优先让应用永不掉线 一、背景与动机:为什么需要离线优先?想象一下这个场景:用户在地铁上看你的 App,正刷得开心,突然进隧道了——没网了。传统做法:页面显示"网络错误",用户只能干瞪眼。离线优先做法:页面继续显示内容,只是右上角有个小图标提示"离线模式"。等出了隧道有网了,数据自动更新。这就是**离线优先(Offline-First)...

离线优先策略:本地数据优先

网络不可怕,没网才可怕——离线优先让应用永不掉线

一、背景与动机:为什么需要离线优先?

想象一下这个场景:

用户在地铁上看你的 App,正刷得开心,突然进隧道了——没网了

传统做法:页面显示"网络错误",用户只能干瞪眼。

离线优先做法:页面继续显示内容,只是右上角有个小图标提示"离线模式"。等出了隧道有网了,数据自动更新。

这就是**离线优先(Offline-First)**的核心理念:把离线当作正常状态,而不是异常状态

离线优先要解决什么问题?

  1. 数据可用性:没网也能看数据
  2. 操作连续性:没网也能操作,有网后同步
  3. 体验一致性:在线离线体验无缝切换
  4. 数据一致性:本地数据与服务器最终一致

离线优先 vs 传统模式

flowchart TB
    subgraph 传统模式
        A1[用户操作] --> B1{有网络?}
        B1 -->|| C1[请求服务器]
        B1 -->|| D1[显示错误]
        C1 --> E1[显示数据]
    end
    
    subgraph 离线优先
        A2[用户操作] --> B2[读取本地数据]
        B2 --> C2[立即显示]
        C2 --> D2{有网络?}
        D2 -->|| E2[请求服务器]
        D2 -->|| F2[标记离线模式]
        E2 --> G2[更新本地数据]
        G2 --> H2[刷新显示]
    end
    
    classDef primary fill:#4A90E2,stroke:#2E5C8A,color:#fff
    classDef warning fill:#F5A623,stroke:#C17D10,color:#fff
    classDef error fill:#E74C3C,stroke:#C0392B,color:#fff
    classDef info fill:#7ED321,stroke:#5BA318,color:#fff
    
    class A1,A2,B2,C2 primary
    class B1,D2 info
    class C1,E1,E2,G2,H2 warning
    class D1,F2 error

二、核心原理:离线优先架构

离线优先不是"加个缓存"就完事,而是一套完整的架构:

flowchart TB
    subgraph 数据层
        A[本地数据库<br/>RDB/Preferences]
        B[操作队列<br/>待同步操作]
        C[数据版本<br/>冲突检测]
    end
    
    subgraph 同步层
        D[同步管理器<br/>协调同步]
        E[冲突解决器<br/>处理冲突]
        F[网络检测器<br/>监听网络状态]
    end
    
    subgraph 业务层
        G[数据访问接口<br/>统一读写]
        H[离线操作队列<br/>暂存操作]
        I[状态同步<br/>UI更新]
    end
    
    G --> A
    G --> B
    G --> C
    
    D --> A
    D --> E
    D --> F
    
    H --> B
    I --> G
    
    F --> D
    D --> H
    
    classDef primary fill:#4A90E2,stroke:#2E5C8A,color:#fff
    classDef warning fill:#F5A623,stroke:#C17D10,color:#fff
    classDef error fill:#E74C3C,stroke:#C0392B,color:#fff
    classDef info fill:#7ED321,stroke:#5BA318,color:#fff
    
    class A,B,C primary
    class D,E,F warning
    class G,H,I info

三层协作

  1. 数据层:本地存储、版本控制、操作队列
  2. 同步层:网络检测、数据同步、冲突解决
  3. 业务层:数据访问、离线操作、UI更新

三、代码实战:完整离线优先实现

示例1:网络状态检测器

// network/offline/NetworkDetector.ets
import connection from '@ohos.net.connection';

/**
 * 网络状态检测器
 * 实时监听网络状态变化
 */
export class NetworkDetector {
  private isConnected: boolean = false;
  private connectionType: connection.NetConnectionType = connection.NetConnectionType.UNKNOWN;
  private listeners: Array<(connected: boolean) => void> = [];
  private netHandle: connection.NetHandle | null = null;

  /**
   * 初始化网络检测
   */
  async init(): Promise<void> {
    try {
      // 获取当前网络状态
      this.netHandle = await connection.getDefaultNet();
      const capabilities = await connection.getNetCapabilities(this.netHandle);
      
      this.isConnected = capabilities !== null;
      this.connectionType = this.getConnectionType(capabilities);
      
      console.info(`[NetworkDetector] 初始化完成, 已连接: ${this.isConnected}, 类型: ${this.connectionType}`);

      // 注册网络状态变化监听
      connection.on('netConnectionChange', (data) => {
        this.handleNetworkChange(data);
      });
    } catch (error) {
      console.error('[NetworkDetector] 初始化失败:', error);
      this.isConnected = false;
    }
  }

  /**
   * 处理网络状态变化
   * @private
   */
  private handleNetworkChange(data: connection.NetConnectionChangeInfo): void {
    const wasConnected = this.isConnected;
    
    this.isConnected = data.netHandle !== null;
    
    console.info(`[NetworkDetector] 网络状态变化: ${wasConnected} -> ${this.isConnected}`);

    // 通知所有监听器
    if (wasConnected !== this.isConnected) {
      this.listeners.forEach(listener => {
        try {
          listener(this.isConnected);
        } catch (error) {
          console.error('[NetworkDetector] 监听器执行失败:', error);
        }
      });
    }
  }

  /**
   * 获取连接类型
   * @private
   */
  private getConnectionType(capabilities: connection.NetCapabilities): connection.NetConnectionType {
    if (capabilities?.bearerTypes?.includes(connection.NetBearType.BEARER_WIFI)) {
      return connection.NetConnectionType.WIFI;
    }
    if (capabilities?.bearerTypes?.includes(connection.NetBearType.BEARER_CELLULAR)) {
      return connection.NetConnectionType.MOBILE;
    }
    return connection.NetConnectionType.UNKNOWN;
  }

  /**
   * 添加网络状态监听器
   */
  addListener(listener: (connected: boolean) => void): void {
    this.listeners.push(listener);
  }

  /**
   * 移除网络状态监听器
   */
  removeListener(listener: (connected: boolean) => void): void {
    const index = this.listeners.indexOf(listener);
    if (index > -1) {
      this.listeners.splice(index, 1);
    }
  }

  /**
   * 当前是否在线
   */
  get isOnline(): boolean {
    return this.isConnected;
  }

  /**
   * 当前是否离线
   */
  get isOffline(): boolean {
    return !this.isConnected;
  }

  /**
   * 获取连接类型
   */
  get type(): connection.NetConnectionType {
    return this.connectionType;
  }

  /**
   * 销毁检测器
   */
  destroy(): void {
    connection.off('netConnectionChange');
    this.listeners = [];
  }
}

示例2:离线操作队列

// network/offline/OperationQueue.ets
import { preferences } from '@kit.ArkData';

/**
 * 操作类型枚举
 */
export enum OperationType {
  CREATE = 'create',
  UPDATE = 'update',
  DELETE = 'delete'
}

/**
 * 离线操作结构
 */
export interface OfflineOperation {
  /** 操作ID */
  id: string;
  /** 操作类型 */
  type: OperationType;
  /** 资源类型(如 'product', 'order') */
  resource: string;
  /** 资源ID */
  resourceId: string;
  /** 操作数据 */
  data: any;
  /** 创建时间 */
  timestamp: number;
  /** 重试次数 */
  retryCount: number;
  /** 状态 */
  status: 'pending' | 'syncing' | 'failed' | 'completed';
}

/**
 * 离线操作队列
 * 管理待同步的操作
 */
export class OperationQueue {
  private store: preferences.Preferences | null = null;
  private queue: OfflineOperation[] = [];
  private maxRetry: number = 3;

  /**
   * 初始化操作队列
   */
  async init(): Promise<void> {
    this.store = await preferences.getPreferences('offline_queue');
    
    // 加载持久化的操作
    const json = await this.store.get('queue', '[]') as string;
    this.queue = JSON.parse(json);
    
    console.info(`[OperationQueue] 初始化完成, 待同步操作: ${this.queue.length}`);
  }

  /**
   * 添加操作到队列
   */
  async add(
    type: OperationType,
    resource: string,
    resourceId: string,
    data: any
  ): Promise<OfflineOperation> {
    const operation: OfflineOperation = {
      id: this.generateId(),
      type,
      resource,
      resourceId,
      data,
      timestamp: Date.now(),
      retryCount: 0,
      status: 'pending'
    };

    this.queue.push(operation);
    await this.persist();

    console.info(`[OperationQueue] 添加操作: ${operation.id}, 类型: ${type}, 资源: ${resource}`);

    return operation;
  }

  /**
   * 获取待同步操作
   */
  getPendingOperations(): OfflineOperation[] {
    return this.queue.filter(op => op.status === 'pending');
  }

  /**
   * 获取指定资源的所有待同步操作
   */
  getOperationsByResource(resource: string): OfflineOperation[] {
    return this.queue.filter(op => op.resource === resource && op.status === 'pending');
  }

  /**
   * 标记操作为同步中
   */
  async markSyncing(operationId: string): Promise<void> {
    const operation = this.queue.find(op => op.id === operationId);
    if (operation) {
      operation.status = 'syncing';
      await this.persist();
    }
  }

  /**
   * 标记操作为完成
   */
  async markCompleted(operationId: string): Promise<void> {
    const index = this.queue.findIndex(op => op.id === operationId);
    if (index > -1) {
      this.queue.splice(index, 1);
      await this.persist();
      console.info(`[OperationQueue] 操作完成: ${operationId}`);
    }
  }

  /**
   * 标记操作为失败
   */
  async markFailed(operationId: string, error: any): Promise<void> {
    const operation = this.queue.find(op => op.id === operationId);
    if (operation) {
      operation.retryCount++;
      
      if (operation.retryCount >= this.maxRetry) {
        // 达到最大重试次数,标记为失败
        operation.status = 'failed';
        console.error(`[OperationQueue] 操作失败(已达最大重试): ${operationId}`, error);
      } else {
        // 重置为待同步,等待下次重试
        operation.status = 'pending';
        console.warn(`[OperationQueue] 操作失败(将重试 ${operation.retryCount}/${this.maxRetry}): ${operationId}`);
      }
      
      await this.persist();
    }
  }

  /**
   * 清空队列
   */
  async clear(): Promise<void> {
    this.queue = [];
    await this.persist();
  }

  /**
   * 获取队列统计信息
   */
  getStats() {
    return {
      total: this.queue.length,
      pending: this.queue.filter(op => op.status === 'pending').length,
      syncing: this.queue.filter(op => op.status === 'syncing').length,
      failed: this.queue.filter(op => op.status === 'failed').length
    };
  }

  /**
   * 持久化队列
   * @private
   */
  private async persist(): Promise<void> {
    if (this.store) {
      const json = JSON.stringify(this.queue);
      await this.store.put('queue', json);
      await this.store.flush();
    }
  }

  /**
   * 生成操作ID
   * @private
   */
  private generateId(): string {
    return `op_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}

示例3:同步管理器

// network/offline/SyncManager.ets
import { NetworkDetector } from './NetworkDetector';
import { OperationQueue, OfflineOperation, OperationType } from './OperationQueue';
import { HttpClient } from '../HttpClient';

/**
 * 同步配置
 */
export interface SyncConfig {
  /** 自动同步间隔(毫秒) */
  autoSyncInterval?: number;
  /** 最大重试次数 */
  maxRetry?: number;
  /** 同步处理器映射 */
  handlers: Map<string, SyncHandler>;
}

/**
 * 同步处理器接口
 */
export interface SyncHandler {
  /** 处理创建操作 */
  handleCreate?(data: any): Promise<any>;
  /** 处理更新操作 */
  handleUpdate?(resourceId: string, data: any): Promise<any>;
  /** 处理删除操作 */
  handleDelete?(resourceId: string): Promise<void>;
}

/**
 * 同步管理器
 * 协调离线操作的同步
 */
export class SyncManager {
  private networkDetector: NetworkDetector;
  private operationQueue: OperationQueue;
  private config: SyncConfig;
  private syncing: boolean = false;
  private syncTimer: number = -1;

  constructor(
    networkDetector: NetworkDetector,
    operationQueue: OperationQueue,
    config: SyncConfig
  ) {
    this.networkDetector = networkDetector;
    this.operationQueue = operationQueue;
    this.config = config;
  }

  /**
   * 初始化同步管理器
   */
  async init(): Promise<void> {
    // 监听网络状态变化
    this.networkDetector.addListener((connected) => {
      if (connected) {
        console.info('[SyncManager] 网络恢复,开始同步');
        this.sync();
      }
    });

    // 启动自动同步
    if (this.config.autoSyncInterval) {
      this.startAutoSync(this.config.autoSyncInterval);
    }

    // 如果当前在线,立即同步
    if (this.networkDetector.isOnline) {
      await this.sync();
    }

    console.info('[SyncManager] 初始化完成');
  }

  /**
   * 启动自动同步
   */
  startAutoSync(interval: number): void {
    if (this.syncTimer !== -1) {
      clearInterval(this.syncTimer);
    }

    this.syncTimer = setInterval(() => {
      if (this.networkDetector.isOnline && !this.syncing) {
        this.sync();
      }
    }, interval);

    console.info(`[SyncManager] 自动同步已启动,间隔: ${interval}ms`);
  }

  /**
   * 停止自动同步
   */
  stopAutoSync(): void {
    if (this.syncTimer !== -1) {
      clearInterval(this.syncTimer);
      this.syncTimer = -1;
      console.info('[SyncManager] 自动同步已停止');
    }
  }

  /**
   * 执行同步
   */
  async sync(): Promise<void> {
    // 检查是否在线
    if (this.networkDetector.isOffline) {
      console.warn('[SyncManager] 离线状态,跳过同步');
      return;
    }

    // 检查是否正在同步
    if (this.syncing) {
      console.warn('[SyncManager] 正在同步中,跳过');
      return;
    }

    this.syncing = true;
    console.info('[SyncManager] 开始同步...');

    try {
      // 获取待同步操作
      const operations = this.operationQueue.getPendingOperations();
      
      if (operations.length === 0) {
        console.info('[SyncManager] 无待同步操作');
        return;
      }

      console.info(`[SyncManager] 待同步操作数: ${operations.length}`);

      // 逐个执行同步
      for (const operation of operations) {
        await this.syncOperation(operation);
      }

      console.info('[SyncManager] 同步完成');
    } catch (error) {
      console.error('[SyncManager] 同步失败:', error);
    } finally {
      this.syncing = false;
    }
  }

  /**
   * 同步单个操作
   * @private
   */
  private async syncOperation(operation: OfflineOperation): Promise<void> {
    // 标记为同步中
    await this.operationQueue.markSyncing(operation.id);

    try {
      // 获取对应的处理器
      const handler = this.config.handlers.get(operation.resource);
      
      if (!handler) {
        console.error(`[SyncManager] 未找到处理器: ${operation.resource}`);
        await this.operationQueue.markFailed(operation.id, 'Handler not found');
        return;
      }

      // 执行同步
      let result: any;
      
      switch (operation.type) {
        case OperationType.CREATE:
          result = await handler.handleCreate?.(operation.data);
          break;
        case OperationType.UPDATE:
          result = await handler.handleUpdate?.(operation.resourceId, operation.data);
          break;
        case OperationType.DELETE:
          await handler.handleDelete?.(operation.resourceId);
          break;
      }

      // 标记为完成
      await this.operationQueue.markCompleted(operation.id);
      
      console.info(`[SyncManager] 操作同步成功: ${operation.id}`);
    } catch (error) {
      // 标记为失败
      await this.operationQueue.markFailed(operation.id, error);
    }
  }

  /**
   * 获取同步状态
   */
  getStatus() {
    return {
      isOnline: this.networkDetector.isOnline,
      syncing: this.syncing,
      queueStats: this.operationQueue.getStats()
    };
  }
}

示例4:离线数据访问层

// network/offline/OfflineDataAccess.ets
import relationalStore from '@ohos.data.relationalStore';
import { NetworkDetector } from './NetworkDetector';
import { OperationQueue, OperationType } from './OperationQueue';

/**
 * 离线数据访问层
 * 提供统一的数据读写接口,自动处理离线场景
 */
export class OfflineDataAccess<T extends { id: string }> {
  private rdbStore: relationalStore.RdbStore | null = null;
  private tableName: string;
  private networkDetector: NetworkDetector;
  private operationQueue: OperationQueue;
  private apiEndpoint: string;

  constructor(
    tableName: string,
    apiEndpoint: string,
    networkDetector: NetworkDetector,
    operationQueue: OperationQueue
  ) {
    this.tableName = tableName;
    this.apiEndpoint = apiEndpoint;
    this.networkDetector = networkDetector;
    this.operationQueue = operationQueue;
  }

  /**
   * 初始化数据访问层
   */
  async init(): Promise<void> {
    // 创建RDB存储
    const config: relationalStore.StoreConfig = {
      name: 'offline_data.db',
      securityLevel: relationalStore.SecurityLevel.S1
    };

    this.rdbStore = await relationalStore.getRdbStore(config);

    // 创建表
    const sql = `
      CREATE TABLE IF NOT EXISTS ${this.tableName} (
        id TEXT PRIMARY KEY,
        data TEXT NOT NULL,
        version INTEGER DEFAULT 0,
        updated_at INTEGER NOT NULL,
        is_local INTEGER DEFAULT 0
      )
    `;
    await this.rdbStore.executeSql(sql);

    console.info(`[OfflineDataAccess] 初始化完成: ${this.tableName}`);
  }

  /**
   * 查询单条数据
   * 离线优先:先查本地,有网时后台更新
   */
  async get(id: string): Promise<T | null> {
    // 1. 查询本地数据
    const local = await this.getLocal(id);
    
    // 2. 如果在线,后台更新
    if (this.networkDetector.isOnline) {
      this.fetchFromServer(id).then(serverData => {
        if (serverData) {
          this.saveLocal(serverData, false);
        }
      }).catch(error => {
        console.error('[OfflineDataAccess] 后台更新失败:', error);
      });
    }

    // 3. 返回本地数据
    return local;
  }

  /**
   * 查询所有数据
   */
  async getAll(): Promise<T[]> {
    // 查询本地数据
    const locals = await this.getAllLocal();

    // 如果在线,后台更新
    if (this.networkDetector.isOnline) {
      this.fetchAllFromServer().then(serverData => {
        this.saveAllLocal(serverData, false);
      }).catch(error => {
        console.error('[OfflineDataAccess] 后台更新失败:', error);
      });
    }

    return locals;
  }

  /**
   * 创建数据
   * 离线优先:先存本地,有网时同步
   */
  async create(data: T): Promise<T> {
    // 生成临时ID(如果是离线创建)
    if (!data.id || this.networkDetector.isOffline) {
      data.id = this.generateTempId();
    }

    // 保存到本地
    await this.saveLocal(data, true);

    // 添加到操作队列
    await this.operationQueue.add(
      OperationType.CREATE,
      this.tableName,
      data.id,
      data
    );

    // 如果在线,立即同步
    if (this.networkDetector.isOnline) {
      // 同步会在SyncManager中自动执行
    }

    return data;
  }

  /**
   * 更新数据
   */
  async update(id: string, data: Partial<T>): Promise<T | null> {
    // 获取现有数据
    const existing = await this.getLocal(id);
    if (!existing) {
      return null;
    }

    // 合并数据
    const updated = { ...existing, ...data, updatedAt: Date.now() };

    // 保存到本地
    await this.saveLocal(updated, true);

    // 添加到操作队列
    await this.operationQueue.add(
      OperationType.UPDATE,
      this.tableName,
      id,
      updated
    );

    return updated;
  }

  /**
   * 删除数据
   */
  async delete(id: string): Promise<void> {
    // 从本地删除
    await this.deleteLocal(id);

    // 添加到操作队列
    await this.operationQueue.add(
      OperationType.DELETE,
      this.tableName,
      id,
      null
    );
  }

  /**
   * 从本地获取数据
   * @private
   */
  private async getLocal(id: string): Promise<T | null> {
    if (!this.rdbStore) return null;

    const predicates = new relationalStore.RdbPredicates(this.tableName);
    predicates.equalTo('id', id);

    const resultSet = await this.rdbStore.query(predicates);
    
    if (resultSet.goToFirstRow()) {
      const json = resultSet.getString(resultSet.getColumnIndex('data'));
      resultSet.close();
      return JSON.parse(json);
    }

    resultSet.close();
    return null;
  }

  /**
   * 从本地获取所有数据
   * @private
   */
  private async getAllLocal(): Promise<T[]> {
    if (!this.rdbStore) return [];

    const predicates = new relationalStore.RdbPredicates(this.tableName);
    const resultSet = await this.rdbStore.query(predicates);

    const items: T[] = [];
    while (resultSet.goToNextRow()) {
      const json = resultSet.getString(resultSet.getColumnIndex('data'));
      items.push(JSON.parse(json));
    }

    resultSet.close();
    return items;
  }

  /**
   * 保存到本地
   * @private
   */
  private async saveLocal(data: T, isLocal: boolean): Promise<void> {
    if (!this.rdbStore) return;

    const valueBucket: relationalStore.ValuesBucket = {
      id: data.id,
      data: JSON.stringify(data),
      version: 0,
      updated_at: Date.now(),
      is_local: isLocal ? 1 : 0
    };

    // 使用REPLACE语义(存在则更新,不存在则插入)
    await this.rdbStore.insert(this.tableName, valueBucket, relationalStore.ConflictResolution.ON_CONFLICT_REPLACE);
  }

  /**
   * 批量保存到本地
   * @private
   */
  private async saveAllLocal(data: T[], isLocal: boolean): Promise<void> {
    for (const item of data) {
      await this.saveLocal(item, isLocal);
    }
  }

  /**
   * 从本地删除
   * @private
   */
  private async deleteLocal(id: string): Promise<void> {
    if (!this.rdbStore) return;

    const predicates = new relationalStore.RdbPredicates(this.tableName);
    predicates.equalTo('id', id);

    await this.rdbStore.delete(predicates);
  }

  /**
   * 从服务器获取数据
   * @private
   */
  private async fetchFromServer(id: string): Promise<T | null> {
    // 这里应该调用实际的API
    // 简化示例,返回null
    return null;
  }

  /**
   * 从服务器获取所有数据
   * @private
   */
  private async fetchAllFromServer(): Promise<T[]> {
    // 这里应该调用实际的API
    // 简化示例,返回空数组
    return [];
  }

  /**
   * 生成临时ID
   * @private
   */
  private generateTempId(): string {
    return `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}

示例5:页面中的离线优先实践

// pages/ProductListPage.ets
import { NetworkDetector } from '../network/offline/NetworkDetector';
import { OfflineDataAccess } from '../network/offline/OfflineDataAccess';

interface Product {
  id: string;
  name: string;
  price: number;
  image: string;
}

@Entry
@Component
struct ProductListPage {
  @State products: Product[] = [];
  @State loading: boolean = true;
  @State isOffline: boolean = false;
  @State pendingCount: number = 0;

  private networkDetector: NetworkDetector = new NetworkDetector();
  private productAccess: OfflineDataAccess<Product>;

  aboutToAppear() {
    this.initOffline();
  }

  /**
   * 初始化离线功能
   */
  async initOffline() {
    // 初始化网络检测
    await this.networkDetector.init();

    // 监听网络状态
    this.networkDetector.addListener((connected) => {
      this.isOffline = !connected;
      
      if (connected) {
        // 网络恢复,刷新数据
        this.loadProducts();
      }
    });

    // 初始化离线数据访问
    this.productAccess = new OfflineDataAccess(
      'products',
      '/api/products',
      this.networkDetector,
      new OperationQueue()
    );
    await this.productAccess.init();

    // 加载数据
    this.isOffline = this.networkDetector.isOffline;
    await this.loadProducts();
  }

  /**
   * 加载商品列表
   */
  async loadProducts() {
    this.loading = true;

    try {
      // 离线优先:从本地读取
      const products = await this.productAccess.getAll();
      this.products = products;
    } catch (error) {
      console.error('加载失败:', error);
    } finally {
      this.loading = false;
    }
  }

  /**
   * 添加商品(支持离线)
   */
  async addProduct() {
    const newProduct: Product = {
      id: '',
      name: '新商品',
      price: 99.9,
      image: ''
    };

    try {
      // 离线优先:先存本地
      const created = await this.productAccess.create(newProduct);
      
      // 更新UI
      this.products = [...this.products, created];
      
      // 提示用户
      if (this.isOffline) {
        promptAction.showToast({
          message: '已保存到本地,联网后自动同步',
          duration: 2000
        });
      }
    } catch (error) {
      console.error('添加失败:', error);
    }
  }

  build() {
    Column() {
      // 顶部状态栏
      this.statusBar();

      // 商品列表
      if (this.loading) {
        LoadingProgress()
          .width(48)
          .height(48)
      } else if (this.products.length === 0) {
        this.emptyState();
      } else {
        this.productList();
      }

      // 添加按钮
      Button('添加商品')
        .type(ButtonType.Capsule)
        .backgroundColor('#4A90E2')
        .fontColor(Color.White)
        .width('90%')
        .margin({ top: 16, bottom: 16 })
        .onClick(() => {
          this.addProduct();
        })
    }
    .width('100%')
    .height('100%')
  }

  /**
   * 状态栏
   */
  @Builder
  statusBar() {
    Row() {
      Text('商品列表')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .layoutWeight(1)

      // 离线指示器
      if (this.isOffline) {
        Row() {
          Image($r('app.media.ic_offline'))
            .width(16)
            .height(16)
          Text('离线')
            .fontSize(12)
            .fontColor('#E74C3C')
            .margin({ left: 4 })
        }
        .padding({ left: 8, right: 8, top: 4, bottom: 4 })
        .backgroundColor('#FFEBEE')
        .borderRadius(12)
      }

      // 待同步指示器
      if (this.pendingCount > 0) {
        Row() {
          Text(`${this.pendingCount}个待同步`)
            .fontSize(12)
            .fontColor('#F5A623')
        }
        .padding({ left: 8, right: 8, top: 4, bottom: 4 })
        .backgroundColor('#FFF8E1')
        .borderRadius(12)
        .margin({ left: 8 })
      }
    }
    .width('100%')
    .height(56)
    .padding({ left: 16, right: 16 })
  }

  /**
   * 空状态
   */
  @Builder
  emptyState() {
    Column() {
      Image($r('app.media.ic_empty'))
        .width(100)
        .height(100)

      Text('暂无商品')
        .fontSize(16)
        .fontColor('#999999')
        .margin({ top: 16 })

      if (this.isOffline) {
        Text('联网后可同步数据')
          .fontSize(14)
          .fontColor('#CCCCCC')
          .margin({ top: 8 })
      }
    }
    .width('100%')
    .layoutWeight(1)
    .justifyContent(FlexAlign.Center)
  }

  /**
   * 商品列表
   */
  @Builder
  productList() {
    List() {
      ForEach(this.products, (product: Product) => {
        ListItem() {
          Row() {
            Image(product.image || $r('app.media.ic_product'))
              .width(60)
              .height(60)
              .borderRadius(8)

            Column() {
              Text(product.name)
                .fontSize(16)
                .fontWeight(FontWeight.Bold)

              Text(`¥${product.price}`)
                .fontSize(14)
                .fontColor('#E74C3C')
                .margin({ top: 4 })
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Start)
            .margin({ left: 12 })
          }
          .width('100%')
          .padding(12)
          .backgroundColor(Color.White)
          .borderRadius(8)
        }
        .margin({ bottom: 8 })
      })
    }
    .width('100%')
    .layoutWeight(1)
    .padding({ left: 16, right: 16 })
  }
}

四、踩坑与注意事项

坑1:临时ID冲突

问题:离线创建的数据用临时ID,同步后服务器返回真实ID,如何处理?

解决:维护ID映射表:

// ID映射表
const idMapping: Map<string, string> = new Map();

// 同步成功后更新映射
function onSyncSuccess(tempId: string, realId: string) {
  idMapping.set(tempId, realId);
  // 更新所有引用了tempId的数据
}

坑2:数据冲突

问题:离线修改了数据A,别人在线也修改了数据A,同步时冲突。

解决:使用版本号 + 冲突解决策略:

// 版本号检测
if (local.version < server.version) {
  // 服务器版本更新,需要冲突解决
  const resolved = await conflictResolver.resolve(local, server);
  return resolved;
}

坑3:大量数据同步卡顿

问题:离线积累了很多操作,一联网就全部同步,卡死。

解决:分批同步 + 限流:

// 每次最多同步10个操作
const batchSize = 10;
const operations = this.queue.slice(0, batchSize);

// 同步间隔
await delay(100); // 每个操作间隔100ms

坑4:网络状态误判

问题:网络检测不准确,明明有网却判断为离线。

解决:心跳检测 + 实际请求验证:

// 定期心跳检测
setInterval(async () => {
  try {
    await fetch('https://api.example.com/ping', { timeout: 5000 });
    this.isConnected = true;
  } catch {
    this.isConnected = false;
  }
}, 30000); // 每30秒检测一次

五、HarmonyOS 6 适配要点

1. RDB API 增强

// HarmonyOS 6 支持更强大的数据库操作
import { relationalStore } from '@kit.ArkData';

// 支持事务
await rdbStore.beginTransaction();
try {
  await rdbStore.insert('table1', data1);
  await rdbStore.insert('table2', data2);
  await rdbStore.commit();
} catch (error) {
  await rdbStore.rollBack();
}

// 支持批量操作
await rdbStore.batchInsert('table', [data1, data2, data3]);

2. 网络检测增强

// HarmonyOS 6 提供更详细的网络信息
const netConnection = connection.createNetConnection();

// 监听网络能力变化
netConnection.on('netCapabilitiesChange', (data) => {
  console.log('网络能力:', data.netCapabilities);
  // 包含:带宽、延迟、是否计费等信息
});

3. 后台任务支持

// HarmonyOS 6 支持后台同步
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';

// 申请后台任务
const id = await backgroundTaskManager.requestSuspendDelay('sync', () => {
  console.log('后台同步任务结束');
});

// 执行同步
await syncManager.sync();

// 取消后台任务
backgroundTaskManager.cancelSuspendDelay(id);

六、总结

离线优先是现代应用的"标配",让用户无感知地享受在线离线切换:

层次 职责 关键技术
数据层 本地存储 RDB、Preferences
同步层 数据同步 操作队列、冲突解决
业务层 数据访问 统一接口、状态管理

记住几个原则:

  • ✅ 本地优先:先读本地,后台更新
  • ✅ 操作队列:离线操作持久化
  • ✅ 网络检测:实时监听网络状态
  • ✅ 冲突解决:版本号 + 策略
  • ✅ 用户提示:离线状态要告知用户

下一篇我们深入网络模拟与Mock,看看开发调试的利器。


💡 最佳实践提示:建议在应用启动时初始化离线系统,并在全局状态中维护网络状态,让所有页面都能感知在线/离线切换。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。