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 等文章, 可能有你想要了解的技能知识点哦~

相关推荐
小小小小宇5 小时前
虚拟列表兼容老DOM操作
前端
悦悦子a啊5 小时前
Python之--基本知识
开发语言·前端·python
安全系统学习6 小时前
系统安全之大模型案例分析
前端·安全·web安全·网络安全·xss
涛哥码咖6 小时前
chrome安装AXURE插件后无效
前端·chrome·axure
OEC小胖胖6 小时前
告别 undefined is not a function:TypeScript 前端开发优势与实践指南
前端·javascript·typescript·web
行云&流水6 小时前
Vue3 Lifecycle Hooks
前端·javascript·vue.js
Sally璐璐7 小时前
零基础学HTML和CSS:网页设计入门
前端·css
老虎06277 小时前
JavaWeb(苍穹外卖)--学习笔记04(前端:HTML,CSS,JavaScript)
前端·javascript·css·笔记·学习·html
灿灿121387 小时前
CSS 文字浮雕效果:巧用 text-shadow 实现 3D 立体文字
前端·css
烛阴7 小时前
Babel 完全上手指南:从零开始解锁现代 JavaScript 开发的超能力!
前端·javascript