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-24
- 02
- Flutter - 危!3.24版本苹果审核被拒!11-13
- 03
- Flutter - 轻松搞定炫酷视差(Parallax)效果09-21