如何快速定位 Flutter APP 内存泄漏
当一个 APP 随着业务发展,无用功能越堆越多,参与开发的人员队伍越发壮大,代码大爆炸式膨胀,尽管有一系列的例如人工review、代码规范、静态lint检查之类流程上的克制手段,也止不住坍缩的小宇宙往深不可测无法捉摸的黑洞演进。其中,最为令人头疼的问题莫过于“内存泄漏”。
什么是内存泄漏?很常见却也很不起眼,你我随手写段代码就能轻松让一个对象被垃圾回收器(GC)视而不见。
如下面这一段:
1 | class WidgetA extends StatefulWidget { |
你能在阅读完这段代码的同时找到那个可能被泄漏的对象吗?
没错,context 可能会短时间泄漏。
但,得益于 Dart 积极高效的 GC 策略,这种泄漏已经算是内存泄漏问题中的可以忽略不计的轻症了。(扩展阅读:Flutter: Don’t Fear the Garbage Collector )
又如这一段代码:
1 | class _WidgetAState extends State<WidgetA> { |
有着丰富的 Flutter 开发经验的你,还是很轻松地就发现了,stream.listen 的返回对象StreamSubscription没有被正确地cancel,导致_WidgetAState 和它的 Element (context) 可能都泄漏了,直到祖节点的 Provider<AuthStateBloc> 被卸载才可能被 GC 回收。
那么回到标题,有没有工具可以程序化的发现内存泄漏呢?
首先要解决的问题是怎么找到内存泄漏点?
Flutter Raw Image Provider
Flutter 中的 Image Widget 内置支持 file、network、memory三种形式的文件。
但这几种都只支持常规的经过压缩后的图片文件或二进制数据,如jpg、png、webp文件等。并没有支持原始的rgba 二进制数据。
这里说的原始二进制数据是指图像的每个像素的色彩值所组成的字节数组。一张图有宽x高个像素点,一个像素点的色彩值用32bit来存储,分为4个通道,每个通道各占用8bit,分别为红、绿、蓝、透明度(RGBA),这个数组就是每个像素点色彩值的集合,dart 中一般用Uint8List。
一般情况下,考虑网络传输效率,会采用算法来压缩这个数据,故而你会看到有各种各样的图像压缩算法和文件格式。
你可能会问什么情况下会有需要直接去加载一张图的原始rgba数据?
这里举个简单例子:分块加载图片。将图片解码后,分割成一个个矩形区域,每个矩形就有一个 raw rgba 数据,将其交给Image渲染,这样做可以降低一定的GPU 内存压力,减少出现GPU OOM 或黑屏的概率。(可以参考我的试验项目:https://github.com/yrom/image_pieces)
要支持 raw rgba ,其实很简单,在 dart:ui包下有个方法decodeImageFromPixels可以直接使用,前提是需要有原始的二进制数据、宽、高。
1 | import 'dart:ui'; |
有了这个 Image(dart:ui)对象就可以交给 RawImage Widget 来加载了。但RawImage太过于底层了,能不能只用 Image Widget呢?因为需要复用 LoadingBuilder这些逻辑。
当然可以。查看一下 Image Widget 的构造函数就知道,我们需要一个 ImageProvider,那么问题进一步简化到如何写一个ImageProvider 支持 raw rgba 数据。
实现一个 ImageProvider,我们需要实现 load这个关键方法。以MemoryImage为例:
1 | class MemoryImage extends ImageProvider<MemoryImage> { |
很显然,我们需要想一个方法构造出raw rgba 数据的 Codec。
瞎拍 - 顾村寻花
植树节刚过,上海也结束了细雨绵绵的天气,稍稍放晴,正好周末,前往顾村公园看看樱花开了没。
到了公园,花是没见着,人却许多。

往深处走,这种长着红叶的树,簇着花,密密麻麻,独占风头。百度识图告诉说是“紫叶李”,希望没认错。


粉色的摩天轮,在上面看樱花视野应该不错,想想而已。

还有这种白如凝脂的花,点缀公园,在一众光溜溜的树干里煞是好看。

瞎拍 - 留沪过年
过年前疫情闹得凶,吓得我把回家票给退了……
谁知上海还是比较给力,没有扩散,却再买不着回去的票,只好一边上班一边摸鱼用盒马囤了许多年货。

就这样,一人一猫留沪过年。

细雨声中,捣鼓好了一个人的年夜饭。
http/2 over http tunnel proxy for Dart
Flutter 项目里用到了 http2 包 (https://pub.dev/packages/http2) 。但官方似乎没有支持 http proxy ,转念一想,似乎也对,proxy 这层不属于 http2 协议该负责实现的事。
研究了一下 http/1.1 tunnel proxy 的协议(RFC 2817)发现其实挺简单的。
这里直接放出示例了,仅供参考哦(需要登录的 proxy 就留给你自己实现了)。
1 | import 'dart:async'; |
用一个简单示例告诉你怎样提升 Flutter App 中的动画性能
观前提醒:本文假设你已经有一定的 Flutter 开发经验,对Flutter 的 Widget,RenderObject 等概念有所了解,并且知道如何开启 DevTools。
现有一个简单的汽泡动画需要实现,如下图:

直接通过 AnimationController 实现
当看到这个效果图的时候,很快啊,啪一下思路就来了。涉及到动画,有状态,用 StatefulWidget ,State 里创建一个 AnimationController,用两个 Container 对应两个圈,外圈的 Container 的宽高监听动画跟着更新就行。
代码如下:
1 | const double size = 56; |
介绍一位新成员
瞎拍 - 夏至厦门
Flutter Logging
1. Install package logging
Add logging to pubspec.yaml file:
1 | dependencies: |
2. Config Logger
Initialize Logger before runApp() in main.dart file:
1 | import 'logging.dart'; |
1 | import 'package:logging/logging.dart'; |
3. Transmit log records from Logger to dart:developer
Logger is a producer, but it will not post any log records if no one is listening.
Print to console:
1 | Logger.root.onRecord.listen((event) { |
Save to file:
1 | File logFile = ... |
It’s strongly recommended to use dart:developer for logging:
1 | import 'dart:developer' as developer; |





