HarmonyOS 鸿蒙Next中怎样统一处理应用错误

HarmonyOS 鸿蒙Next中怎样统一处理应用错误 应用需要统一的错误处理,怎样实现?

  • 捕获并记录错误
  • 友好的错误提示
  • 生产环境日志收集
  • 调试日志控制
3 回复

解决方案

1. 日志工具类

import { hilog } from '@kit.PerformanceAnalysisKit';

/**
 * 日志工具类
 */
export class Logger {
  private static readonly DOMAIN = 0x0001;  // 日志域
  private static readonly TAG = 'GlanceHome';  // 日志标签
  private static isDebug: boolean = true;  // 调试模式
  
  /**
   * 调试日志
   */
  static debug(tag: string, message: string, ...args: Object[]): void {
    if (this.isDebug) {
      hilog.debug(this.DOMAIN, `${this.TAG}_${tag}`, message, args);
    }
  }
  
  /**
   * 信息日志
   */
  static info(tag: string, message: string, ...args: Object[]): void {
    hilog.info(this.DOMAIN, `${this.TAG}_${tag}`, message, args);
  }
  
  /**
   * 警告日志
   */
  static warn(tag: string, message: string, ...args: Object[]): void {
    hilog.warn(this.DOMAIN, `${this.TAG}_${tag}`, message, args);
  }
  
  /**
   * 错误日志
   */
  static error(tag: string, message: string, error?: Error): void {
    const errorMsg = error ? `${message}: ${error.message}` : message;
    hilog.error(this.DOMAIN, `${this.TAG}_${tag}`, errorMsg);
    
    // ✅ 生产环境上报错误
    if (!this.isDebug) {
      this.reportError(tag, errorMsg, error);
    }
  }
  
  /**
   * 上报错误到服务器
   */
  private static async reportError(tag: string, message: string, error?: Error): Promise<void> {
    try {
      // TODO: 发送到错误收集服务
      console.info('上报错误:', tag, message);
    } catch (err) {
      console.error('上报失败:', err);
    }
  }
  
  /**
   * 设置调试模式
   */
  static setDebugMode(isDebug: boolean): void {
    this.isDebug = isDebug;
  }
}

// ✅ 使用
Logger.debug('Database', '查询物品', { id: 123 });
Logger.info('UI', '页面加载完成');
Logger.warn('Network', '网络请求慢');
Logger.error('Database', '查询失败', error);

2. 错误处理基类

/**
 * 应用错误基类
 */
export class AppError extends Error {
  code: string;
  
  constructor(code: string, message: string) {
    super(message);
    this.code = code;
    this.name = 'AppError';
  }
}

/**
 * 数据库错误
 */
export class DatabaseError extends AppError {
  constructor(message: string) {
    super('DB_ERROR', message);
    this.name = 'DatabaseError';
  }
}

/**
 * 网络错误
 */
export class NetworkError extends AppError {
  constructor(message: string) {
    super('NET_ERROR', message);
    this.name = 'NetworkError';
  }
}

/**
 * 业务错误
 */
export class BusinessError extends AppError {
  constructor(code: string, message: string) {
    super(code, message);
    this.name = 'BusinessError';
  }
}

3. 统一错误处理

/**
 * 错误处理器
 */
export class ErrorHandler {
  /**
   * 处理错误
   */
  static handle(error: Error | AppError, context: string = ''): void {
    Logger.error(context || 'Global', '发生错误', error);
    
    // ✅ 根据错误类型显示不同提示
    let message = '操作失败,请稍后重试';
    
    if (error instanceof DatabaseError) {
      message = '数据保存失败';
    } else if (error instanceof NetworkError) {
      message = '网络连接失败';
    } else if (error instanceof BusinessError) {
      message = error.message;  // 业务错误直接显示
    }
    
    promptAction.showToast({ message });
  }
  
  /**
   * 安全执行(自动捕获错误)
   */
  static async safeExecute<T>(
    fn: () => Promise<T>,
    context: string,
    fallback?: T
  ): Promise<T | undefined> {
    try {
      return await fn();
    } catch (error) {
      this.handle(error as Error, context);
      return fallback;
    }
  }
}

// ✅ 使用
async function loadData(): Promise<void> {
  await ErrorHandler.safeExecute(
    async () => {
      const items = await ItemDao.findAll();
      this.items = items;
    },
    'ItemList',
    []  // 失败时返回空数组
  );
}

4. 实战案例

/**
 * 带完整错误处理的Service
 */
export class ItemService {
  private static instance: ItemService;
  private itemDao = ItemDao.getInstance();
  
  static getInstance(): ItemService {
    if (!ItemService.instance) {
      ItemService.instance = new ItemService();
    }
    return ItemService.instance;
  }
  
  /**
   * 添加物品
   */
  async addItem(item: Item): Promise<number> {
    const TAG = 'ItemService';
    
    try {
      Logger.debug(TAG, '开始添加物品', item);
      
      // ✅ 业务校验
      this.validateItem(item);
      
      // ✅ 检查重复
      const exists = await this.itemDao.existsByName(item.name);
      if (exists) {
        throw new BusinessError('DUPLICATE_NAME', '物品名称已存在');
      }
      
      // ✅ 保存到数据库
      const itemId = await this.itemDao.insert(item);
      
      Logger.info(TAG, '添加物品成功', { id: itemId });
      return itemId;
      
    } catch (error) {
      // ✅ 转换数据库错误
      if (error instanceof Error && error.message.includes('SQLITE')) {
        throw new DatabaseError('数据保存失败');
      }
      
      // ✅ 记录并抛出
      Logger.error(TAG, '添加物品失败', error as Error);
      throw error;
    }
  }
  
  /**
   * 业务校验
   */
  private validateItem(item: Item): void {
    if (!item.name || item.name.trim() === '') {
      throw new BusinessError('INVALID_NAME', '物品名称不能为空');
    }
    
    if (item.name.length > 50) {
      throw new BusinessError('NAME_TOO_LONG', '物品名称不能超过50字');
    }
  }
  
  /**
   * 查询所有物品(安全)
   */
  async getAllItems(): Promise<Item[]> {
    return await ErrorHandler.safeExecute(
      async () => {
        return await this.itemDao.findAll();
      },
      'ItemService.getAllItems',
      []  // 失败返回空数组
    ) || [];
  }
}

5. 页面级错误处理

@Component
struct ItemListPage {
  @State items: Item[] = [];
  @State isLoading: boolean = false;
  @State errorMessage: string = '';
  
  async aboutToAppear(): Promise<void> {
    await this.loadData();
  }
  
  async loadData(): Promise<void> {
    this.isLoading = true;
    this.errorMessage = '';
    
    try {
      Logger.debug('ItemListPage', '开始加载数据');
      
      const items = await ItemService.getInstance().getAllItems();
      
      if (items.length === 0) {
        this.errorMessage = '暂无数据';
      } else {
        this.items = items;
        Logger.info('ItemListPage', `加载成功: ${items.length}条`);
      }
      
    } catch (error) {
      // ✅ 页面级错误处理
      ErrorHandler.handle(error as Error, 'ItemListPage');
      this.errorMessage = '加载失败';
      
    } finally {
      this.isLoading = false;
    }
  }
  
  build() {
    Column() {
      if (this.isLoading) {
        LoadingProgress();
      } else if (this.errorMessage) {
        this.buildErrorState();
      } else {
        this.buildList();
      }
    }
  }
  
  @Builder
  buildErrorState() {
    Column({ space: 16 }) {
      Text(this.errorMessage)
        .fontSize(14)
        .fontColor('#999999');
      
      Button('重新加载')
        .onClick(() => {
          this.loadData();
        });
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }
  
  @Builder
  buildList() {
    List() {
      ForEach(this.items, (item: Item) => {
        ListItem() {
          Text(item.name);
        }
      })
    }
  }
}

关键要点

1. 日志级别

Logger.debug()  // 调试信息,生产环境不输出
Logger.info()   // 重要信息
Logger.warn()   // 警告信息
Logger.error()  // 错误信息,需上报

2. 错误分类

// ✅ 系统错误
DatabaseError    // 数据库错误
NetworkError     // 网络错误

// ✅ 业务错误
BusinessError    // 业务逻辑错误,可直接提示用户

3. 错误处理原则

// ✅ 低层抛出,高层捕获
// Dao层
async findById(id: number): Promise<Item> {
  // 发生错误直接抛出
  throw new DatabaseError('查询失败');
}

// Service层
async getItem(id: number): Promise<Item> {
  try {
    return await itemDao.findById(id);
  } catch (err) {
    // 记录日志并转换错误
    Logger.error('ItemService', '获取物品失败', err);
    throw err;
  }
}

// UI层
async loadItem(): Promise<void> {
  try {
    this.item = await itemService.getItem(this.id);
  } catch (err) {
    // 最终捕获并提示用户
    ErrorHandler.handle(err);
  }
}

最佳实践

✅ 统一使用Logger记录日志 ✅ 自定义错误类型 ✅ 错误信息对用户友好 ✅ 生产环境上报错误 ✅ 使用safeExecute简化代码

完善的错误处理提升应用稳定性!

更多关于HarmonyOS 鸿蒙Next中怎样统一处理应用错误的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中,统一处理应用错误主要通过全局异常处理器实现。开发者可在ApplicationonCreate方法中调用ErrorManager.registerErrorHandler()注册自定义错误处理器。该处理器需实现ErrorHandler接口,重写onError方法以捕获和处理未捕获的异常。

在HarmonyOS Next中,统一处理应用错误可以通过构建一个集中的错误管理机制来实现。以下是针对你提出的几个方面的具体实现思路:

1. 捕获并记录错误

  • 全局异常捕获:在应用的入口文件(通常是 EntryAbility)中,使用 errorManager 或通过 window.onerror(在Web兼容环境中)来设置全局未捕获异常处理器。这可以捕获到大多数未被局部 try-catch 处理的运行时错误。
  • 异步错误处理:对于Promise异步操作,可以使用 Promise.catch() 进行捕获,或者考虑使用 async/await 配合 try-catch
  • 自定义错误边界:对于UI渲染错误,可以设计一个顶层的“错误边界”组件。这个组件使用生命周期方法(如 aboutToAppear)或 @State 装饰器来捕获子组件树的JavaScript异常,防止整个应用崩溃,并显示降级UI。
  • 日志记录:在捕获到错误后,立即调用统一的日志记录函数。记录的信息应包括错误对象、时间戳、设备信息、用户操作路径等上下文信息。

2. 友好的错误提示

  • 用户提示组件:设计一个全局的、可复用的错误提示组件(例如,一个Toast或Dialog弹窗)。当业务逻辑中发生可预知的错误(如网络请求失败、表单验证失败)时,通过状态管理或事件总线触发该组件显示。
  • 错误分类与映射:建立一个错误码或错误类型到友好提示信息的映射表。根据捕获的错误类型,显示对应的、对用户有指导意义的文字,避免直接展示技术性错误堆栈。
  • 降级UI:对于因错误导致无法渲染的组件,在错误边界中显示预设的友好占位界面,引导用户进行刷新或其他操作。

3. 生产环境日志收集

  • 日志分级与过滤:实现日志分级(如Debug, Info, Warn, Error)。在生产环境中,通常只上报 WarnError 级别的日志,并可能过滤掉一些敏感的或过于频繁的信息。
  • 持久化与上报:将重要的错误日志持久化存储到本地(例如使用 PreferencesRDB)。可以设置一个日志上报服务,在适当的时机(如应用启动、网络恢复、达到一定数量)将本地缓存的日志批量上传到你的服务器。
  • 关键信息附加:上报的日志应附带有助于诊断的上下文,如应用版本、HarmonyOS版本、设备型号、错误发生前的用户操作序列等。

4. 调试日志控制

  • 环境感知:通过编译变量或配置文件区分开发、测试和生产环境。
  • 条件输出:在开发环境下,可以将所有级别的日志(尤其是Debug信息)输出到控制台(console.log),便于调试。在生产环境下,则完全禁用 console.log 或将其重定向到无操作函数,仅保留必要的错误记录和上报逻辑。
  • 动态调试开关(可选):可以考虑实现一个通过特定操作(如连续点击版本号)激活的“调试模式”,在该模式下临时开启更详细的日志输出,方便在测试或生产环境复现问题时进行现场诊断。

架构建议

可以创建一个独立的 ErrorServiceLogger 模块来封装以上所有功能。该模块提供统一的API供应用其他部分调用,例如 logError(error, extraInfo)showUserToast(message) 等。在应用初始化时配置该模块,根据当前环境设置不同的行为策略。这样能够确保错误处理逻辑的一致性和可维护性。

回到顶部