【ArkUI提高练中学】第7课:性能调优与稳定性治理

本节目标

  • 掌握卡顿丢帧的根因分析方法,能够使用 Frame 模板、ArkUI 模板和 ArkUI Inspector 精准定位 UI 渲染瓶颈
  • 掌握 ArkTS 内存泄漏的检测与定位方法,能够使用 JSLeakWatcher 生成泄漏报告并分析引用链
  • 掌握 AppFreeze 冻屏故障的分析方法,能够解读冻屏日志中的 THREAD_BLOCK_6S、APP_INPUT_BLOCK 等关键信息
  • 掌握崩溃的异常处理分层模型,能够建立 try-catch、Promise.catch、全局异常监听的纵深防御
  • 掌握资源泄漏的运行态检测能力,能够使用 HiDebug 采集 FD、线程、Native 内存等资源分配栈
  • 掌握线上性能监控方案,能够使用 HiAppEvent 订阅故障事件并结合 APMS 进行多维度分析
  • 掌握 FaultLogExtensionAbility 的延迟通知机制,能够处理应用崩溃/冻屏后未及时启动的故障补报场景
  • 能够为应用建立"开发态检测 → 运行态监控 → 线上告警 → 修复验证"的全链路稳定性治理闭环

一、卡顿丢帧的根因分析

1.1 卡顿丢帧的故障模型

屏幕以固定频率发送 Vsync 信号来控制每一帧绘制操作的时机。以 120Hz 刷新率为例,每帧平均耗时 8.33ms。应用侧收到 Vsync 信号后响应用户输入事件,确定 UI 元素的位置、大小、资源、动效属性等,然后提交绘制指令给渲染服务(Render Service),渲染服务根据绘制指令进行图形计算和渲染操作,将渲染结果写入帧缓冲区,最终送到屏幕上显示。

按上述流程,应用侧和 Render Service 侧都可能因为处理时间较长,超过了 Vsync 信号周期,导致界面送显的频率低于屏幕刷新率,出现卡顿丢帧的情况。前者可能是应用业务逻辑、组件复杂、执行耗时逻辑等导致,后者可能是界面结构过于复杂、GPU 负载过大等导致。

关键指标:

  • 帧率:稳定 60 帧,最低不低于 45 帧
  • 单帧耗时:120Hz 下不超过 8.33ms,60Hz 下不超过 16.67ms
  • 丢帧率:连续丢帧超过 3 帧即可感知卡顿

1.2 用 Frame 模板定位卡顿

使用 DevEco Profiler 的 Frame 模板抓取滑动翻页过程的 Trace 信息。常见 Trace 关键字:

  • H:touchEventDispatch:屏幕触摸事件
  • H:FrameNode[组件名][id:组件ID]::RenderTask:执行单个组件的绘制任务
  • H:ReceiveVsync:应用接收 Vsync 信号

排查思路:先看帧级耗时,再分流到 UI 主线程或渲染线程。

  • 如果 H:ExecuteJS、FlushDirtyNodeUpdate、Measure/Layout/Create 耗时长,说明是主线程问题,需优化 ArkTS 和组件刷新。
  • 如果 RSUniRenderThread 单帧长、纹理创建/大图耗时,说明是渲染侧问题,需降低图片尺寸、避免动画中频繁模糊/裁剪/阴影。

1.3 用 ArkUI 模板分析组件与状态

ArkUI 模板用于定位由于组件耗时、页面布局、状态变量更新导致的卡顿问题。常见场景:

  • 布局嵌套过多引起的性能问题
  • 数据结构设计不合理,应用使用一个较大的 Object,在更新时只更新某些属性,导致其他没变化的属性也会更新,产生冗余刷新
  • 父组件中的子组件重复绑定同一个状态变量进行更新
  • 未正确使用装饰器,如错误使用 @Prop 传递一个大的对象进行深度拷贝

通过 ArkUI Component 泳道 可以直观感知组件绘制频率、耗时等统计情况,Summary 区域展示绘制次数、总耗时、最小耗时、平均耗时、最大耗时、耗时标准差。通过 ArkUI State 泳道 可以查看录制过程中发生的状态变量变化,定位到可能造成卡顿的状态变量变化时间点,框选对应时间段后查看对应组件刷新时间。

1.4 完整案例分析:Canvas 绘制频率过低导致翻页掉帧

问题现象:阅读文档场景下,左右滑动翻页时丢帧,帧率低。

定位过程:

  1. 使用 DevEco Profiler Frame 模板抓取滑动翻页过程的 Trace 信息。
  2. 查看 render_service 泳道确认当前屏幕刷新率为 120Hz,但通过 Present Fence 可看到左右翻页时页面变化的帧率接近 20fps。
  3. 在 Slice List 中通过 RenderTask 关键字过滤,发现有应用执行 Canvas 组件的绘制任务。
  4. 使用 ArkUI Inspector 查看应用布局,确认应用采用 Canvas 组件来显示文档。
  5. 根据 ArkUI Inspector 获取的 Canvas 组件 id 号,在 Profiler 中搜索 H:FrameNode[Canvas][id:2321]::RenderTask,确认应用两次执行 Canvas 组件绘制任务的时间间隔为 50ms 左右,正好是 20fps。

分析结论:应用组件绘制的频率过低,小于屏幕刷新率,导致丢帧问题。

修改建议:优化组件绘制的相关业务逻辑,提高组件绘制的频率,可参考 Drawing 自绘制性能提升。

二、ArkTS 内存泄漏的精准定位

2.1 内存泄漏的危害

在 ArkTS 开发中,若一个 ArkTS 对象不再需要,但仍被某个引用链"意外"持有,则垃圾回收器无法回收该内存,从而导致内存泄漏。危害包括:

  • 性能方面:应用占用内存持续增长,系统为释放内存会频繁触发 GC,GC 执行时会暂停应用主线程(Stop-The-World 机制),导致界面卡顿、滑动不流畅。
  • 内存方面:若泄漏内存持续积累并达到 ArkTS Local 堆/共享堆或进程的 OOM 上限阈值时,则会产生 JS Crash。
  • 功耗方面:系统频繁 GC 会消耗大量 CPU 资源,持续高占用会导致设备发热,加速电量消耗。
  • 功能方面:部分泄漏会因对象引用残留间接导致功能异常。

2.2 JSLeakWatcher 检测原理

HarmonyOS 提供了 ArkTS 内存泄漏检测能力 JSLeakWatcher,开发者可轻松接入相关 API,实现对系统内具有生命周期的 ArkTS 组件对象定期执行泄漏自检测。

检测流程:

  1. 应用在启动后调用 enableLeakWatcher() 接口开启检测功能。
  2. 检测框架创建 FinalizationRegistry 对象,用于监控系统内具有生命周期的 5 类常见 ArkTS 组件对象(元能力-Ability、窗口-Window、NodeContainer、XComponent、自定义组件-CustomComponent),并注册生命周期结束回调函数。
  3. 添加异步定时 GC 任务,每 90 秒执行一次 FullGC 操作,同时添加定时 dump 任务,FullGC 执行 5 秒后执行一次泄漏检测。
  4. 尝试去解除引用的组件对象会被记录在列表 list1 中;被销毁的组件在被 GC 时,GC 的回调函数会将该对象记录在 list2 中;list1 - list2 就是泄漏对象。

2.3 常见泄漏原因

  • Native 层强引用:在 Node-API 中对 ArkTS 对象创建了持久化强引用。
  • 闭包捕获:内部函数持有对外部作用域 ArkTS 对象的引用,即使外部作用域已退出。
  • 全局或模块级缓存:使用 Map、Array 缓存长期持有 ArkTS 对象。

2.4 引用链分析方法

当检测到泄漏时,JSLeakWatcher 会生成泄漏信息文件(*.rawheap 文件和 *.jsleaklist 文件),将这两个文件导入 IDE(DevEco Studio 6.0.0 起均支持),就可以获取泄漏对象列表。通过泄漏对象列表中的泄漏对象直接跳转到引用链,加速找到持有该泄漏对象的根 GC_ROOT。

判断真泄漏还是正常未释放:

  • 退出页面后等待异步回调结束,连续进入/退出 3~5 次。
  • 在 Snapshot 中导入 JsLeakWatcher 的 .jsleaklist,查看对象从 GC Root 到该对象的引用链。
  • 重点查全局单例/缓存、定时器、事件订阅、回调闭包等。

2.5 修复示例:定时器未清理导致泄漏

typescript 复制代码
// ❌ 泄漏写法:页面销毁时未清理定时器
@Entry
@Component
struct LeakPage {
  private timerId: number = -1;

  aboutToAppear(): void {
    this.timerId = setInterval(() => {
      console.log('tick');
    }, 1000);
  }

  build() {
    Column() {
      Text('泄漏示例')
    }
  }
}

// ✅ 修复写法:在 aboutToDisappear 中清理
@Entry
@Component
struct FixedPage {
  private timerId: number = -1;

  aboutToAppear(): void {
    this.timerId = setInterval(() => {
      console.log('tick');
    }, 1000);
  }

  aboutToDisappear(): void {
    if (this.timerId !== -1) {
      clearInterval(this.timerId);
      this.timerId = -1;
    }
  }

  build() {
    Column() {
      Text('修复示例')
    }
  }
}

三、AppFreeze 冻屏故障分析

3.1 AppFreeze 检测机制

AppFreeze(应用冻屏)是一种看门狗机制。当应用主线程被长时间阻塞,无法及时响应用户输入或系统调度时,系统会强制终止该应用。AppFreeze 主要包含两种核心检测机制:

  • THREAD_BLOCK_6S:应用主线程卡死超时。
  • APP_INPUT_BLOCK:用户输入响应超时。

检测原理是当用户对设备进行操作时,多模输入服务会将事件派发给应用,如果应用的主线程在收到输入事件后超过 5 秒仍未反馈处理结果,系统认为该应用无法响应用户输入,触发输入阻塞故障。

3.2 冻屏日志三步定位法

第一步:确认故障类型与时间差

打开日志文件,首先查看日志头部的 Reason 字段,确认是哪种机制触发。接着在日志中搜索 THREAD_BLOCK_6S 的日志段落,找到 mainHandler dump 区域,对比当前任务开始时间与日志抓取检测时间。若差值接近或超过 6 秒,说明是当前正在运行的任务耗时过长;若差值很小,需要排查主线程是否积压大量任务。

第二步:排查消息队列积压

查看日志中 Event runner 下方的队列统计信息,检查高优先级事件数量。如果 High events 数量超过 400 或总事件数异常巨大,说明业务代码在短时间内发送了大量高优先级任务,导致主线程被占满。

第三步:分析主线程堆栈

找到日志中 Tid 与应用进程 ID 一致的线程堆栈,根据栈顶函数判断卡死原因。常见场景:

  • IPC 通信阻塞:栈顶出现 Binder 通信相关函数。
  • 等锁卡死 :栈顶出现 pthread_mutex_lock 等锁相关函数。
  • 业务代码耗时/死循环 :栈顶直接指向开发者的 .ets 或 .ts 文件代码行。

3.3 锁竞争案例:采样栈定位

在使用 Worker 或 TaskPool 进行并行计算时,如果多个子线程竞争同一个全局锁,会导致子线程执行变慢,进而拖慢等待子线程的主线程,最终引发冻屏。

通过对比 THREAD_BLOCK_3S 和 THREAD_BLOCK_6S 两次堆栈快照可以发现:

  • 3S 时主线程在执行业务逻辑,处于运行态。
  • 6S 时主线程状态变为 S(Sleeping),栈顶出现 pthread_join 和 std::__n1::thread::join,说明主线程在等待子线程完成,而子线程因为锁竞争执行变慢,最终拖垮了主线程。

通过采样栈可以识别"频繁等锁"的故障模式,精准定位到业务代码中的锁竞争问题。

四、崩溃与异常处理体系

4.1 崩溃的三类根因

在 HarmonyOS 中,进程被系统强制终止(崩溃)的原因通常有三类:

  • 未捕获异常:最常见的,JS/ArkTS 抛异常但没人 catch。
  • Native 层错误:如 C++ 野指针、内存越界导致 Segmentation Fault。
  • 系统资源问题:内存 OOM、线程爆炸。

4.2 异常处理分层模型

HarmonyOS 的异常处理可以理解为三层保护:

  • 应用层:try-catch、Promise.catch、全局异常监听。
  • Runtime 层:ArkTS/JS 引擎负责异常传播和崩溃收集。
  • 系统层:内核最终决定是否 kill 进程。

关键原则:开发者唯一能控制的是"异常发生后,能不能接住"。

4.3 四层兜底防御实战

基础防御:try-catch

90% 的崩溃都是因为"懒得写 try-catch"。在可能抛出异常的代码块中包裹 try-catch,捕获后记录日志或降级处理。

typescript 复制代码
try {
  const data = JSON.parse(response);
  this.processData(data);
} catch (err) {
  console.error('解析失败:', err);
  this.showFallbackUI();
}

Promise 异常捕获

异步请求必须写 .catch(),否则异常会直接导致未捕获异常崩溃。

typescript 复制代码
fetchData()
  .then(data => this.handleData(data))
  .catch(err => {
    console.error('请求失败:', err);
    this.showErrorToast();
  });

全局异常监听

使用 errorManager.on('error', callback) 注册全局错误监听,这是最后一道防线,确保任何未被捕获的异常都能被记录。

typescript 复制代码
import { errorManager } from '@kit.AbilityKit';

errorManager.on('error', (err: Error) => {
  console.error('全局异常:', err.message, err.stack);
  // 上报到监控平台
  reportError(err);
});

Ability 生命周期异常处理

在 onCreate、onForeground、onBackground 等生命周期回调中包裹 try-catch,因为这些地方一旦崩溃,用户连界面都看不到。

五、资源泄漏的运行态检测

5.1 HiDebug 资源采集能力

HarmonyOS 6.1 带来的 HiDebug 资源采集 API,通过 Performance Analysis Kit 开放的 OH_HiDebug_StartProfiler / OH_HiDebug_StopProfiler,应用可以在合适条件下采集资源分配栈,把"感觉有泄漏"变成"这里有证据"。

支持的资源类型:

  • 文件描述符:通过 open/fopen、epoll、eventfd、socket 等创建的 FD。
  • 线程 :通过 pthread_create 创建的线程。
  • Native 内存:通过 malloc/calloc/realloc/new 分配的堆内存,以及 mmap 映射的内存。
  • GPU 内存:通过 OpenGL ES、Vulkan、OpenCL 等创建的纹理和缓冲区。
  • 全局句柄 :Native 侧通过 napi_create_reference 创建的 ArkTS 对象全局引用。

5.2 资源泄漏排查思路

线上资源问题往往很隐蔽,开发者能看到"应用越跑越卡""内存曲线一路抬头""线程数不知不觉变多""FD 数量悄悄逼近上限"等现象,但要回答"是谁分配的""从哪条调用链来的""为什么没释放",光靠流水日志往往不够。

使用 HiDebug 资源采集 API 时,type 参数用于选择采集资源类型,你怀疑谁不老实,就先把观察镜头对准谁。config 参数中的采集参数需要平衡"有用"和"别太打扰用户"之间的关系,maxDuration 控制采集时长,maxStackDepth 控制采集栈深度,sampleInterval 控制采样间隔。

5.3 资源泄漏修复示例

typescript 复制代码
// ❌ FD 泄漏:打开文件后未关闭
const file = fileIo.openSync(path, fileIo.OpenMode.READ_ONLY);
const content = fileIo.readSync(file.fd, buffer);
// 忘记 closeSync(file)

// ✅ 修复:使用 try-finally 确保关闭
const file = fileIo.openSync(path, fileIo.OpenMode.READ_ONLY);
try {
  const content = fileIo.readSync(file.fd, buffer);
} finally {
  fileIo.closeSync(file);
}

六、线上性能监控与告警

6.1 HiAppEvent 事件订阅

在应用初始化阶段注册观察者,订阅应用冻屏、崩溃和资源超限事件。推荐使用 sceneRunId 将同一次复现统一关联,按"场景脚本 → 主动采样 → 系统故障事件 → external_log → Profiler/Trace 证据 → 修复后同脚本复测"串起来。

typescript 复制代码
import { hiAppEvent, hilog } from '@kit.PerformanceAnalysisKit';

const DOMAIN: number = 0x1200;
const TAG: string = 'StabilityWatcher';

export function registerStabilityWatcher(): void {
  hiAppEvent.addWatcher({
    name: 'stability_watcher_v1',
    appEventFilters: [{
      domain: hiAppEvent.domain.OS,
      names: [
        hiAppEvent.event.APP_FREEZE,
        hiAppEvent.event.APP_CRASH,
        hiAppEvent.event.RESOURCE_OVERLIMIT
      ]
    }],
    onReceive: (domain: string, groups: Array<hiAppEvent.AppEventGroup>): void => {
      groups.forEach((group: hiAppEvent.AppEventGroup) => {
        group.appEventInfos.forEach((info: hiAppEvent.AppEventInfo) => {
          const logPaths: string = JSON.stringify(info.params['external_log'] ?? []);
          hilog.info(DOMAIN, TAG,
            'fault domain=%{public}s name=%{public}s time=%{public}s logs=%{public}s',
            domain, info.name, String(info.params['time'] ?? ''), logPaths);
          // 实际项目在这里把元数据写入本地待上报队列;不要读取或上传用户内容
        });
      });
    }
  });
}

注意事项:

  • APP_CRASH 本身不能证明 OOM,要解析 external_log 中的 jscrash,确认 OutOfMemoryError,或看 RESOURCE_OVERLIMIT 的 resource_type 是否为 js_heap。
  • 处理完外部日志要及时归档/删除,官方事件目录有容量上限;若 log_over_limit=true,本次日志可能未成功生成。

6.2 固定脚本复现与内存基线

建立四条独立脚本进行系统化复现:

  1. 前台/后台切换 100 次,每轮停留时间固定。
  2. 弱网下请求超时、取消、重试 200 次,记录并发数和未完成请求数。
  3. 同一核心路径运行 2~8 小时,每 5 分钟采样一次。
  4. 加入多个应用驻留制造系统内存压力。

每轮记录 ArkTS heap、native heap/PSS、Graphic/DMA、线程数、FD 数、Web/XComponent/播放器实例数、请求队列长度。多应用压力会改变 GC 和系统回收时机,只有在相同场景结束并完成 GC 后,基线仍随轮次单调增长,才有泄漏证据。推荐用线性回归记录 MB/100 轮,不要只比较峰值。

6.3 APMS 故障指标

APMS 基于应用现网运行的真实数据,端侧故障事件实时上报、云侧统计分析,为开发者提供崩溃、冻屏、应用终止、OOM、资源泄漏等常见故障的多维度指标数据。故障指标入口在 AppGallery Connect 开发与服务/质量/APMS/故障指标。

APMS 提供的故障指标分类:

  • 崩溃:CPP_CRASH / JS_ERROR
  • 应用冻屏:THREAD_BLOCK_6S / APP_INPUT_BLOCK / LIFECYCLE_TIMEOUT
  • 应用终止:THREAD_THRESHOLD_KILLER 等
  • OOM/资源泄漏:THREAD_LEAK / MEMORY_LEAK / FD_LEAK

指标页面提供四块功能:

  • 指标查询:过滤项包括应用版本、系统版本、设备型号、崩溃类型、时间范围,支持天/小时/分钟维度。
  • 版本对比:当前版本与对比版本的指标对比。
  • 维度分布:应用版本、系统版本、设备型号的分布,确定问题发生应用版本或高频发生的系统版本与设备型号。
  • TOP 问题:汇总异常日志形成问题列表,可跳转至故障分析进行问题定位。

6.4 FaultLogExtensionAbility 延迟通知

从 API version 21 开始,可以在 FaultLogExtensionAbility 中使用 HiAppEvent 事件订阅接口,实现应用故障事件(仅包括崩溃事件和应用冻屏事件)的延迟通知。应用因崩溃或冻屏退出后,无法启动或长时间未启动的场景下,可以不依赖应用启动实现故障事件信息的订阅回调。

工作原理:

  1. 主进程启动后添加事件观察者 A(包含正常回调处理函数)和事件观察者 B(空实现,仅用于生成过滤条件)。
  2. 两个观察者的订阅过滤条件被保存到应用沙箱中。
  3. 应用发生故障后,系统采集故障信息并保存到沙箱。
  4. 若应用及时重启,由观察者 A 处理;若应用未及时重启,系统在故障发生后 30 分钟拉起 FaultLogExtensionAbility 进程,该进程中添加与 B 同名的事件观察者并实现正常回调,处理沙箱中未回调的故障事件。

约束 :FaultLogExtensionAbility 被拉起后只有 10 秒的时间用以完成故障处理,超时未处理完成可以在 onDisconnect 中保存状态。从开机或上次拉起 FaultLogExtensionAbility 后,应用首次触发崩溃或冻屏开始计时,30 分钟内事件均不会重新计时。

七、多元化习题

习题 1(判断题)

题目:在 ArkUI 中,AppFreeze 的 THREAD_BLOCK_6S 机制表示应用主线程卡死超过 6 秒,系统会强制终止该应用。

答案:正确

解读:AppFreeze 是一种看门狗机制。当应用主线程被长时间阻塞,无法及时响应用户输入或系统调度时,系统会强制终止该应用。THREAD_BLOCK_6S 表示应用主线程卡死超时,是 AppFreeze 最常见的检测机制。

习题 2(单选题)

题目:以下哪个工具可以用于分析 ArkTS 状态变量的变化,定位因冗余刷新导致的卡顿问题?

A. HiDebug

B. JSLeakWatcher

C. ArkUI 模板的 ArkUI State 泳道

D. FaultLogExtensionAbility

答案:C

解读:ArkUI 模板的 ArkUI State 泳道可以查看录制过程中发生的状态变量变化,包括状态变量名称、变化次数、状态变量类型、所属组件和所属类,定位到可能造成卡顿的状态变量变化时间点。HiDebug 用于资源泄漏检测,JSLeakWatcher 用于内存泄漏检测,FaultLogExtensionAbility 用于故障事件的延迟通知。

习题 3(多选题)

题目:关于 JSLeakWatcher,以下说法正确的有(多选):

A. JSLeakWatcher 可以监控 Ability、Window、NodeContainer、XComponent 和自定义组件五类对象

B. JSLeakWatcher 通过 FinalizationRegistry 机制注册组件对象的 GC 回调

C. JSLeakWatcher 检测到泄漏后生成 rawheap 和 jsleaklist 文件

D. JSLeakWatcher 的 FullGC 默认每 60 秒执行一次

答案:A、B、C

解读:JSLeakWatcher 监控的 5 类对象包括元能力-Ability、窗口-Window、NodeContainer、XComponent、自定义组件-CustomComponent,选项 A 正确。检测框架创建 FinalizationRegistry 对象,用于监控系统内具有生命周期的 5 类常见 ArkTS 组件对象,选项 B 正确。检测到泄漏后会生成泄漏信息文件,包括 rawheap 文件和 jsleaklist 文件,选项 C 正确。FullGC 默认每 90 秒执行一次,不是 60 秒,选项 D 错误。

习题 4(代码填空题)

题目:请补全以下 AppFreeze 日志分析代码,判断主线程是否因当前任务耗时过长导致卡死。

typescript 复制代码
// 在 AppFreeze 日志中搜索 THREAD_BLOCK_6S 段落
// 找到 mainHandler dump 区域
// 对比 Current Running: start at ... 与 EventHandler dump begin curTime: ...

// 若差值接近或超过 6 秒,说明是当前正在运行的任务耗时过长
// 若差值很小,需要排查主线程是否 ______________

答案:积压大量任务导致调度不及时

解读:AppFreeze 日志分析中,如果当前任务开始时间与日志抓取检测时间的差值接近或超过 6 秒,说明是当前正在运行的任务耗时过长直接导致卡死。如果差值很小(如 1 秒内),说明当前任务并未超时,需要排查主线程是否积压大量任务导致调度不及时。

习题 5(代码改错题)

题目:以下代码在异常处理方面存在不足,请指出问题并完善。

typescript 复制代码
async function fetchUserData(userId: string): Promise<UserData> {
  const response = await httpRequest(`/api/user/${userId}`);
  const data = JSON.parse(response);
  return data as UserData;
}

答案:该代码存在两个问题:HTTP 请求没有错误处理,JSON 解析可能因数据格式异常抛出未捕获异常。完善如下:

typescript 复制代码
async function fetchUserData(userId: string): Promise<UserData | null> {
  try {
    const response = await httpRequest(`/api/user/${userId}`);
    if (!response) {
      console.error('请求返回为空');
      return null;
    }
    const data = JSON.parse(response) as UserData;
    return data;
  } catch (err) {
    console.error('获取用户数据失败:', err);
    return null;  // 降级返回 null,由调用方处理
  }
}

解读:90% 的崩溃都是因为"懒得写 try-catch"。异步请求和 JSON 解析都必须做好异常处理,捕获后记录日志或降级处理,避免未捕获异常直接导致崩溃。

习题 6(简答题)

题目:简述 AppFreeze 冻屏日志的三步定位法,以及每一步的核心目的。

答案:AppFreeze 冻屏日志的三步定位法为:第一步,确认故障类型与时间差。打开日志文件,查看 Reason 字段确认触发机制,搜索 THREAD_BLOCK_6S 段落,对比当前任务开始时间与日志抓取检测时间。目的是判断是当前任务耗时过长还是主线程任务积压。第二步,排查消息队列积压。查看 Event runner 下方的队列统计信息,检查高优先级事件数量。目的是确认是否存在业务代码在短时间内发送大量高优先级任务导致主线程被占满。第三步,分析主线程堆栈。根据栈顶函数判断卡死原因,区分 IPC 通信阻塞、等锁卡死、业务代码耗时/死循环等场景。目的是找到导致主线程阻塞的具体根因。

解读:三步定位法的核心逻辑是"先判断方向,再排查积压,最后定位根因"。从日志的时间差判断是单任务耗时还是任务积压,再通过堆栈分析找到具体的阻塞点。

习题 7(简答题)

题目:简述如何建立"开发态检测 → 运行态监控 → 线上告警 → 修复验证"的全链路稳定性治理闭环。

答案:全链路稳定性治理闭环包括四个环节:

开发态检测:使用 DevEco Profiler 的 Frame/ArkUI 模板分析卡顿丢帧,使用 JSLeakWatcher 检测内存泄漏,使用 ArkUI Inspector 排查布局层级问题。

运行态监控:在应用初始化阶段注册 HiAppEvent 观察者,订阅 APP_FREEZE、APP_CRASH、RESOURCE_OVERLIMIT 等事件,使用 HiDebug 采集 FD、线程、Native 内存等资源分配栈,通过固定脚本定期复现并记录内存基线。

线上告警:使用 APMS 故障指标进行多维度分析,包括指标查询、版本对比、维度分布和 TOP 问题汇聚,识别崩溃率、冻屏率、OOM 等指标的趋势变化。

修复验证:使用同一脚本复测,对比修复前后的指标数据,确认问题已解决且未引入新问题。四个环节形成从本地到线上、从手动到自动的完整治理体系。

解读:稳定性治理不是一次性的优化行为,而是贯穿开发全流程的持续实践。核心在于将稳定性验证自动化、常态化,在性能退化早期就发现并修复问题,而非等到用户反馈后再排查。

习题 8(简答题)

题目:简述 HiDebug 资源采集 API 支持的资源类型,以及如何利用它排查资源泄漏。

答案 :HiDebug 资源采集 API 支持五种资源类型:文件描述符(通过 open/fopen、epoll、eventfd、socket 等创建的 FD)、线程(通过 pthread_create 创建的线程)、Native 内存(通过 malloc/calloc/realloc/new 分配的堆内存,以及 mmap 映射的内存)、GPU 内存(通过 OpenGL ES、Vulkan、OpenCL 等创建的纹理和缓冲区)、全局句柄(Native 侧通过 napi_create_reference 创建的 ArkTS 对象全局引用)。排查资源泄漏时,通过 OH_HiDebug_StartProfiler / OH_HiDebug_StopProfiler 采集资源分配栈,type 参数选择怀疑的资源类型,config 参数控制采集时长、栈深度和采样间隔。采集结果可以显示资源的分配调用栈,从而定位到具体是哪段代码分配了未释放的资源。

解读:HiDebug 资源采集的核心价值在于将"感觉有泄漏"变成"这里有证据"。通过采集分配栈,开发者可以精准定位到泄漏资源的分配位置,大幅缩短排查时间。

八、本节知识点总结

卡顿丢帧根因分析

使用 Frame 模板定位连续丢帧帧,通过 H:ExecuteJS / FlushDirtyNodeUpdate 判断主线程问题,通过 RSUniRenderThread / 纹理判断渲染侧问题。使用 ArkUI 模板分析组件绘制耗时和状态变量变化,定位冗余刷新。ArkUI Inspector 用于排查布局层级问题。

ArkTS 内存泄漏检测

JSLeakWatcher 通过 FinalizationRegistry 机制监控 5 类 ArkTS 组件对象,每 90 秒执行 FullGC,生成 rawheap 和 jsleaklist 文件。导入 IDE 后可查看泄漏对象列表和引用链。常见泄漏原因包括 Native 层强引用、闭包捕获、全局缓存。

AppFreeze 冻屏分析

三步定位法:确认故障类型与时间差 → 排查消息队列积压 → 分析主线程堆栈。常见场景包括 IPC 通信阻塞、等锁卡死、业务代码耗时/死循环。采样栈可识别锁竞争导致的频繁等锁故障模式。

崩溃异常处理体系

崩溃三类根因:未捕获异常、Native 层错误、系统资源问题。四层兜底防御:try-catch、Promise.catch、全局异常监听、Ability 生命周期异常处理。

资源泄漏运行态检测

HiDebug 资源采集 API 支持 FD、线程、Native 内存、GPU 内存、全局句柄五种资源类型。通过 OH_HiDebug_StartProfiler / OH_HiDebug_StopProfiler 采集资源分配栈。

线上性能监控

HiAppEvent 订阅 APP_FREEZE、APP_CRASH、RESOURCE_OVERLIMIT 事件。APMS 提供崩溃、冻屏、OOM、资源泄漏等多维度故障指标。FaultLogExtensionAbility 实现 30 分钟延迟通知,处理应用未及时启动的故障补报场景。

全链路治理闭环

开发态检测 → 运行态监控 → 线上告警 → 修复验证。核心原则:自动化、常态化,在性能退化早期发现并修复问题。

下节预告

第8课将进入 ArkUI 的 AI 能力集成的学习,涵盖端侧 AI 推理框架、AI 辅助代码生成、智能 UI 适配以及 AI 驱动的性能优化。

相关推荐
艾莉丝努力练剑1 小时前
【QT】系统相关:多线程QThread基础
开发语言·网络·c++·qt·学习·大模型
sunshine22 girl1 小时前
Java学习五 面向对象高级5 内部类4-匿名内部类(重点)
java·学习
妄汐霜1 小时前
SSE,RocketMQ,MQTT不同之处
笔记·学习·rocketmq
starsky762382 小时前
Agent学习——AI Agent总体概览
学习
東隅已逝,桑榆非晚2 小时前
c++模板进阶
开发语言·c++·笔记·学习
JWASX2 小时前
Java 转 go 学习 - map
学习·golang
扶风ff11 小时前
新品知识更新太快?用练题簿在线刷题,安排企业培训的小测与复盘
前端·学习·小程序
传奇开心果编程13 小时前
【SwiftUI提高练中学】第13课 实时活动与灵动岛:ActivityKit 实战
学习·ui·ios·swiftui·swift
欣欣之王来了13 小时前
Python入门:什么是Python以及为什么选择它
学习·架构·面向对象·项目·python教程