Flutter - 解决返回原生页面时dispose方法未被触发的问题 🐞
# 一、概述
在去年对公司的 Flutter 混编项目做了性能优化,其中最坑的,莫过于从 Flutter 页面返回原生页面时 dispose 方法竟然不走,如图所示

这就导致一些资源无法得到释放,如 fijkplayer 所使用的视频资源。在原生页面与满屏视频的 Flutter 页面之间反复进出时,内存占用越来越高

上图是进出 7 次视频页面后的内存占用情况。
最终会导致手机发烫,App 变得卡顿,甚至最后闪退(当 App 的内存占用达到图中红色虚线时就会直接被系统杀掉)
随即向 Flutter 官方提出了 issue: https://github.com/flutter/flutter/issues/137924 (opens new window)
# 二、解决方案
在提出 issue 的第二天,组织成员 dnfield 出提了一个 PR: https://github.com/flutter/flutter/pull/137957 (opens new window) ,不过该 PR 只是加了说明,解释为何会出现该问题,以及如何去处理。
根据其说明进行了优化,内存确实得到了有效的及时收回,如图所示

该PR的说明指出:
当通过原生的方式关闭了
Flutter页面时,Flutter端并不知道开发者是否打算提前释放资源,所以Widget树依旧保持着挂载的状态,这么做是为了再一次展示Flutter页面时可以尽可能快的进行渲染。【想必这也是上图中内存占用并没有得到彻底回落的原因!】如果想要尽快的释放资源,可以建立
platform channel,当返回原生页时告知Flutter端去再次调用runApp方法,并传入SizedBox.shrink,这样就可以释放掉活跃的Widget树。
通过 platform channel 的方式需要与原生端打交道,有没有纯 Flutter 的方式呢?
有的,那就是通过监听 Flutter App 的状态,当状态为 AppLifecycleState.detached 时,就可以去执行 runApp 方法了,达到一样释放资源的效果。
关于 AppLifecycleState.detached 的说明:
此时
Flutter App仍被Flutter引擎所持有,但已与任何的视图分离。也就是说引擎正在以没有视图的状态运行着。该状态既可能是在
Flutter引擎初始化时附加视图的过程中,也可能是在视图因Navigator弹出而被销毁之后。
当 App 从 Flutter 页面返回原生页面后,就会切换到该状态。
# 三、代码
下面给出完整的示例代码,供大家参考
main(List<String> args) async {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
final bool isDetached;
const MyApp({
Key? key,
this.isDetached = false,
}) : super(key: key);
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.detached:
// 相关处理参考
// https://github.com/flutter/flutter/pull/137957
runApp(const MyApp(isDetached: true));
// 清除内存中的图片缓存
// https://github.com/Baseflow/flutter_cached_network_image/issues/429
final imageCache = PaintingBinding.instance.imageCache;
imageCache.clear();
imageCache.clearLiveImages();
break;
default:
}
}
// This widget is the root of your application.
Widget build(BuildContext context) {
if (widget.isDetached) {
// 引擎被detach, 移除所有的widget, 以此来及时释放资源
Widget resultWidget = const SizedBox.shrink();
// Fix report no OKToast widget found.
// resultWidget = OKToast(child: resultWidget);
return resultWidget;
}
return MaterialApp(
...
);
}
}
细心的朋友肯定注意到了 OKToast 的相关代码,当时在 Sentry 上看到了错误: No OKToast widget found,这是因为有些网络请求在返回原生页面后才请求成功,进而触发吐丝,而此时已经执行了 runApp 去展示空白页面,考虑到即使取消了所有请求,有些业务会去对 cancel 的情况去吐丝提醒,所以还是套一个 OKToast 来得方便~

- 01
- Flutter 拖拉对比组件,换装图片前后对比必备11-09
- 02
- Flutter 多仓库本地 Monorepo 方案与体验优化10-25
- 03
- Flutter webview 崩溃率上升怎么办?我的分析与解决方案10-19