50

Flutter 图片缓存 Image.network 源码分析

 6 years ago
source link: https://mp.weixin.qq.com/s/W5iu3VsNuvIygFbKM-giqA?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

来这里找志同道合的小伙伴!

作  者  简  介

郭海生

Android高级工程师, 6年以上开发经验,有丰富的代码重构和架构设计经验,负责京东商城我的京东的开发工作,热衷于学习和研究新技术。

随着手机设备硬件水平的飞速发展,用户对于图片的显示要求也越来越高,稍微处理不好就会容易造成内存溢出等问题。所以我们在使用Image的时候,建立一个图片缓存机制已经是一个常态。Android目前提供了很丰富的图片框架,像ImageLoader、Glide、Fresco等。对于Flutter而言,为了探其缓存机制或者定制自己的缓存框架,特从其Image入手进行突破。

> > > >

Image 的用法

Image是Flutter里提供的显示图片的控件,类似Android里ImageView,不过其用法有点类似Glide等图片框架。

我们先看Image的用法。Flutter对Image控件提供了多种构造函数:

我们只分析Image.network源码,分析理解完这个之后,其他的也是一样的思路。

我们先从Image.network的用法入手:显示一个网络图片很简单,直接通过Image.network携带一个url参数即可。

范例:

> > > >

Image结构UML类图

我们首先看一下Image的UML类图:

Ij2eEvf.jpg!web

可以看到Image的框架结构还是有点儿复杂的,在你只调用一行代码的情况下,其实Flutter为你做了很多工作。

初步梳理下每个类概念:

  1. StatefulWidget就是有状态的Widget,是展示在页面上的元素。

  2. Image继承于StatefulWidget,是来显示和加载图片。

  3. State控制着StatefulWidget状态改变的生命周期,当Widget被创建、Widget配置信息改变或者Widget被销毁等等,State的一系列方法会被调用。

  4. _ImageState继承于State,处理State生命周期变化以及生成Widget。

  5. ImageProvider提供加载图片的入口,不同的图片资源加载方式不一样,只要重写其load方法即可。同样,缓存图片的key值也有其生成。

  6. NetWorkImage负责下载网络图片的,将下载完成的图片转化成ui.Codec对象交给ImageStreamCompleter去处理解析。

  7. ImageStreamCompleter就是逐帧解析图片的。

  8. ImageStream是处理Image Resource的,ImageState通过ImageStream与ImageStreamCompleter建立联系。ImageStream里也存储着图片加载完毕的监听回调。

  9. MultiFrameImageStreamCompleter就是多帧图片解析器。

先把Image的框架结构了解一下,有助于下面我们更加清晰地分析代码。

> > > >

源码分析

我们看下Image.network都做了什么:

我们看到Image是一个StatefulWidget对象,可以直接放到Container或者Column等容器里,其属性解释如下:

  • width:widget的宽度

  • height:widget的高度

  • color:与colorBlendMode配合使用,将此颜色用BlendMode方式混合图片

  • colorBlendMode:混合模式算法

  • fit:与android:scaletype一样,控制图片如何resized/moved来匹对Widget的size

  • alignment:widget对齐方式

  • repeat:如何绘制未被图像覆盖的部分

  • centerSlice:支持9patch,拉伸的中间的区域

  • matchTextDirection:绘制图片的方向:是否从左到右

  • gaplessPlayback:图片变化的时候是否展示老图片或者什么都不展示

  • headers:http请求头

  • image:一个ImageProvide对象,在调用的时候已经实例化,这个类主要承担了从网络加载图片的功能。它是加载图片的最重要的方法,不同的图片加载方式(assert文件加载、网络加载等等)也就是重写ImageProvider加载图片的方法(load())。

Image是一个StatefulWidget对象,所以我们看它的State对象:

我们对_ImageState的两个属性对象解释一下:

  • ImageStream是处理Image Resource的,ImageStream里存储着图片加载完毕的监听回调,ImageStreamCompleter也是其成员,这样ImageStream将图片的解析流程交给了ImageStreamCompleter去处理。

  • ImageInfo包含了Image的数据源信息:width和height以及ui.Image。 将ImageInfo里的ui.Image设置给RawImage就可以展示了。RawImage就是我们真正渲染的对象,是显示ui.Image的一个控件,接下来我们会看到。

我们知道State的生命周期,首先State的initState执行,然后didChangeDependencies会执行,我们看到ImageState里没有重写父类的initState,那我们看其didChangeDependencies():

> > > >

_resolveImage方法解析

我们看到首先调用了resolveImage(),我们看下resolveImage方法:

这个方法是处理图片的入口。widget.image这个就是上面的创建的NetworkImage对象,是个ImageProvider对象,调用它的resolve并且传进去默认的ImageConfiguration。 我们看下resolve方法,发现NetworkImage没有,果不其然,我们在其父类ImageProvider找到了:

我们看到这个方法创建了ImageStream并返回,调用obtainKey返回一个携带NetworkImage的future,以后会作为缓存的key使用,并且调用ImageStream的setCompleter的方法:

这个方法就是给ImageStream设置一个ImageStreamCompleter对象,每一个ImageStream对象只能设置一次,ImageStreamCompleter是为了辅助ImageStream解析和管理Image图片帧的,并且判断是否有初始化监听器,可以做一些初始化回调工作。 我们继续看下PaintingBinding.instance.imageCache.putIfAbsent方法:

这个是Flutter默认提供的内存缓存api的入口方法,这个方法会先通过key获取之前的ImageStreamCompleter对象,这个key就是NetworkImage对象,当然我们也可以重写obtainKey方法自定义key,如果存在则直接返回,如果不存在则执行load方法加载ImageStreamCompleter对象,并将其放到首位(最少最近使用算法)。

也就是说ImageProvider已经实现了内存缓存:默认缓存图片的最大个数是1000,默认缓存图片的最大空间是10MiB。 第一次加载图片肯定是没有缓存的,所以我们看下loader方法,我们看到ImageProvider是空方法,我们去看NetWorkImage,按照我们的预期确实在这里:

这个方法为我们创建了一个MultiFrameImageStreamCompleter对象,根据名字我们也能知道它继承于ImageStreamCompleter。还记得ImageStreamCompleter是做什么的吗,就是辅助ImageStream管理解析Image的。

参数解析:

  • _loadAsync()是请求网络加载图片的方法

  • scale是缩放系数

  • informationCollector是信息收集对象的,提供错误或者其他日志用

MultiFrameImageStreamCompleter是多帧的图片处理加载器,我们知道Flutter的Image支持加载gif,通过MultiFrameImageStreamCompleter可以对gif文件进行解析:

我们看到MultiFrameImageStreamCompleter拿到loadAsync返回的codec数据对象,通过handleCodecReady来处理数据,然后会调用_decodeNextFrameAndSchedule方法:

通过codec.getNextFrame()去拿下一帧,对于静态的图片frameCount是1,直接用ImageInfo组装image,交给emitFrame方法,这个方法里会调用setImage,如下:

setImage方法就是设置当前的ImageInfo并检查监听器列表,通知监听器图片已经加载完毕可以刷新UI了。

对于动图来说就是就是交给SchedulerBinding逐帧的去调用setImage,通知UI刷新,代码就不贴了,有兴趣的可以自行查看下。 至此resolveImage调用流程我们算是讲完了,接下来我们看listenToStream。

> > > >

_listenToStream方法解析

我们继续分析didChangeDependencies方法,这个方法里会判断TickerMode.of(context)的值,这个值默认是true,和AnimationConrol有关,后续可以深入研究。然后调用_listenToStream()。 我们看下这个方法:

这个就是添加图片加载完毕的回调器。还记得吗,当图片加载并解析完毕的时候,MultiFrameImageStreamCompleter的setImage方法会调用这里传过去的回调方法。我们看下这里回调方法里做了什么:

很显然就是拿到上层传过来ImageInfo,调用setState更新UI 我们看下build方法:

就是用imageInfo和widget的信息来封装RawImage,RawImage是RenderObjectWidget对象,是应用程序真正渲染的对象,将咱们的图片显示到界面上。

> > > >

总结

梳理下流程:

  1. 从入口开始,Image是继承于StatefulWidget,它为咱们实现好了State:_ImageState,并且提供了一个已经实例化的NetWorkImage对象,它是继承于ImageProvider对象的。

  2. ImageState创建完之后,ImageState通过调用resolveImage(),resolveImage()又会调用ImageProvider的resolve()方法返回一个ImageStream对象。_ImageState也注册了监听器给ImageStream,当图片下载完毕后会执行回调方法。

  3. 然后在ImageProvider的resolve()方法里不仅创建了ImageStream还设置了ImageStream的setComplete方法去设置ImageStreamCompleter,在这里去判断是否有缓存,没有缓存就调用load方法去创建ImageStreamCompleter并且添加监听器为了执行加载完图片之后的缓存工作。ImageStreamCompleter是为了解析已经加载完成的Image的。

  4. NetWorkImage实现了ImageProvider的load方法,是真正下载图片的地方,创建了MultiFrameImageStreamCompleter对象,并且调用_loadAsync去下载图片。当图片下载完成后就调用UI的回调方法,通知UI刷新。

> > > >

最后

至此,对Image.network的源码分析到这里也结束了,你也可以返回去看下Image的结构图了。怎么样,分析完之后是不是对Flutter加载网络图片的流程已经很了解了,也找到了Flutter缓存的突破口,Flutter自身已经提供了内存缓存( 虽然不太完美 ),接下来你就可以添加你的硬盘缓存或者定制你的图片框架了。

---------------------END ---------------------

下面的内容同样精彩

点击图片即可阅读

Z3YZfqf.jpg!web

B3UBRnv.jpg!web

京东技术   关注技术的公众号

fYNjmqZ.jpg!webFvQRfim.jpg!web

长按,识别二维码,加关注


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK