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

相关推荐
Jiaberrr1 小时前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy1 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白1 小时前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、1 小时前
Web Worker 简单使用
前端
web_learning_3211 小时前
信息收集常用指令
前端·搜索引擎
tabzzz2 小时前
Webpack 概念速通:从入门到掌握构建工具的精髓
前端·webpack
200不是二百2 小时前
Vuex详解
前端·javascript·vue.js
滔滔不绝tao2 小时前
自动化测试常用函数
前端·css·html5
键盘敲没电2 小时前
【iOS】KVC
ios·objective-c·xcode
吾吾伊伊,野鸭惊啼2 小时前
2024最新!!!iOS高级面试题,全!(二)
ios