前言
随着Flutter稳固版本逐渐迭代更新,京东APP外部的Flutter业务也日益增多,Flutter开发为咱们提供了高效的开发环境、优良的跨平台适配、丰盛的性能组件及动画、靠近原生的交互体验,但随之也带来了一些OOM问题,通过线上监控信息和Observatory工具联合剖析咱们发现问题的起因是因为Flutter页面中加载的大量图片导致的内存溢出,这也是在原生开发中常见的问题之一,Flutter官网为咱们提供的Image widget实现图片加载及显示,只有理解Flutter中图片的加载原理及图片内存治理形式能力真正发现问题的实质,本文将重点介绍Flutter中图片的加载原理,应用过程中有哪些须要留神的中央及优化思路和伎俩,心愿能给大家带来一些启发和帮忙。
根本应用
上面是 Image 的根本应用办法,image参数是 Image 控件中的必选参数,也是数据源类型能够是Asset、网络、文件、内存,上面将以咱们罕用的网络图片加载形式为例子解说原理,根本应用如下:
Image( image: NetworkImage( "https://avatars2.githubusercontent.com/u/20411648?s=460&v=4"), width: 100.0, heitht: 100.0)
Image 控件的应用办法这里就不在开展了,控件的参数及API详情请参阅:https://api.flutter.dev/flutter/widgets/Image-class.html
图片加载流程
Flutter 的图片加载原理与原生客户端中的图片框架加载原理类似,具体可点击下方大图查看,加载步骤如下:
1、 辨别数据起源生成缓存列表中数据映射的惟一key;
2、 通过key读取缓存列表中的图片数据;
3、 缓存存在,返回已存在的图片数据;
4、 缓存不存在,按起源加载图片数据,解码后同步到缓存中并返回;
5、 设置回调监听图片数据加载状态,数据加载实现后从新渲染控件显示图片;
大家可能留神到了下面流程图中的文件缓存局部是灰色的,目前官网还不反对此性能,上面咱们会通过源码逐渐剖析加载流程及如何通过批改源码补全文件缓存性能。
源码剖析
上面将通过流程图联合UML类图剖析图片加载流程:
这个UML类图看起来略微有点儿简单,但认真看会发现已将图片数据加载流程分成几大模块,上面将依照模块进行逐渐剖析,上面将以网络图片加载形式为例解说外围类和外围办法性能。
外围类及办法介绍
启动缓存相干类
PaintingBinding:图片缓存类和着色器预加载,该类是基于框架的应用程序启动时绑定到Flutter引擎的胶水类,在启动入口main.dart的runApp办法中创立WidgetsFlutterBinding类时被初始化的,通过笼罩父类的initInstances()办法初始化外部的着色器预加载(Skia第一次在GPU上绘制须要编译相应的着色器,这个过程大略20ms~200ms)及图片缓存等,图片缓存以单例的形式(PaintingBinding.instance.imageCache)对外提供办法应用,也就是说这个图片缓存在APP中是全局的,并在这个类中还提供了图像解码(instantiateImageCodec)、缓存革除(evict)等性能。
ImageCache:图片缓存类,默认提供缓存最大个数限度1000个对象和最大容量限度100MB,因为图片加载过程是一个异步操作,所以缓存的图片分为三种状态:已应用、已加载、未应用,别离对应三个图片缓存列表,当图片列表超限时会将图片缓存列表中最近起码应用图片进行删除,缓存列表别离是:沉闷中图片缓存列表(\_cache)、已加载图片缓存列表(\_pendingImages)、未沉闷图片缓存列表(_liveImages),并对外提供以下办法:获取缓存(putIfAbsent)、清空缓存(clear、clearLiveImages)、驱赶单个图片(evict)、最大缓存个数限度(maximumSize)、最大缓存大小限度(maximumSizeBytes)等办法。
从源码中咱们能够看到缓存列表是Map类型,Flutter中的Map创立的对象是LinkedHashMap是有序的,按键值插入程序迭代,Flutter应用LinkedHashMap存储图片数据并实现相似LRU算法的缓存,当缓存列表中的图片被应用后会将图片数据从新插入到缓存列表的开端,这样最近起码应用的图片始终会被放在列表的头部。
当缓存列表减少图片数据后,会通过最大缓存个数和最大缓存大小两个纬度进行查看缓存列表是否超限,若存在超限状况则通过Map的keys.first办法获取缓存列表头部最近起码应用的图片对象进行删除,直到满足缓存限度。
启动缓存小结:
Flutter启动后在PaintingBinding中创立ImageCache缓存,图片缓存是全局的并以单例形式对外提供应用办法,缓存默认最大个数限度1000个对象、最大容量限度100MB,缓存中的Map列表通过key/value形式存储图片信息,并通过keys.first办法实现的相似LRU算法治理图片缓存列表,对外提供putIfAbsent()办法获取已缓存图像,若缓存中不存在则通过回调图片加载类中的load()办法加载图片数据,另外图片缓存中还提供clear()和evict()办法用来删除缓存。
图片数据加载相干类
ImageProvider:图片数据提供抽象类,该类定义了图像数据解析办法(resolve)、惟一key生成办法(obtainKey)、数据加载办法(load),obtainKey 和load办法均由子类实现,obtainKey办法生成的对象用于内存缓存的key值应用,load办法将依照不同数据源加载图像数据,罕用的Provider子类有:NetworkImage、AssetImage、FileImage、MemoryImage,咱们能够看到resolve办法返回的是图片加载对象类(ImageStream),load办法返回的是ImageStreamCompleter类用来治理图像加载状态及图像数据(ImageInfo)。
ImageStreamCompleter:是一个抽象类,用于治理加载图像对象(ImageInfo)加载过程的一些接口,Image控件中正是通过它来监听图片加载状态的。
ImageStream:图像的加载对象,可监听图像数据加载状态,由ImageStreamCompleter返回一个ImageInfo对象用于图像显示
NetworkImage:网络图片加载类,ImageProvider的实现类,通过URL加载网络图像,笼罩load()办法返回ImageStreamCompleter的实现类MultiFrameImageStreamCompleter,构建该类须要一个codec参数类型是Future<ui.Codec>,通过调用_loadAsync()办法下载网络图片数据取得字节流后通过调用PaintingBinding.instance.instantiateImageCodec办法对数据进行解码后取得Future<ui.Codec>对象,obtainKey办法咱们发现返回的是SynchronousFuture<NetworkImage>(this)对象,正是NetworkImage 本人自身,咱们通过该类的==办法能够看到判断两个NetworkImage类是否相等通过runtimeType 、url 、scale 这三个参数来判断,所以图片缓存中的key相等判断取决于图片的url、scale、runtimeType参数。
MultiFrameImageStreamCompleter:是ImageStreamCompleter的子类是Flutter SDK的预置类,构建该类须要一个codec参数类型是Future<ui.Codec>,Codec 是解决图像编解码器的句柄也是Flutter Engine API的包装类,可通过其外部的frameCount变量获取图像帧数,别离解决单帧和多帧(动态图)图像,外部的getNextFrame()办法获取每帧的图像数据并创立Image控件中渲染须要的ImageInfo数据,调用onImage办法将ImageInfo返回给Image控件。
图像数据加载小结:
下面以网络图像加载流程剖析,首先通过ImageProvider的resolve()办法创立ImageStream对象,obtainKey()办法创立图像缓存列表中的惟一key(取决于图像url和scale),通过load()办法加载图像数据并返回MultiFrameImageStreamCompleter对象,并将其设置给ImageStream中的setCompleter()办法增加监听图像加载实现状态,图像数据通过Codec 解决帧数别离解决最终创立ImageInfo对象通过ImageStreamListener的onImage办法返回给Image控件。
图片渲染相干类
_ImageState:是Image控件创立的State类,通过调用ImageProvider的resolve()办法解析图片数据,resolve()办法返回的ImageStream对象,通过addListener()减少图片解析状态监听,通过ImageStreamListener的onImage回调中获取图片数据(ImageInfo)加载实现状态,onChunk回调监听数据加载进度,onError监听图片加载谬误状态,最终通过调用setState进行数据更新绘制。
仔细的同学会发现ImageProvider的实例对象(widget.image)被ScrollAwareImageProvider包装了一下又从新创立了一个provider,在ScrollAwareImageProvider外部次要是重写了其中的resolveStreamForKey()办法,Flutter SDK 1.17版本中对图片解析减少了疾速滚动优化,当判断以后屏幕处在疾速滚动状态时,则将图片解析过程提早下一帧帧尾进行。
RawImage:RenderObjectWidget的子类,重写createRenderObject办法创立RenderObject子类。
RenderImage:渲染树中RenderObject的实现类,Flutter的三棵树Widget、Element、RenderObject ,而RenderObject这是负责绘制渲染的,RenderImage重写performLayout()办法度量渲染尺寸并布局,重写paint()办法获取画布Canvas,Canvas是记录图片操作的接口类,通过参数解决图片镜像、裁剪、平铺等逻辑后调用的drawImageNine()和drawImageRect()办法将图片合成到画布上最终调用Skia引擎API进行绘制。
图片渲染小结:
Image控件中通过调用ImageProvider的resolve()办法获取图片数据ImageInfo对象,通过setState办法将数据更新给图片渲染控件(RenderImage),RenderImage中重写paint()办法依据传入参数对图片数据处理后绘制到Canvas画布上并调用Skia引擎API进行绘制。
总结
以上是 Image 图片加载原理及源码剖析,那么咱们在翻阅了Image源码后能做些什么呢?应用过程中有哪些能够优化的局部呢?让咱们持续往下看。
图片缓存池大小限度优化
Flutter自身提供了定制化Cache的能力,所以优化ImageCache的第一步就是要依据机型的理论物理内存去做缓存大小的适配,通过PaintingBinding.instance.imageCache调用的maximumSize和maximumSizeBytes动静设置正当的图片缓存大小限度防止因图片过多导致OOM。
未显示图像内存优化
可联合StatefulWidget控件生命周期中的deactive()、dispose()等办法,在页面控件中的图片未显示在屏幕上或控件已销毁时调用图片缓存中的evict()办法进行资源开释。
图片预缓存解决
Image控件中提供了precacheImage()办法能够将须要显示的图片事后加载到ImageCache的缓存列表中,缓存列表中通过key值辨别雷同图片,在页面关上后间接从内存缓存获取,可疾速显示图片。
图片文件缓存
通过查看网络图片加载类NetworkImage源码能够发现,图片数据下载和解码过程都是通过_loadAsync()办法实现的,所以咱们能够通过革新这个办法中图片文件下载、读取、保留过程去减少图片文件本地存储、获取原生图片库缓存、图片下载DNS解决等性能。
自定义占位图、谬误图成果
Image控件中的frameBuilder和errorBuilder参数别离为咱们提供了占位图和谬误图的自定义形式,也可应用FadeInImage控件提供的占位图(placeholder)、谬误图imageErrorBuilder等参数,FadeInImage外部实现也是Image控件,感兴趣的同学能够查看其源码实现。
大图下载进度自定义显示
显示成果:https://flutter.github.io/assets-for-api-docs/assets/widgets/loading\_progress\_image.mp4
图片可拉伸区域设置(.9图片)
RenderImage的paint办法中咱们发现在调用Canvas API绘制前会判断centerSlice参数别离调用drawImageNine()和drawImageRect()办法,Image正式通过centerSlice参数配置图片的可拉伸区域,参考代码:centerSlice: Rect.fromLTWH(20, 20, 1, 1),L:横向可拉伸区域右边起始点地位,T:纵向可拉伸区域上边起始点地位,W:横向可拉伸区域宽度,H:纵向可拉伸区域宽度。
将来布局
本文介绍了京东APP中Flutter摸索遇到的问题以及图片的加载原理和应用过程中的一些技巧,随着Flutter SDK版本迭代更新,咱们将持续对图片加载框架进行优化,原生开发中的多个优良图片框架曾经经验了大量用户的考验这也始终是咱们渴望在Flutter上复用的能力,所以咱们也在积极探索原生和Flutter中图片内存共享计划,咱们心愿这个加强能力是非侵入式的,咱们也在尝试外接纹理等计划,这块技术细节停顿将在后续文章中持续和大家一起探讨。
参考资料
1、http://storage.360buyimg.com/pub-image/Image-source.jpg
2、https://book.flutterchina.club/chapter14/image\_and\_cache.html
3、https://api.flutter-io.cn/flutter/painting/ImageCache-class.html
作者:京东批发 徐雄伟
起源:京东云开发者社区