鸿蒙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);
  }
}
相关推荐
诸神黄昏EX26 分钟前
Android Build系列专题【篇四:编译相关语法】
android
雨白3 小时前
优雅地处理协程:取消机制深度剖析
android·kotlin
leon_zeng03 小时前
更改 Android 应用 ID (ApplicationId) 后遭遇记
android·发布
RollingPin3 小时前
iOS八股文之 RunLoop
ios·多线程·卡顿·ios面试·runloop·ios保活·ios八股文
2501_916007475 小时前
iOS 混淆工具链实战,多工具组合完成 IPA 混淆与加固(iOS混淆|IPA加固|无源码混淆|App 防反编译)
android·ios·小程序·https·uni-app·iphone·webview
LinXunFeng5 小时前
Flutter webview 崩溃率上升怎么办?我的分析与解决方案
flutter·ios·webview
Jeled6 小时前
Retrofit 与 OkHttp 全面解析与实战使用(含封装示例)
android·okhttp·android studio·retrofit
游戏开发爱好者86 小时前
FTP 抓包分析实战,命令、被动主动模式要点、FTPS 与 SFTP 区别及真机取证流程
运维·服务器·网络·ios·小程序·uni-app·iphone
Nick56839 小时前
Xcode16 避坑
ios
ii_best9 小时前
IOS/ 安卓开发工具按键精灵Sys.GetAppList 函数使用指南:轻松获取设备已安装 APP 列表
android·开发语言·ios·编辑器