今天的是Flutter鸿蒙【DFX系列】的第四篇:卡死问题专项。跟前3篇一样,都是实战干货。所谓的应用卡死,也叫应用冻屏幕 - AppFreeze,这个异常和崩溃问题性质不同,因为进程还在,但是界面不动了。

一、卡死问题该从哪定位?

Flutter 鸿蒙应用有三条关键线程,任何一条的「罢工」都会导致应用卡死:

线程负责什么卡死时的表现严重度
UI 线程运行 Dart 代码、构建界面界面完全冻结,触摸无响应致命
Raster 线程把界面画成像素交给 GPU界面不更新,触摸可能有响应严重
Platform 线程处理触摸事件、系统消息系统消息阻塞,可能间接卡 UI严重

引擎遇到卡死问题,DFX是怎么设计去保留证据链的呢?这里有套机制叫Watchdog,功能机制与其名字一样,会像一个巡逻Dog一样进行监测,大致机制是每3秒会给UI线程发一次请求,请求有响应了,表明正常,如果没响应,就会触发报警。报警分两个阶段:

阶段卡死时间会发生什么用户能看到吗
第 1 阶段3 秒记录到日志,不弹窗可能感觉到卡,但没弹窗
第 2 阶段6 秒上报系统,生成 APP_FREEZE弹「应用无响应」弹窗,可选等待或关闭

Watchdog直接监控的是 UI 线程。Raster 和 Platform 线程阻塞不会被直接检测到,但会间接把 UI 线程拖死。比如 Raster 不画帧,UI 线程等着提交帧也导致卡住,最终都会体现在 UI 线程的看门狗报警上。

二、我们如何定位:先搜三个词,再看前 10 秒日志

有问题抓日志,日志到手,按固定顺序搜三个词定层:

# 搜到 "FlutterUiThread is not alive" → UI 线程卡死,看第三节
hdc shell hilog | grep "is not alive"
# 搜到 → 看门狗检测到卡死,看 description 确定哪个线程
hdc shell hilog | grep "FLUTTER_THREAD_STUCK"
# 搜到 → UI 线程卡死超过 6 秒,系统要杀进程了
hdc shell hilog | grep "APP_FREEZE"

确认卡死之后,就要找原因了,确认为什么卡,三个方向各搜一个词:

# GPU 回收导致 Raster 阻塞 → 看第四节
hdc shell hilog | grep "GpuReclaim"
# 平台通道阻塞 → 看第五节
hdc shell hilog | grep "handlePlatformMessage"
# 纹理回调阻塞 → 看第四节
hdc shell hilog | grep "external_texture"

最关键的一步:定位到关键字后,日志向前看 5-10 秒,按DFX机制,卡死的原因肯定是在Watchdog监测到之前就发生的。

三、UI 线程卡死

3.1 怎么确认

搜索命令:

hdc shell hilog | grep -E "FlutterUiThread|FlutterWatchdog|HiCollie"

典型的卡死日志如下,3 秒和 6 秒两个阶段各来一轮:

[T+3s] E Flutter: FlutterWatchdog: FlutterUiThread is not alive
[T+3s] W Flutter: FlutterWatchdog: calling OH_HiCollie_Report(), m_is_six_second_event = false
[T+3s] I Flutter: FlutterWatchdog: OH_HiCollie_Report() success
[T+3s] HiAppEvent: FLUTTER_STABILITY_EVENT, eventName=FLUTTER_THREAD_STUCK
            description: "Flutter UI thread stuck. isSixSecondEvent=false"
[T+6s] E Flutter: FlutterWatchdog: FlutterUiThread is not alive
[T+6s] W Flutter: FlutterWatchdog: calling OH_HiCollie_Report(), m_is_six_second_event = true
[T+6s] HiAppEvent: FLUTTER_STABILITY_EVENT, eventName=FLUTTER_THREAD_STUCK
            description: "Flutter UI thread stuck. isSixSecondEvent=true"
[T+6s] 系统生成 APP_FREEZE 事件,可能弹出「应用无响应」弹窗

教你阅读日志:FlutterUiThread is not alive 说明 UI 线程 3 秒没响应了;m_is_six_second_event = false 是第 1 阶段,卡死 3 秒,只记录不弹窗;m_is_six_second_event = true 是第 2 阶段,卡死 6 秒,可能弹窗;由于APP_FREEZE 是系统级卡死事件,可能就会导致杀进程。

3.2 三步进行排查

第 1 步,还是搜日志,命令如下:

hdc shell hilog | grep -E "FlutterUiThread|FlutterWatchdog|FLUTTER_THREAD_STUCK"

第 2 步最重要,需要会看日志,主要是看卡死前的日志:

hdc shell hilog > flutter_log.txt
# 在日志文件中找到 "is not alive" 的行,往上翻 5-10 秒的日志
# 那里面通常就能定位到原因,现在AI时代了,看不懂可以直接贴日志让AI分析

第 3 步,能复现的话就抓HiTrace,下面是抓取命令,主要定位分析心跳是否中断了(方法见第九节):

hdc shell hitrace --trace_clock boottime -t 30 flutter -o /data/local/tmp/trace.ftrace
hdc file recv /data/local/tmp/trace.ftrace ./trace.ftrace
# Chrome 打开 chrome://tracing,Load 文件,搜 "feedFlutterWatchdog"

3.3 四个常见原因与修复

原因 1:死循环或死锁。代码陷入无限循环,或者两个锁互相等待,UI 线程永远跑不出来,典型异常代码如下:

// 错误:死循环,condition 永远不为 true
while (true) {
  if (condition) break;
}
// 错误:死锁——线程 A 拿锁 1 等锁 2,线程 B 拿锁 2 等锁 1
// 正确:避免嵌套锁,加超时
await lock.synchronized(() async {
  await someWork();
}, timeout: Duration(seconds: 5));

原因 2:主线程做耗时操作。以文件读写和网络请求为例,以下为典型代码:

// 错误:主线程同步读大文件,阻塞 UI
final data = File('large_file.json').readAsStringSync();
// 正确:异步 IO
final data = await File('large_file.json').readAsString();
// 正确:compute() 把耗时任务放到独立 isolate
final data = await compute(_readFile, 'large_file.json');
// 正确:分批处理大数据,让 UI 有机会响应
for (var i = 0; i < list.length; i += batchSize) {
  await Future.delayed(Duration.zero);
}

原因 3:复杂计算阻塞。主线程里处理大量数据,常见异常代码如下:

// 错误:UI 线程里解析 10 万条,会卡死
final list = jsonDecode(json) as List;
return list.map((e) => Data.fromJson(e)).toList();
// 正确:compute 移到独立 isolate
final data = await compute(_parseData, json);

原因 4:等锁但持有者不释放。这个多线程玩家一看就懂了,异常代码如下:

// 错误:无超时等待,持有者崩了锁就永远不释放
await lock.synchronized(() async { /* ... */ });
// 正确:加超时
await lock.synchronized(() async { /* ... */ }, timeout: Duration(seconds: 5))
  .catchError((e) {
    print('锁超时: $e');
    return null;
  });

四、Raster 线程阻塞

Raster 线程是负责把界面画成像素。它卡了不会被Watchdog直接检测到,但帧画不出来,最终把 UI 线程也拖死。这边定位就要结合Trace抓取和分析了,命令行如下:

# GPU 相关:GPU 回收经常导致 Raster 阻塞
hdc shell hilog | grep -E "GpuReclaim|Teardown|SetDisplayWindow|Surface"
# 外接纹理相关
hdc shell hilog | grep -E "external_texture|NativeImage|AcquireNativeWindowBuffer"
# Trace:搜 feedFlutterRasterWatchdog 看 Raster 心跳,搜 flutter::Frame 看帧耗时
hdc shell hitrace --trace_clock boottime -t 30 flutter sched -o /data/local/tmp/trace.ftrace

有五个常见原因:包括GPU 管线阻塞,Vulkan 渲染开销大,纹理回调阻塞,图层太多和GPU回收期间阻塞,这些问题的根因后续会陆续拆解,期待后续篇章更新。

五、Platform 线程阻塞

Platform 线程负责和 OHOS 系统交互。排查的时候先搜:

hdc shell hilog | grep -E "handlePlatformMessage|onTouchEvent|MethodChannel|DartMessenger"

原因 1:ArkTS 插件处理 MethodChannel 消息太慢。Platform 线程收到 Dart 发来的消息后同步调用插件,插件里有同步 IO 就把线程卡住了:

// 错误:onMethodCall 里同步读文件,阻塞
onMethodCall(call: MethodCall, result: MethodResult): void {
  const data = fs.readFileSync(path);
  result.success(data);
}
// 正确:异步 IO
async onMethodCall(call: MethodCall, result: MethodResult): Promise<void> {
  try {
    const data = await fs.readFile(path);
    result.success(data);
  } catch (e) {
    result.error("IO_ERROR", e.message, null);
  }
}

原因 2:Platform 线程同步等待 Raster 线程。引擎里有六处地方会让 Platform 线程同步等 Raster 完成,Raster 正忙时 Platform 就被卡住。

六处分别是:Surface 窗口变化(NotifySurfaceWindowChanged)、窗口尺寸变化(NotifyChanged)、引擎销毁(NotifyDestroyed)、纹理注销(UnRegisterExternalTexture)、纹理替换(SetExternalNativeImage)和纹理重置(ResetExternalTexture)。这些都是框架层面的事情了,了解下就行。

原因 3:触摸事件处理太慢。Platform 线程还负责分发触摸事件,触摸回调里做耗时操作会阻塞线程。

六、日志关键字速查表

关键字含义什么时候出现
FlutterUiThread is not aliveUI 线程卡死了UI 卡死时,每次都有
calling OH_HiCollie_Report()正在上报卡死UI 卡死时
OH_HiCollie_Report() success上报成功UI 卡死时
m_is_six_second_event = false第 1 阶段,卡死 3 秒卡死 3 秒时
m_is_six_second_event = true第 2 阶段,卡死 6 秒卡死 6 秒时
thread may be blocked, do not report防误报跳过高负载时偶尔出现,正常
FLUTTER_THREAD_STUCK卡死事件每次卡死上报
APP_FREEZE系统级卡死事件卡死 6 秒时
GpuReclaimGPU 回收,卡死的间接原因GPU 相关卡死
handlePlatformMessage平台消息,卡死的间接原因Platform 阻塞

七、HiAppEvent 事件

卡死的引擎侧事件是 FLUTTER_STABILITY_EVENT(类型 FAULT),eventName 为 FLUTTER_THREAD_STUCK:

参数说明
frameworkName固定 “FLUTTER”
eventName固定 “FLUTTER_THREAD_STUCK”
description哪个线程卡死、卡了多久,读法见下表
pid进程 ID
timeStamp事件时间

description 字段理解:

description 内容通俗解释
Flutter UI thread stuck. isSixSecondEvent=falseUI 线程卡了 3 秒,还没弹窗
Flutter UI thread stuck. isSixSecondEvent=trueUI 线程卡了 6 秒,可能弹窗了
Flutter Raster thread stuck. isSixSecondEvent=falseRaster 线程卡了
Flutter Platform thread stuck. isSixSecondEvent=falsePlatform 线程卡了

八、排查清单

第一步,确认卡死: 抓全量日志 hdc shell hilog > flutter_log.txt;搜 is not alive 确认哪个线程卡死;搜 FLUTTER_THREAD_STUCK 看事件详情;搜 APP_FREEZE 确认是否触发系统事件;查看卡死前 5-10 秒的日志,这步最重要。

第二步,找原因: 搜 GpuReclaim,看是否 GPU 回收导致;搜 external_texture,看是否纹理回调阻塞;搜 handlePlatformMessage,看是否平台通道阻塞;检查 Dart 代码有没有死循环、死锁;检查主线程有没有同步 IO、网络请求;检查有没有复杂计算阻塞 UI 线程。

第三步,HiTrace 分析: 抓 hdc shell hitrace --trace_clock boottime -t 30 flutter;搜 feedFlutterWatchdog 查 UI 心跳;搜 feedFlutterRasterWatchdog 查 Raster 心跳;搜 flutter::Frame 看帧渲染耗时;记录卡死时的操作场景。

九、Trace 分析:看心跳

正常情况下,两条心跳每 3 秒各出现一次,是规律的:

[0.0s] feedFlutterWatchdog        ← UI 心跳
[0.0s] feedFlutterRasterWatchdog  ← Raster 心跳
[3.0s] feedFlutterWatchdog        ← UI 心跳
[3.0s] feedFlutterRasterWatchdog  ← Raster 心跳

UI 线程卡死时,心跳断了,feedFlutterWatchdog 不再出现,取而代之的是保安的巡逻检查:

[0.0s] feedFlutterWatchdog        ← 最后一次正常心跳
[3.0s] runHiCollieStuckDetectionTask  ← 发现 UI 没签到
[6.0s] runHiCollieStuckDetectionTask  ← 触发 6 秒报警

抓取和分析:30 秒 Trace,期间复现卡死操作

hdc shell hitrace --trace_clock boottime -t 30 flutter -o /data/local/tmp/trace.ftrace
hdc file recv /data/local/tmp/trace.ftrace ./trace.ftrace

如何分析Trace,第二篇中有简单介绍,后续有需要可以详细出个调试调优工具专题,大家可以先回顾下


以上就是今天的全部了,下一篇会展开卡顿丢帧专项,大家期待下~

小伙伴们记得点赞+关注

关注 CPF-Flutter 社区

AI再牛,技术不能丢

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐