前语

跟着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
)

深入理解 Flutter 图片加载原理 | 京东云技术团队

Image 控件的运用办法这儿就不在展开了,控件的参数及API详情请参阅:api.flutter.dev/flutter/wid…

图片加载流程

Flutter 的图片加载原理与原生客户端中的图片框架加载原理类似,具体可点击下方大图检查,加载步骤如下:

1、 区别数据来历生成缓存列表中数据映射的仅有key;

2、 经过key读取缓存列表中的图片数据;

3、 缓存存在,回来已存在的图片数据;

4、 缓存不存在,按来历加载图片数据,解码后同步到缓存中并回来;

5、 设置回调监听图片数据加载状况,数据加载完结后重新烘托控件显现图片;

深入理解 Flutter 图片加载原理 | 京东云技术团队

咱们或许留意到了上面流程图中的文件缓存部分是灰色的,目前官方还不支持此功用,下面咱们会经过源码逐渐剖析加载流程及如何经过修改源码补全文件缓存功用。

源码剖析

下面将经过流程图结合UML类图剖析图片加载流程:

深入理解 Flutter 图片加载原理 | 京东云技术团队

这个UML类图看起来稍微有点儿杂乱,但仔细看会发现已将图片数据加载流程分红几大模块,下面将依照模块进行逐渐剖析,下面将以网络图片加载办法为例解说中心类和中心办法功用。

中心类及办法介绍

发动缓存相关类

PaintingBinding:图片缓存类和着色器预加载,该类是依据框架的应用程序发动时绑定到Flutter引擎的胶水类,在发动入口main.dart的runApp办法中创立WidgetsFlutterBinding类时被初始化的,经过覆盖父类的initInstances()办法初始化内部的着色器预加载(Skia第一次在GPU上制作需求编译相应的着色器,这个过程大概20ms~200ms)及图片缓存等,图片缓存以单例的办法(PaintingBinding.instance.imageCache)对外供给办法运用,也便是说这个图片缓存在APP中是全局的,并在这个类中还供给了图画解码(instantiateImageCodec)、缓存清除(evict)等功用。

深入理解 Flutter 图片加载原理 | 京东云技术团队

深入理解 Flutter 图片加载原理 | 京东云技术团队

ImageCache: 图片缓存类,默许供给缓存最大个数约束1000个目标和最大容量约束100MB,因为图片加载过程是一个异步操作,所以缓存的图片分为三种状况:已运用、已加载、未运用,别离对应三个图片缓存列表,当图片列表超限时会将图片缓存列表中最近最少运用图片进行删去,缓存列表别离是:活泼中图片缓存列表(_cache)、已加载图片缓存列表(_pendingImages)、未活泼图片缓存列表(_liveImages),并对外供给以下办法:获取缓存(putIfAbsent)、清空缓存(clear、clearLiveImages)、驱赶单个图片(evict)、最大缓存个数约束(maximumSize)、最大缓存巨细约束(maximumSizeBytes)等办法。

深入理解 Flutter 图片加载原理 | 京东云技术团队

从源码中咱们能够看到缓存列表是Map类型,Flutter中的Map创立的目标是LinkedHashMap是有序的,按键值刺进次序迭代,Flutter运用LinkedHashMap存储图片数据并完结类似LRU算法的缓存,当缓存列表中的图片被运用后会将图片数据重新刺进到缓存列表的末尾,这样最近最少运用的图片一直会被放在列表的头部。

深入理解 Flutter 图片加载原理 | 京东云技术团队

当缓存列表添加图片数据后,会经过最大缓存个数和最大缓存巨细两个纬度进行检查缓存列表是否超限,若存在超限情况则经过Map的keys.first办法获取缓存列表头部最近最少运用的图片目标进行删去,直到满意缓存约束。

深入理解 Flutter 图片加载原理 | 京东云技术团队

深入理解 Flutter 图片加载原理 | 京东云技术团队

发动缓存小结:

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)。

深入理解 Flutter 图片加载原理 | 京东云技术团队

ImageStreamCompleter: 是一个抽象类,用于办理加载图画目标(ImageInfo)加载过程的一些接口,Image控件中正是经过它来监听图片加载状况的。

深入理解 Flutter 图片加载原理 | 京东云技术团队

ImageStream: 图画的加载目标,可监听图画数据加载状况,由ImageStreamCompleter回来一个ImageInfo目标用于图画显现****

深入理解 Flutter 图片加载原理 | 京东云技术团队

NetworkImage: 网络图片加载类,ImageProvider的完结类,经过URL加载网络图画,覆盖load()办法回来ImageStreamCompleter的完结类MultiFrameImageStreamCompleter,构建该类需求一个codec参数类型是Future<ui.Codec>,经过调用_loadAsync()办法下载网络图片数据获得字节流后经过调用PaintingBinding.instance.instantiateImageCodec办法对数据进行解码后获得Future<ui.Codec>目标,obtainKey办法咱们发现回来的是SynchronousFuture(this)目标,正是NetworkImage自己自身,咱们经过该类的==办法能够看到判别两个NetworkImage类是否持平经过runtimeType、url、scale这三个参数来判别,所以图片缓存中的key持平判别取决于图片的url、scale、runtimeType参数。

深入理解 Flutter 图片加载原理 | 京东云技术团队

MultiFrameImageStreamCompleter: 是ImageStreamCompleter的子类是Flutter SDK的预置类,构建该类需求一个codec参数类型是Future<ui.Codec>,Codec是处理图画编解码器的句柄也是Flutter Engine API的包装类,可经过其内部的frameCount变量获取图画帧数,别离处理单帧和多帧(动态图)图画,内部的getNextFrame()办法获取每帧的图画数据并创立Image控件中烘托需求的ImageInfo数据,调用onImage办法将ImageInfo回来给Image控件。

深入理解 Flutter 图片加载原理 | 京东云技术团队

深入理解 Flutter 图片加载原理 | 京东云技术团队

图画数据加载小结:

上面以网络图画加载流程剖析,首先经过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进行数据更新制作。

深入理解 Flutter 图片加载原理 | 京东云技术团队

仔细的同学会发现ImageProvider的实例目标(widget.image)被ScrollAwareImageProvider包装了一下又重新创立了一个provider,在ScrollAwareImageProvider内部主要是重写了其中的resolveStreamForKey()办法,Flutter SDK 1.17版别中对图片解析添加了快速翻滚优化,当判别当时屏幕处在快速翻滚状况时,则将图片解析过程延迟下一帧帧尾进行。

深入理解 Flutter 图片加载原理 | 京东云技术团队

RawImage: RenderObjectWidget的子类,重写createRenderObject办法创立RenderObject子类。

RenderImage: 烘托树中RenderObject的完结类,Flutter的三棵树Widget、Element、RenderObject ,而RenderObject这是担任制作烘托的,RenderImage重写performLayout()办法度量烘托尺度并布局,重写paint()办法获取画布Canvas,Canvas是记载图片操作的接口类,经过参数处理图片镜像、裁剪、平铺等逻辑后调用的drawImageNine()和drawImageRect()办法将图片组成到画布上终究调用Skia引擎API进行制作。

深入理解 Flutter 图片加载原理 | 京东云技术团队

深入理解 Flutter 图片加载原理 | 京东云技术团队

图片烘托小结:

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处理等功用。

自界说占位图、过错图效果

深入理解 Flutter 图片加载原理 | 京东云技术团队

Image控件中的frameBuilder和errorBuilder参数别离为咱们供给了占位图和过错图的自界说办法,也可运用FadeInImage控件供给的占位图(placeholder)、过错图imageErrorBuilder等参数,FadeInImage内部完结也是Image控件,感兴趣的同学能够检查其源码完结。

大图下载发展自界说显现

深入理解 Flutter 图片加载原理 | 京东云技术团队

显现效果:flutter.github.io/assets-for-…

图片可拉伸区域设置(.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、storage.360buyimg.com/pub-image/I…

2、book.flutterchina.club/chapter14/i…

3、api.flutter-io.cn/flutter/pai…

作者:京东零售徐宏伟

来历:京东云开发者社区