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

相关推荐
贵州数擎科技有限公司7 分钟前
霓虹沙尘暴的 Three.js 实现
前端·webgl
一只叁木Meow8 分钟前
电商 SKU 选择器:用算法实现优雅的用户交互
前端·javascript·算法
笔优站长10 分钟前
vue-sign-canvas v2 重构复盘:从 Vue 2 签名板到 Vue 3 + TypeScript 组件库
前端·vue.js
Aolith14 分钟前
事件驱动设计:我如何为校园论坛实现消息通知功能
前端·vue.js
yingyima15 分钟前
GitHub Actions 定时任务 schedule 踩坑实录:核心语法与实战技巧
前端
代码煮茶16 分钟前
CSS 单位完全指南:px、em、rem、vw、vh、clamp 详解
前端·css
KaMeidebaby20 分钟前
卡梅德生物技术快报|PROTAC 药物降解蛋白原理及数据库平台开发全流程
前端·数据库·其他·百度·新浪微博
玄米乌龙茶1231 小时前
LLM成长笔记(七): AI 应用框架与编排
前端·人工智能·笔记
芯芯点灯2 小时前
gd32f303烧录提示Flash Timeout. Reset the Target and try it again.;
开发语言·前端·javascript
前端若水2 小时前
自定义消息组件:图片、文件附件与图表
前端·人工智能·react.js·typescript