【DFX系列】Flutter 鸿蒙应用如何排查黑屏,白屏,OOM闪退和内存持续增长异常
在Flutter鸿蒙应用调优过程中,遇到了黑屏,白屏,OOM 闪退和内存持续增长异常,该怎么排查?这四类问题总结成了一句口诀,大家可以细品:内存看 old 还是 new,黑屏看 REBUILT 还是 failed,引擎看 CREATE 和 DESTROY 配不配对,不渲染看 Vsync 句柄。怎么理解呢,大家接着往下看:
一、四类问题总览
| 问题 | 看到的现象 | 日志关键词 | 通俗解释 |
|---|---|---|---|
| Dart 内存超限 | 应用卡顿、OOM 闪退 | Dart heap memory usage exceeds threshold | Dart 代码用了太多内存,超过 1.5GB |
| GPU 上下文丢失 | 黑屏、白屏、渲染异常 | GpuReclaim | 退后台时系统回收了 GPU 资源 |
| 引擎生命周期异常 | 引擎创建、销毁异常 | FLUTTER_ENGINE_CREATE/DESTROY | 引擎没正常创建或被意外销毁 |
| Vsync 管道故障 | 不渲染、渲染卡顿 | vsync / Poll error | 垂直同步信号出问题了 |
二、Dart 内存超限
2.1 先理解机制
引擎每隔 10 秒检查一次 Dart 代码用了多少内存,如果超过 1.5GB 就会报警并记录日志:
| 参数 | 值 | 说明 |
|---|---|---|
| 阈值 | 1.5 GB | 超过就报警 |
| 检查间隔 | 10 秒 | 每 10 秒检查一次 |
| 触发次数 | 单次 | 报警一次后停止监控 |
注意:1.5GB 是引擎的检测阈值,系统级 OOM可能在更低的内存量时发生,因为系统是看的整体内存压力,不只是 Dart 的。
2.2 异常现象有哪些
| 看到的现象 | 严重度 |
|---|---|
| 应用越来越卡 | 中 |
| 应用 OOM 闪退,被系统杀 | 高 |
| Old Space 持续增长不回落 | 高 |
| 报警后不再监控 | — |
| 只有 HiLog 没有 HiAppEvent | 中 |
2.3 三步进行排查
第 1 步,搜日志确认是否触发:
hdc shell hilog | grep -E "heap memory|threshold|FrameworkMemAnomaly"
典型日志:
W Flutter: Dart heap memory usage exceeds threshold:
total = 1600 MB (old = 1200 MB, new = 400 MB)
-
total = 1600 MB理解:总共用了 1600MB(超过 1.5GB 阈值了) -
old = 1200 MB理解:Old Space(长期存活的对象)用了 1200MB- 如果 old 持续增长不回落 = 内存泄漏!
-
new = 400 MB理解:New Space(新创建的临时对象)用了 400MB- new 高但 GC 后回落 = 正常,只是短时间分配太多
第 2 步,按内存分布判断问题类型:
| 内存分布 | 诊断 | 怎么办 |
|---|---|---|
| old 高且持续增长 | 内存泄漏 | 检查 dispose() 是否正确释放资源 |
| new 高但 GC 后降 | 短时间大量分配 | 优化批量操作,减少临时对象 |
| old 和 new 都高 | 综合问题 | 先治泄漏,再优化分配 |
第 3 步,用 DevTools Memory 精确定位
2.4 四个常见原因与修复
原因 1:图片缓存太多
现象:加载很多图片后内存涨,页面退出后不回落。
// 检查图片缓存使用量
print(PaintingBinding.instance.imageCache.currentSize);
// 缓存了多少张
print(PaintingBinding.instance.imageCache.currentSizeBytes);
// 缓存占多少字节
// 限制缓存大小:100 张 / 50MB(默认 1000 张 / 100MB)
PaintingBinding.instance.imageCache.maximumSize = 100;
PaintingBinding.instance.imageCache.maximumSizeBytes = 50 * 1024 * 1024;
// 大图按显示尺寸解码,不解码全尺寸
Image.network(url,
cacheWidth: (screenWidth * devicePixelRatio).toInt(),
cacheHeight: (screenHeight * devicePixelRatio).toInt(),
);
原因 2:大列表没有懒加载
现象:长列表页面内存暴涨。
// 错误:一次性构建所有子项,1000 个全构建
ListView(children: items.map((e) => ItemWidget(e)).toList());
// 正确:懒加载,只构建可见的;更好:加 itemExtent 免测量
ListView.builder(
itemExtent: 80.0,
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(items[index]),
);
原因 3:资源没释放,内存泄漏
现象:页面退出后内存不回落,反复进出持续增长。
class _MyPageState extends State<MyPage> {
late StreamSubscription _sub;
late AnimationController _controller;
late TextEditingController _textController;
@override
void dispose() {
// 必须释放,否则就是内存泄漏
_sub.cancel();
// 取消 Stream 订阅
_controller.dispose();
// 释放动画控制器
_textController.dispose();
// 释放文本控制器
super.dispose();
}
}
原因 4:Isolate 没关闭
现象:用了 Isolate.spawn 后内存不释放。
Isolate? isolate;
Future<void> startWork() async {
final receivePort = ReceivePort();
isolate = await Isolate.spawn(_entryPoint, receivePort.sendPort);
}
void stopWork() {
isolate?.kill(priority: Isolate.immediate);
// 用完后关闭
isolate = null;
}
// 提示:compute() 自动管理 Isolate 生命周期,优先使用
final result = await compute(_heavyTask, data);
三、GPU 上下文丢失
3.1 先理解机制
应用退到后台或系统内存不足时,鸿蒙系统会回收 GPU 资源。退后台时引擎会自动处理清理 GPU 资源,回前台时重建 GPU 上下文。在重建期间如果丢失了上下文,屏幕就会持续黑屏。
3.2 现象
| 看到的现象 | 严重度 | 说明 |
|---|---|---|
| 退后台再回前台,短暂黑屏后恢复 | 正常 | GPU 回收后成功恢复 |
| 回前台后持续黑屏、白屏 | 高 | Surface 重建失败了 |
| 回前台后画面卡顿几秒 | 中 | 重建耗时较长 |
| 图片显示异常 | 低 | 图片缓存被清理,需要重新加载 |
| 退后台后视频、纹理不更新 | 正常 | 帧闸门开启,正常行为 |
| 反复黑屏闪烁 | 中 | 生命周期状态抖动,频繁切换前后台 |
3.3 排查方法
搜 GpuReclaim,按时间线比对日志。一段正常的日志流水如下:
[T+0] GpuReclaim: Lifecycle Resumed -> Paused
← 退后台了
[T+0] GpuReclaim: Restore -> Aggressive
← 决定:回收 GPU
[T+0] GpuReclaim: ExecuteAggressive - deferring 1000ms
← 延迟 1 秒执行,等 PiP 检测
[T+0] GpuReclaim: Sending memoryPressure
← 通知 Dart 清理图片缓存
[T+0] GpuReclaim: [Raster] Surface torn down
← GPU 资源已释放
[T+N] GpuReclaim: Lifecycle Paused -> Resumed
← 回前台了
[T+N] GpuReclaim: Aggressive -> Restore
← 决定:恢复
[T+N] GpuReclaim: Rebuilding onscreen context
← 正在重建
[T+N] GpuReclaim: [Raster] Surface REBUILT
← 重建成功,画面恢复
看到 Surface REBUILT 就是成功,如果看到 SetDisplayWindow failed 就是失败了,确认失败后,就要具体再深度分析了。
3.4 三个常见问题与修复
问题 1:回前台后持续黑屏。
排查:搜 SetDisplayWindow failed during rebuild。
三种原因
(1)Surface 重建失败:检查 native_window 是否还有效
(2)重建未执行:检查 ExecuteRestore 日志是否存在
(3)应用层 onDestroy 被过早调用:确保 FlutterPage.onDestroy 在真正销毁时才调用
// FlutterPage.ets:确保 onLoad / onDestroy 正确配对
.onLoad((context) => {
this.flutterView = FlutterManager.getInstance().getFlutterView(this.viewId);
this.surfaceLifecycleToken = this.flutterView?.onSurfaceCreated() ?? 0;
})
.onDestroy(() => {
// 必须在真正销毁时才调用,不能提前
this.flutterView?.onSurfaceDestroyed(this.surfaceLifecycleToken);
this.flutterView = null;
})
问题 2:退后台后纹理、视频还在耗电。
排查:搜 frame gate enabled。
两种原因:
(1)生命周期没正确传到引擎:确保 onPageShow / onPageHide 被正确调用(2)插件没实现暂停逻辑:插件实现 onInactive() / onActive()。
应用层要转发页面可见性:
// FlutterAbilityAndEntryDelegate.ets 必须转发页面可见性
onPageShow() {
this.flutterView?.onPageVisibilityChanged(true);
}
onPageHide() {
this.flutterView?.onPageVisibilityChanged(false);
}
问题 3:图片显示异常。
原因:GPU 回收时图片缓存被清理了(Sending memoryPressure),重新显示时需要重新解码。
修法方法:回前台后预加载关键图片:
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
precacheImage(AssetImage('assets/key_image.png'), context);
}
}
3.5 关键日志速查
| 日志 | 通俗解释 | 正常吗 |
|---|---|---|
| Restore -> Aggressive | 开始回收 GPU | 正常,退后台时 |
| Aggressive -> Restore | 开始恢复 GPU | 正常,回前台时 |
| Surface torn down | GPU 资源已释放 | 正常 |
| Surface REBUILT | 重建成功 | 正常 |
| SetDisplayWindow failed | 重建失败 | 异常 |
| frame gate enabled | 帧闸门开启 | 正常,退后台时 |
| Sending memoryPressure | 通知清理图片缓存 | 正常 |
| Deferred aggressive cancelled - PiP visible | PiP 可见,取消回收 | 正常,PiP 场景 |
四、引擎生命周期
关键两个事件:FLUTTER_ENGINE_CREATE 和 FLUTTER_ENGINE_DESTROY
现象:
| 看到的现象 | 严重度 | 说明 |
|---|---|---|
| 应用启动后白屏 | 高 | 引擎没创建成功 |
| 应用闪退,无 Dart 异常 | 高 | 引擎意外销毁 |
| 内存持续增长 | 高 | 引擎重复创建但没销毁 |
| 内存监控不工作 | 中 | 引擎创建时没启动监控 |
排查:搜两个事件名,检查 CREATE 和 DESTROY 是否 1:1 配对。
如果CREATE 多于 DESTROY,说明有引擎没被销毁,意味着内存泄漏了:
hdc shell hilog | grep -E "FLUTTER_ENGINE_CREATE|FLUTTER_ENGINE_DESTROY"
问题 1:引擎创建失败,白屏。
三个检查点:
(1)Flutter 配置错误:检查 flutter_ohos.json5
(2)Dart bundle 加载失败:检查 flutter_assets 是否完整打包
(3)SO 库加载失败:检查 libflutter.so 是否正确打包。
问题 2:引擎重复创建,内存泄漏。每次都 new 一个新引擎,修法是复用:
// 错误:每次都创建新引擎
getEngine(): FlutterEngine {
return new FlutterEngine(context);
}
// 正确:复用引擎
private flutterEngine: FlutterEngine | null = null;
getEngine(): FlutterEngine {
if (this.flutterEngine === null) {
this.flutterEngine = new FlutterEngine(context);
}
return this.flutterEngine;
}
五、Vsync 管道故障
| 看到的现象 | 严重度 | 对应日志 |
|---|---|---|
| 完全不渲染,画面静止 | 致命 | vsync_handle_ is nullptr |
| 偶发卡顿 | 中 | AwaitVSync…failed |
| 帧率无法设置,120fps 无效 | 中 | SetExpectedFrameRateRange failed |
| 不渲染加卡死 | 致命 | Failed to dlopen libnative_vsync.so |
排查命令和日志含义:
hdc shell hilog | grep -E "vsync|Vsync|VSync|Poll error"
| 日志 | 通俗解释 | 严重度 |
|---|---|---|
| vsync_handle_ is nullptr | 节拍器没装上,句柄无效 | 致命 |
| AwaitVSync…failed: | 节拍器不响了,请求失败 | 致命 |
| Failed to dlopen libnative_vsync.so | Vsync 库加载失败 | 致命 |
| SetExpectedFrameRateRange failed | 帧率设置失败 | 中 |
| Poll error: | 消息循环出错 | 中 |
三种故障的修法:
(1)完全不渲染:排查Vsync 库是不是没加载
(2)偶发卡顿:排查下是不是系统繁忙导致请求偶尔失败
(3)120fps 无效:排查下是不是设备或系统不支持
本次内容比较多,相关排查方法希望能帮到大家~ 同时大家也持续关注,后续会持续更新
小伙伴们记得点赞+关注
关注 CPF-Flutter 社区
“AI再牛,技术不能丢”
更多推荐





所有评论(0)