【DFX系列】Flutter 鸿蒙应用卡死问题定位指南
今天的是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 alive | UI 线程卡死了 | 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 秒时 |
| GpuReclaim | GPU 回收,卡死的间接原因 | 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=false | UI 线程卡了 3 秒,还没弹窗 |
| Flutter UI thread stuck. isSixSecondEvent=true | UI 线程卡了 6 秒,可能弹窗了 |
| Flutter Raster thread stuck. isSixSecondEvent=false | Raster 线程卡了 |
| Flutter Platform thread stuck. isSixSecondEvent=false | Platform 线程卡了 |
八、排查清单
第一步,确认卡死: 抓全量日志 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再牛,技术不能丢
更多推荐




所有评论(0)