Flutter App 内存测试

Flutter 应用内存测试数据。

内存测试一 - 简单 UI

分别使用 Android 和 Flutter 实现最简单的界面

  • 显示一个文本
  • 显示一个图片

使用 TextView 显示 “Hello World!” 的 Android Demo,布局如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

使用 Text Widget 显示 “Hello World!” 的 Flutter Demo,代码如下:

1
Center(child: Text('Hello World!')),

显示图片的 Demo 非常类似,这里略过。

测试数据显示:Flutter 应用比原生应用占用更多内存,多出来的部分主要包括 Other、Code、Native、Graphics。(注意:Android Studio 中无法正常显示 Other 部分变化,adb shell dumpsys meminfo 及 Flutter DevTools 中可正常观察到)

以下是具体测试数据。

数据一

机型及配置:华为 Nova 2,Android 8.0,分辨率480dp(3x)。

Android Text Demo


Android Image Demo


Flutter Text Demo


Flutter Image Demo


数据二

机型及配置:小米 8,分辨率440dp(2.8x)

Android Text Demo


Android Image Demo


Flutter Text Demo


Flutter Image Demo


内存测试二 - 分配大对象

持续在 Dart 代码中分配对象。观察内存变化。

测试数据显示:从 Android 角度看,Dart Heap 中分配的对象归类为 “Private Other” 内存

1
2
3
4
5
6
List<Uint8List> _memList = List();

void _addToMemList() {
// 点击一次, 分配 10MB 内存
_memList.add(_createData(10 * 1024 * 1024));
}

-w1428

随着分配的 Dart 对象,Dart 内存和 Android 内存都在增加

  • 左图是 Dart 内存
    • Dart Heap Capacity (绿线区域)一直在增长
    • Dart Heap Used (浅蓝色区域)一直在增长
    • Dart External (蓝色区域)几乎不变
  • 右图是 Android 内存
    • Total (绿线)一直在增长
    • Other (紫线)一直在增长
    • 其他几乎不变

内存测试三 - 显示图片

持续在 Dart 代码中加载并显示本地图片(1024x1024)。观察内存变化。

测试数据显示:Flutter 应用中图片在 Graphics 内存中分配

-w1666

随着加载和显示的图片增多,Dart 内存和 Android 内存都在增加

  • 左图是 Dart 内存
    • Dart Heap Capacity (绿线区域)一直在增长
    • Dart Heap Used (浅蓝色区域)几乎不变
    • Dart External (蓝色区域)一直在增长
  • 右图是 Android 内存
    • Total (绿线)一直在增长
    • Graphics (橙色区域)波浪形增长
    • 其他几乎不变

内存测试四 - 预加载图片

持续在 Dart 代码中预加载但不显示本地图片(1024x1024)。观察内存变化。

测试数据显示:Flutter 应用中图片在 Graphics 内存中分配

-w1294

随着预加载的图片增多,Dart 内存和 Android 内存都在增加

  • 左图是 Dart 内存
    • Dart Heap Capacity (绿线区域)一直在增长
    • Dart Heap Used (浅蓝色区域)几乎不变
    • Dart External (蓝色区域)一直在增长
  • 右图是 Android 内存
    • Total (绿线)一直在增长
    • Graphics (橙色区域)波浪形增长
    • 其他几乎不变

内存测试五 - 启动多个 Flutter 引擎

持续启动 Flutter 引擎,每次增加一个。观察内存变化。

测试数据显示:每开启一个 Flutter 引擎,Native 内存大约有5-6MB增长,Private Other 有10MB左右增长

-w1302

随着 Flutter 引擎数量增多,Dart 内存和 Android 内存都在增加

  • 左图是 Dart 内存
    • Dart Heap Used (浅蓝色区域)几乎不变
    • Dart External (蓝色区域)一直在增长
  • 右图是 Android 内存
    • Total (绿线)一直在增长
    • Other (紫色区域)一直在快速增长
    • Native (蓝色区域)一直在较快增长
    • Graphics (橙色区域)无增长

在 Android Studio 中测试结果如下:

随着 Flutter 引擎数量增多,

  • Private Other 一直在增长 (注:注意:Android Studio 中无法正常显示 Other 部分变化,adb shell dumpsys meminfo 及 Flutter DevTools 中可正常观察到)
  • Native 内存(蓝色区域)一直在增长

测试结论

  • Flutter 应用比原生应用占用更多内存,多出来的部分主要包括 Other、Code、Native、Graphics 内存分类 meminfo
  • 从 Android 角度看,Dart Heap 属性 “Private Other” 内存。Dart 中创建对象会导致 Private Other 内存增长
  • Flutter 应用中图片在 Graphics 内存中分配 闲鱼技术 Android Flutter实践内存初探
  • 每开启一个 Flutter 引擎,Native 内存大约有5-6MB增长,Private Other 有10MB左右增长

为什么 Flutter 应用会比原生应用占用更多内存?官网给出的一些数据也许能解释部分原因。以下翻译自 Load sequence, performance, and memory - Flutter

展示 Flutter UI 会有一定不可忽视的延迟。可以提前启动 Flutter 引擎来减少这个延迟。

集成 Flutter 到已有应用时要评估预加载 Flutter 引擎(即,加载 Flutter 库、启动 Dart VM、运行 isolate 入口程序)带来的内存开销及延迟。

在 2015 年的一款低端手机上,Flutter v1.10.3 release-AOT 模式下预热 FlutterEngine 开销如下:

  • Android 42 MB 和 1530 ms。其中有 330 ms 会阻塞主线程
  • iOS 22 MB 和 860 ms。其中有 260 ms 会阻塞主线程

内存方面,一个测试样本数据如下(根据使用场景会有所变化)

  • ~4 MB OS’s memory usage for creating pthreads.
  • ~10 MB GPU driver memory.
  • ~1 MB for Dart runtime-managed memory.
  • ~5 MB for Dart-loaded font maps.

延迟方面,一个测试样本数据如下(根据使用场景会有所变化)

  • ~20 ms to collect the Flutter assets from the application package.
  • ~15 ms to dlopen the Flutter engine library.
  • ~200 ms to create the Dart VM and load the AOT snapshot.
  • ~200 ms to load Flutter-dependent fonts and assets.
  • ~400 ms to run the entrypoint, create the first widget tree, and compile the needed GPU shader programs.

预热 Flutter 引擎的时机应该足够晚,以减小内存占用;同时还要足够早,以避免 Flutter 引擎启动时间跟首帧延迟叠加在一起(否则会感觉启动慢,体验非常糟糕)

具体预热时机要根据应用的实际结构来决定。通常经验是在将显示 Flutter 页面的前一个页面中预热

假设引擎已经预热,关联 UI 时的首帧开销如下:

  • Android 320 ms 以及额外的 12 MB (跟屏幕的物理像素尺寸非常相关,physical pixel size)
  • iOS 200 ms 以及额外的 16 MB (跟屏幕的物理像素尺寸非常相关,physical pixel size)

内存方面,开销主要来自用于渲染的 graphical memory buffer (跟屏幕尺寸相关)

延迟方面,开销主要是等原生系统回调(提供 surface)以及编译 shader 程序(原文: compiling the remaining shader programs that are not pre-emptively predictable)的时间。这里的延迟仅在第一帧

Flutter UI 释放后,相关内存也会释放。这里的内存释放跟 FlutterEngine 中的 Flutter State 无关,除非 FlutterEngine 也释放了

优化方向

Flutter 应用内存可优化的点分别是:

  • Private Others,Dart 代码分配的对象属于这一类。优化点是减少 Dart 代码中不必要的对象分配,尤其是大对象
  • Code - 用于处理代码和资源,如 dex 字节码,so 库和字体。优化点是减少 libflutter.solibapp.so 大小
  • Graphics - 图形缓冲区队列向屏幕显示像素(包括 GL 表面、GL 纹理等等)所使用的内存。比较好入手的一个优化是减少 Dart 代码中图片占用的内存。另外可以考虑优化 Flutter UI 这一块(同样使用 skia,为什么 Android 原生 UI 占用的内存比 Flutter UI 少?)
  • Native - C 或 C++ 代码分配的对象的内存。优化点是减少 Flutter 引擎占用的内存,比如单引擎比多引擎使用更少的内存

-w1261