Android Flutter 内存机制初探

简介:

Dart RunTime简介

Flutter Framework使用Dart语言开发,所以App进程中需要一个Dart运行环境(VM),和Android Art一样,Flutter也对Dart源码做了AOT编译,直接将Dart源码编译成了本地字节码,没有了解释执行的过程,提升执行性能。这里重点关注Dart VM内存分配(Allocate)和回收(GC)相关的部分。

和Java显著不同的是Dart的"线程"(Isolate)是不共享内存的,各自的堆(Heap)和栈(Stack)都是隔离的,并且是各自独立GC的,彼此之间通过消息通道来通信。Dart天然不存在数据竞争和变量状态同步的问题,整个Flutter Framework Widget的渲染过程都运行在一个isolate中。

3d86fa921017be55c92ada8ab14a0e87902fa8db

Dart VM将内存管理分为新生代(New Generation)和老年代(Old Generation)。

d47e62d2b349aca45e42305ed6714efbe5ed61d9新生代(New Generation): 通常初次分配的对象都位于新生代中,该区域主要是存放内存较小并且生命周期较短的对象,比如局部变量。新生代会频繁执行内存回收(GC),回收采用“复制-清除”算法,将内存分为两块(图中的from 和 to),运行时每次只使用其中的一块(图中的from),另一块备用(图中的to)。当发生GC时,将当前使用的内存块中存活的对象拷贝到备用内存块中,然后清除当前使用内存块,最后,交换两块内存的角色。

75591eab41c93d5cc9db1c57006dd16dc90a3188

d47e62d2b349aca45e42305ed6714efbe5ed61d9老年代(Old Generation): 在新生代的GC中“幸存”下来的对象,它们会被转移到老年代中。老年代存放生命力周期较长,内存较大的对象。老年代通常比新生代要大很多。老年代的GC回收采用“标记-清除”算法,分成标记和清除两个阶段。在标记阶段会触发停顿(stop the world),多线程并发的完成对垃圾对象的标记,降低标记阶段耗时。在清理阶段,由GC线程负责清理回收对象,和应用线程同时执行,不影响应用运行。

784ba289976cd874e3fb961a0b6942d8665824a9

可以看到,Dart VM借鉴了很多JVM的思路,Dart中产生内存泄露的方式也和Java类似,Java中很多排查内存泄露的思路和防止内存泄露的编程方法应该也可以借鉴过来。

Image内存初探

对图片的合理使用和优化是UI编程的重要部分,Flutter提供了Image Widget,我们可以方便地使用:

1d33e352866d483b9a49e44807d60a91cf903633

我们知道Android将内存分为Java虚拟机内存和Native内存,各大厂商都对Java虚拟机内存有一个上限限制,到达上限就会触发OOM异常,而对Native内存的使用没有太严格的限制,现在的手机内存都很大,一般有较大的Native内存富余。那么Android中ImageView使用的是Java虚拟机内存还是Native内存呢?

我们可以来做一个测试:在一个界面上,每点击一次,就在上面堆加一张图片。为了防止后面的图片完全覆盖前面的图片而出现优化的情况,每次都缩小几个像素,这样就不会出现完全覆盖。

9d0a1b7ca088bbfc2cb6a767d2a2326c7509a2ea

打开Android Profiler,一张一张添加图片,观察内存数据。分别测试了Android的6.0,7.0和8.0系统,结果如下:

1305adc185d4ce10bc7bf060e93df966e00195be

在测试中,随着图片一张张增加,Android 6.0 和 7.0都是Java部分的内存在增长,而Android 8.0则是Native部分的内存在增长。由此有结论,Android原生的ImageView在6.0和7.0版本中使用的Java虚拟机内存,而在Android 8.0中则使用的Native内存。

而Flutter Image Widget使用的是哪部分内存呢?我们用Flutter界面来做相同的测试。Flutter Engine的Debug版本和Release版本存在很大的性能差异,所以我们测试最好使用Release版本,但是,Release版本的Apk又不能使用Android profiler来观察内存,所以我们需要在Debug版本的Apk中打包一个Release版本的Flutter Engine, 可以修改flutter tool中的flutter.gradle来实现:

45990da8b69de380f5f36a3af8e8a47328bb326b

相同地,我们向Flutter界面中添加图片并用Android Profiler来观察内存,测试使用的dart代码:

b91d4ed124df6773ba1b2fff09102451380e038a

得到的结果是:

7912f8215055b51e4601b50d9247821e4ca98f86

可以看到,Flutter Image使用的内存既不属于Java虚拟机内存也不属于Native内存,而是Graphics内存(在Meizu pro5设备上也不属于Graphics,事实上Meizu pro5设备不能归类Flutter Image所使用的内存),官方对Graphics内存的解释是:

924caa241eb87a81bac0080acabca0d6aca1760a

那么至少Flutter Image所使用的内存不会是Java虚拟机内存,这对不少Android设备都是一个好消息,这意味着使用Flutter Image没有OOM的风险,能够较好的利用Native内存。

使用Image的时候,建立一个内存缓存池是个好习惯,Flutter Framework提供了一个ImageCache来缓存加载的图片,但它不同于Android Lru Cache,不能精确的使用内存大小来设定缓存池容量,而是只能粗略的指定最大缓存图片张数。

FlutterView内存初探

Flutter设计之初是想统一Android和IOS的界面编程,所以理想的基于Flutter的apk只需要提供一个MainActivity做入口即可,后面所有的页面跳转都在FlutterView中管理。但是,如果是一个已有规模的app接入Flutter开发,我们不可能将已有的Activity页面都用Flutter重新实现一遍,这时候就需要考虑本地页面和Flutter页面之间的跳转交互了。iOS可以方便的管理页面栈,但是Android就很复杂(Android有任务栈机制,低内存Activity回收机制等),所以通常我们还是使用Activity作为页面容器来展示flutter页面。这时有两种选择,可以每次启动一个Activity就启动一个新的FlutterView,也可以启动Activity的时候复用已有的FlutterView。

6092e0e393f73db8eb13093e3cd57d4313ff4275

Flutter Framework中FlutterView是绑定Activity使用的,要复用FlutterView就必须能够把FlutterView单独拎出来使用。所幸现在FlutterView和Activity耦合程度并不很深,最关键的地方是FlutterNativeView必须attach一个Activity:

c8a071c943a1718a48bb77d937a8e07b3d237689

初始化FlutterView时必须传入一个Activity,当其他Activity复用FlutterView时再调用该Attach方法即可。这里有个问题,就是FlutterView中必须保存一个Activity引用,这个一个内存泄露隐患,我们可以在FluterView detach时候将MainActivity传入,因为通常整个App交互过程中MainActivity都是一直存在的,可以避免其他Activity泄露。

为了更好的权衡两种方法的利弊,我们先用空页面来测试一下当页面增加时内存的变化:

e5480779284824474bfcebc589617ec1561f3100

不复用FlutterView时平均打开一个页面(空页面),Java内存增长0.02M,Native内存增长0.73M。复用FlutterView时平均打开一个页面(空页面),Java内存增长0.019M,Native内存增长0.65M。可见复用FlutterView在内存使用上是有优势的,但主要复用的还是Native部分的内存。复用FlutterView必然带来额外的一些复杂逻辑,有时候为了逻辑简单,后期维护上的方便,牺牲一些相对不太珍贵的Native内存也是值得的。

复用单个FlutterView有时会有些“意外”,比如当Activity切换时,就不得不将当前FlutterView detach掉给后面新建的Activity使用,当前界面就会空白闪动,有个想法是可以将当前界面截屏下来遮挡住后面的界面变化,这种方式有时会带来额外的适配问题。

FlutterView复用与否不是绝对的,有时候可以使用一些综合性折中方案,比如,我们可以建立一个FlutterViewProvider,里面维护N个可复用的FlutterView,如图:

2e082aa8d946a8e6f8ba0f5024caed54c13062f0

这样的好处是,可以存在一定程度上的复用,又可以避免只有一个FlutterView出现的一些尴尬问题。

FlutterView的首帧渲染耗时较高,在Debug版本有明显感受,大概会黑屏2秒,release版本会好很多。但我们观察Cpu曲线,发现还是一个较为耗时的过程。有一种体验优化的思路是,我们可以预先让将要使用的FlutterView加载好首帧,这样,在真正使用的时候就很快了,可以先建立一个只有1个像素的窗口,在这个窗口里面完成FlutterView首帧渲染,代码如下:

f53d449ae7c41fb15784d4eb41fb2bbd0c28f8a5

以上就是闲鱼团队在Flutter的应用过程中的一些实践,希望有更多的新技术尝试和技术挑战的同学,请在下面留言告诉我们。


原文发布时间为:2018-05-22

本文作者:匠修

本文来自云栖社区合作伙伴“阿里技术”,了解相关信息可以关注“阿里技术”。

相关文章
|
4月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
528 4
|
1月前
|
前端开发 Java 编译器
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
90 36
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
|
1月前
|
缓存 Java 测试技术
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
213 3
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
|
1月前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
191 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
1月前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
63 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
2月前
|
Dart 前端开发 Android开发
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
56 1
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
3月前
|
监控 Java Android开发
深入探索Android系统的内存管理机制
本文旨在全面解析Android系统的内存管理机制,包括其工作原理、常见问题及其解决方案。通过对Android内存模型的深入分析,本文将帮助开发者更好地理解内存分配、回收以及优化策略,从而提高应用性能和用户体验。
|
4月前
|
前端开发 数据处理 Android开发
Flutter前端开发中的调试技巧与工具使用方法,涵盖调试的重要性、基本技巧如打印日志与断点调试、常用调试工具如Android Studio/VS Code调试器和Flutter Inspector的介绍
本文深入探讨了Flutter前端开发中的调试技巧与工具使用方法,涵盖调试的重要性、基本技巧如打印日志与断点调试、常用调试工具如Android Studio/VS Code调试器和Flutter Inspector的介绍,以及具体操作步骤、常见问题解决、高级调试技巧、团队协作中的调试应用和未来发展趋势,旨在帮助开发者提高调试效率,提升应用质量。
114 8
|
4月前
|
Linux Android开发 iOS开发
深入探索Android与iOS的多任务处理机制
在移动操作系统领域,Android和iOS各有千秋,尤其在多任务处理上展现出不同的设计理念和技术实现。本文将深入剖析两大平台在后台管理、资源分配及用户体验方面的策略差异,揭示它们如何平衡性能与电池寿命,为用户带来流畅而高效的操作体验。通过对比分析,我们不仅能够更好地理解各自系统的工作机制,还能为开发者优化应用提供参考。
|
4月前
|
缓存 前端开发 数据安全/隐私保护
Flutter 框架提供了丰富的机制和方法来优化键盘处理和输入框体验
在移动应用开发中,Flutter 框架提供了丰富的机制和方法来优化键盘处理和输入框体验。本文深入探讨了键盘的显示与隐藏、输入框的焦点管理、键盘类型的适配、输入框高度自适应、键盘遮挡问题处理及性能优化等关键技术,结合实例分析,旨在帮助开发者提升应用的用户体验。
166 6

热门文章

最新文章