鸿蒙NEXT开发全局异常捕获与崩溃日志收集工具类(ArkTs)

复制代码
import errorManager from '@ohos.app.ability.errorManager';
import { BusinessError } from '@kit.BasicServicesKit';
import { WriteOptions } from '@kit.CoreFileKit';
import { appRecovery, common, Want } from '@kit.AbilityKit';
import { DateUtil } from './DateUtil';
import { LogUtil } from './LogUtil';
import { FileUtil } from './FileUtil';
import { AppUtil } from './AppUtil';

/**
 * 定义异常对象的接口。
 */
interface ErrorObject {
  message?: string; // 错误消息
  code?: number; // 错误码
  details?: string; // 错误详情
  stack?: string; // 错误堆栈信息
}


/**
 * 全局异常捕获与崩溃日志收集工具类。
 *
 * @author 鸿蒙布道师
 * @since 2025/04/08
 */
export class CrashUtil {
  private static observerId: number | undefined = undefined; // 错误观测器 ID
  private static errorFilePath: string | undefined = undefined; // 错误日志文件路径

  /**
   * 注册错误观测器。
   * 注册后可以捕获到应用产生的 JS Crash,应用崩溃时进程不会退出,并将异常信息写入本地文件。
   */
  static onHandledException(): void {
    try {
      if (CrashUtil.observerId === undefined) {
        CrashUtil.observerId = errorManager.on('error', {
          /**
           * 捕获未处理的异常信息。
           *
           * @param errMsg 异常信息。
           */
          onUnhandledException(errMsg: string): void {
            const errStr = `${DateUtil.getTodayStr()} - 未处理异常:\n${errMsg}\n\n`;
            CrashUtil.writeErrorLog(errStr);
          },
          /**
           * 捕获异常对象。
           *
           * @param errObject 异常对象。
           */
          onException(errObject: Error | ErrorObject): void {
            const errStr = `${DateUtil.getTodayStr()} - 异常对象:\n${JSON.stringify(errObject)}\n\n`;
            CrashUtil.writeErrorLog(errStr);
          }
        });
      }
    } catch (err) {
      LogUtil.error(`注册错误观测器失败:${err}`);
    }
  }

  /**
   * 注销错误观测器。
   */
  static onExceptionDestroy(): void {
    try {
      if (CrashUtil.observerId !== undefined) {
        errorManager.off('error', CrashUtil.observerId, (err: BusinessError) => {
          if (err) {
            LogUtil.error(`注销错误观测器失败:${JSON.stringify(err)}`);
          }
        });
        CrashUtil.observerId = undefined;
      }
    } catch (err) {
      LogUtil.error(`注销错误观测器时发生错误:${err}`);
    }
  }

  /**
   * 获取错误日志文件路径。
   *
   * @returns 返回错误日志文件路径。
   */
  static getErrorFilePath(): string {
    if (!CrashUtil.errorFilePath) {
      CrashUtil.errorFilePath = FileUtil.getFilesDirPath("harmony_utils_error_log", "ErrorLog.json");
    }
    return CrashUtil.errorFilePath;
  }

  /**
   * 读取错误日志文件的 JSON 字符串。
   *
   * @returns 返回错误日志内容的 JSON 字符串。
   */
  static async getErrorJson(): Promise<string> {
    const errorFilePath = CrashUtil.getErrorFilePath();
    if (FileUtil.accessSync(errorFilePath)) {
      return await FileUtil.readText(errorFilePath);
    }
    return '';
  }

  /**
   * 将错误日志写入文件。
   *
   * @param logContent 日志内容。
   */
  private static writeErrorLog(logContent: string): void {
    const errorFilePath = CrashUtil.getErrorFilePath();
    try {
      CrashUtil.writeStr(errorFilePath, logContent);
    } catch (err) {
      LogUtil.error(`写入错误日志失败:${err}`);
    }
  }

  /**
   * 将数据写入文件,并关闭文件。
   *
   * @param path 文件路径。
   * @param str 要写入的字符串。
   * @returns 返回写入的字节数。
   */
  private static writeStr(path: string, str: string): number {
    const file = FileUtil.openSync(path);
    const offset = FileUtil.statSync(file.fd).size;
    try {
      const formattedStr = offset === 0 ? `[${str}]` : `,${str}`;
      const options: WriteOptions = { offset, encoding: 'utf-8' };
      return FileUtil.writeSync(file.fd, formattedStr, options);
    } finally {
      FileUtil.closeSync(file.fd); // 确保文件始终被关闭
    }
  }

  /**
   * 启用应用恢复功能(已过时)。
   * 推荐使用 `AppUtil.enableAppRecovery()`。
   */
  static enableAppRecovery(
    restart: appRecovery.RestartFlag = appRecovery.RestartFlag.ALWAYS_RESTART,
    saveOccasion: appRecovery.SaveOccasionFlag = appRecovery.SaveOccasionFlag.SAVE_WHEN_ERROR,
    saveMode: appRecovery.SaveModeFlag = appRecovery.SaveModeFlag.SAVE_WITH_FILE
  ): void {
    AppUtil.enableAppRecovery(restart, saveOccasion, saveMode);
  }

  /**
   * 重启应用(已过时)。
   * 推荐使用 `AppUtil.restartApp()`。
   */
  static restartApp(): void {
    AppUtil.restartApp();
  }

  /**
   * 设置下次恢复主动拉起场景下的 Ability(已过时)。
   * 推荐使用 `AppUtil.setRestartWant()`。
   */
  static setRestartWant(want: Want): void {
    AppUtil.setRestartWant(want);
  }

  /**
   * 保存当前 App 状态或主动保存 Ability 的状态(已过时)。
   * 推荐使用 `AppUtil.saveAppState()`。
   */
  static saveAppState(context?: common.UIAbilityContext): boolean {
    return AppUtil.saveAppState(context);
  }
}

代码如下:
TypeScript 复制代码
import errorManager from '@ohos.app.ability.errorManager';
import { BusinessError } from '@kit.BasicServicesKit';
import { WriteOptions } from '@kit.CoreFileKit';
import { appRecovery, common, Want } from '@kit.AbilityKit';
import { DateUtil } from './DateUtil';
import { LogUtil } from './LogUtil';
import { FileUtil } from './FileUtil';
import { AppUtil } from './AppUtil';


/**
 * 定义异常对象的接口。
 */
interface ErrorObject {
  message?: string; // 错误消息
  code?: number; // 错误码
  details?: string; // 错误详情
  stack?: string; // 错误堆栈信息
}


/**
 * 全局异常捕获与崩溃日志收集工具类。
 *
 * @author 鸿蒙布道师
 * @since 2025/04/08
 */
export class CrashUtil {
  private static observerId: number | undefined = undefined; // 错误观测器 ID
  private static errorFilePath: string | undefined = undefined; // 错误日志文件路径

  /**
   * 注册错误观测器。
   * 注册后可以捕获到应用产生的 JS Crash,应用崩溃时进程不会退出,并将异常信息写入本地文件。
   */
  static onHandledException(): void {
    try {
      if (CrashUtil.observerId === undefined) {
        CrashUtil.observerId = errorManager.on('error', {
          /**
           * 捕获未处理的异常信息。
           *
           * @param errMsg 异常信息。
           */
          onUnhandledException(errMsg: string): void {
            const errStr = `${DateUtil.getTodayStr()} - 未处理异常:\n${errMsg}\n\n`;
            CrashUtil.writeErrorLog(errStr);
          },
          /**
           * 捕获异常对象。
           *
           * @param errObject 异常对象。
           */
          onException(errObject: Error | ErrorObject): void {
            const errStr = `${DateUtil.getTodayStr()} - 异常对象:\n${JSON.stringify(errObject)}\n\n`;
            CrashUtil.writeErrorLog(errStr);
          }
        });
      }
    } catch (err) {
      LogUtil.error(`注册错误观测器失败:${err}`);
    }
  }

  /**
   * 注销错误观测器。
   */
  static onExceptionDestroy(): void {
    try {
      if (CrashUtil.observerId !== undefined) {
        errorManager.off('error', CrashUtil.observerId, (err: BusinessError) => {
          if (err) {
            LogUtil.error(`注销错误观测器失败:${JSON.stringify(err)}`);
          }
        });
        CrashUtil.observerId = undefined;
      }
    } catch (err) {
      LogUtil.error(`注销错误观测器时发生错误:${err}`);
    }
  }

  /**
   * 获取错误日志文件路径。
   *
   * @returns 返回错误日志文件路径。
   */
  static getErrorFilePath(): string {
    if (!CrashUtil.errorFilePath) {
      CrashUtil.errorFilePath = FileUtil.getFilesDirPath("harmony_utils_error_log", "ErrorLog.json");
    }
    return CrashUtil.errorFilePath;
  }

  /**
   * 读取错误日志文件的 JSON 字符串。
   *
   * @returns 返回错误日志内容的 JSON 字符串。
   */
  static async getErrorJson(): Promise<string> {
    const errorFilePath = CrashUtil.getErrorFilePath();
    if (FileUtil.accessSync(errorFilePath)) {
      return await FileUtil.readText(errorFilePath);
    }
    return '';
  }

  /**
   * 将错误日志写入文件。
   *
   * @param logContent 日志内容。
   */
  private static writeErrorLog(logContent: string): void {
    const errorFilePath = CrashUtil.getErrorFilePath();
    try {
      CrashUtil.writeStr(errorFilePath, logContent);
    } catch (err) {
      LogUtil.error(`写入错误日志失败:${err}`);
    }
  }

  /**
   * 将数据写入文件,并关闭文件。
   *
   * @param path 文件路径。
   * @param str 要写入的字符串。
   * @returns 返回写入的字节数。
   */
  private static writeStr(path: string, str: string): number {
    const file = FileUtil.openSync(path);
    const offset = FileUtil.statSync(file.fd).size;
    try {
      const formattedStr = offset === 0 ? `[${str}]` : `,${str}`;
      const options: WriteOptions = { offset, encoding: 'utf-8' };
      return FileUtil.writeSync(file.fd, formattedStr, options);
    } finally {
      FileUtil.closeSync(file.fd); // 确保文件始终被关闭
    }
  }

  /**
   * 启用应用恢复功能(已过时)。
   * 推荐使用 `AppUtil.enableAppRecovery()`。
   */
  static enableAppRecovery(
    restart: appRecovery.RestartFlag = appRecovery.RestartFlag.ALWAYS_RESTART,
    saveOccasion: appRecovery.SaveOccasionFlag = appRecovery.SaveOccasionFlag.SAVE_WHEN_ERROR,
    saveMode: appRecovery.SaveModeFlag = appRecovery.SaveModeFlag.SAVE_WITH_FILE
  ): void {
    AppUtil.enableAppRecovery(restart, saveOccasion, saveMode);
  }

  /**
   * 重启应用(已过时)。
   * 推荐使用 `AppUtil.restartApp()`。
   */
  static restartApp(): void {
    AppUtil.restartApp();
  }

  /**
   * 设置下次恢复主动拉起场景下的 Ability(已过时)。
   * 推荐使用 `AppUtil.setRestartWant()`。
   */
  static setRestartWant(want: Want): void {
    AppUtil.setRestartWant(want);
  }

  /**
   * 保存当前 App 状态或主动保存 Ability 的状态(已过时)。
   * 推荐使用 `AppUtil.saveAppState()`。
   */
  static saveAppState(context?: common.UIAbilityContext): boolean {
    return AppUtil.saveAppState(context);
  }
}
相关推荐
CV资深专家10 分钟前
Android 各分区模块编译配置(mk/bp)总结
android
louisgeek2 小时前
Java 线程池取消的方式
android
Billy_Zuo2 小时前
人工智能机器学习——模型评价及优化
android·人工智能·机器学习
tangweiguo030519873 小时前
Flutter与原生混合开发:实现完美的暗夜模式同步方案
android·flutter
祥睿夫子4 小时前
鸿蒙 ArkTS 类继承与多态实战:从语法到员工工资计算全指南
harmonyos
雨白4 小时前
深入理解 Android 触摸事件:以实现 ViewPager 为例
android
shenshizhong4 小时前
看懂鸿蒙系统源码 比较重要的知识点
android·harmonyos
他们都不看好你,偏偏你最不争气4 小时前
【iOS】UIViewController
开发语言·macos·ios·objective-c·cocoa
一只修仙的猿6 小时前
再谈性能优化,一次项目优化经历分享
android·性能优化
特立独行的猫a6 小时前
强大的鸿蒙HarmonyOS网络调试工具PageSpy 介绍及使用
网络·华为·harmonyos