鸿蒙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);
  }
}
相关推荐
提子拌饭13335 分钟前
风息时钟:鸿蒙Flutter 实现的自然风格时钟应用
flutter·华为·架构·开源·harmonyos
浮芷.4 小时前
Flutter 框架跨平台鸿蒙开发 - AR动物互动应用
flutter·ar·harmonyos
SharpCJ4 小时前
Android 开发者为什么必须掌握 AI 能力?端侧视角下的技术变革
android·ai·aigc
_李小白4 小时前
【OSG学习笔记】Day 38: TextureVisitor(纹理访问器)
android·笔记·学习
JJay.5 小时前
Kotlin 高阶函数学习指南
android·开发语言·kotlin
jinanwuhuaguo5 小时前
截止到4月8日,OpenClaw 2026年4月更新深度解读剖析:从“能力回归”到“信任内建”的范式跃迁
android·开发语言·人工智能·深度学习·kotlin
加农炮手Jinx5 小时前
Flutter 组件 conventional 适配鸿蒙 HarmonyOS 实战:约定式提交标准,构建自动化版本治理与 CI/CD 质量治理架构
flutter·harmonyos·鸿蒙·openharmony
王码码20355 小时前
Flutter 三方库 appstream 的鸿蒙化适配指南 - 驾驭 Linux 生态元数据规范,打造高性能、标准化、国际化的 OpenHarmony 桌面应用商店分发基石
flutter·harmonyos·鸿蒙·openharmony
见山是山-见水是水5 小时前
Flutter 框架跨平台鸿蒙开发 - AR植物养护助手
flutter·华为·ar·harmonyos
JJay.5 小时前
Android Kotlin 协程使用指南
android·开发语言·kotlin