鸿蒙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);
  }
}
相关推荐
fleaxin3 小时前
Atlas 300V:(3)模型精度数据对比
yolo·华为·atlas
xiaoshiquan12063 小时前
Gradle 复合构建:宿主项目直接依赖 SDK 源码模块的配置实践
android
威哥爱编程3 小时前
HarmonyOS 7 Agent 开发实战 08:结构化输出解析
agent·harmonyos
威哥爱编程3 小时前
HarmonyOS 7 Agent 开发实战 09:Agent 测试评测集
agent·harmonyos
小师兄吃牛肉5 小时前
Android Studio 2025 专业介绍:新特性、性能与开发体验全面解析
android·ide·android studio
美摄科技5 小时前
安卓离线类美颜特效SDK
android
黑化旺仔6 小时前
【OC】Runtime:类元数据、加载初始化与消息派发
开发语言·macos·ios·objective-c
fengci.7 小时前
BUUCTF(困难部分)
android·开发语言·前端·学习·php
songgeb7 小时前
iOS App支持设置RTL语言的实践
ios·swift·ui kit
软泡芙7 小时前
【iOS】swift启动屏显示黑屏
ios