Flutter - 解决返回原生页面时dispose方法未被触发的问题 🐞

欢迎关注微信公众号:FSA全栈行动 👋

一、概述

在去年对公司的 Flutter 混编项目做了性能优化,其中最坑的,莫过于从 Flutter 页面返回原生页面时 dispose 方法竟然不走,如图所示

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

上图是进出 7 次视频页面后的内存占用情况。

最终会导致手机发烫,App 变得卡顿,甚至最后闪退(当 App 的内存占用达到图中红色虚线时就会直接被系统杀掉)

随即向 Flutter 官方提出了 issue: github.com/flutter/flu...

二、解决方案

在提出 issue 的第二天,组织成员 dnfield 出提了一个 PR: github.com/flutter/flu... ,不过该 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 弹出而被销毁之后。

AppFlutter 页面返回原生页面后,就会切换到该状态。

三、代码

下面给出完整的示例代码,供大家参考

dart 复制代码
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);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  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.
  @override
  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 来得方便~

如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有 iOS 技术,还有 AndroidFlutterPython 等文章, 可能有你想要了解的技能知识点哦~

相关推荐
哟哟耶耶20 分钟前
React-01React创建第一个项目(npm install -g create-react-app)
前端·javascript·react.js
张拭心23 分钟前
工作九年程序员的三月小结
android·前端
每次的天空25 分钟前
Flutter学习总结之Android渲染对比
android·学习·flutter
try again!33 分钟前
HTML快速上手
前端·css·html
Dontla34 分钟前
前端页面鼠标移动监控(鼠标运动、鼠标监控)鼠标防抖处理、mousemove、debounce()、事件停止触发、超时触发
前端·计算机外设
喝拿铁写前端1 小时前
字段混乱如何影响系统治理?
前端
知远同学1 小时前
关闭Chrome提示更新失败的弹窗
前端·chrome
karshey2 小时前
【IOS webview】源代码映射错误,页面卡住不动
ios
Heidi__2 小时前
Vue 3 的响应式原理
前端·javascript·vue.js
LinXunFeng2 小时前
Flutter - Xcode16 还原编译速度
前端·flutter·xcode