鸿蒙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);
  }
}
相关推荐
石山岭6 小时前
自己动手写了一个 Android 虚拟定位 App:GPSSimulate 技术实
android·前端
杉氧8 小时前
副作用 (Side Effects) 全攻略:如何像大师一样掌控 Composable 的生命周期?
android·架构·android jetpack
ONEDAY11 小时前
HarmonyOS 深色模式适配实践:从资源、WebView 到网络图统一处理
harmonyos
Kapaseker12 小时前
Kotlin Toolchain 0.11 发布:主要是把 Amper 干没了
android·kotlin
三少爷的鞋13 小时前
Android 现代架构不需要事件总线进阶篇
android
杉氧1 天前
深入理解 Compose 重组机制:快照系统如何驱动 UI 精准刷新?
android·架构·android jetpack
召钱熏1 天前
状态枚举正确≠渲染正确:一个语音按钮的状态机边界修复实录
android·前端
杉氧1 天前
深度解析:Jetpack Compose 核心架构与底层原理 —— 十年安卓老兵的“破茧重生”
android·架构·android jetpack
通玄1 天前
Jetpack Compose 入门系列(七):ViewModel 与界面状态管理
android
落魄Android在线炒饭1 天前
Android Framework 开发技巧:android.jar 生成与系统快速编译验证
android