- 本文链接:https://rainmonth.github.io/posts/A220317.html
- 分析版本:Fresco 2.5
基本介绍
包结构
- drawee
- fbcore
- fresco
- imagepipeline,Fresco的核心模块
- memory
- nativeimagefilters
- nativeimagetranscoder
应用到的设计模式
从设计模式的角度来看Fresco,主要运用了如下设计模式
- 单例模式,如
ImagePipelineFactory
; - Builder模式;
- 工厂方法模式;
- 代理模式(静态代理)
- Producer/Consumer
- 适配器模式,比如 AbstractProducerToDataSourceAdapter(让Producer对象可以当DataSource来使用)
接口设计
Supplier<T>
,简单的单类型对象提供者,实现该类后,通过该类就可以获取对应的对象;DataSource
DataSubscriber
Producer
ProducerContext
Consumer
基本使用
使用很简单,第一步初始化Fresco:
1 | Fresco.initialize(this); |
加载网络图片
1 | // 加载网络图片:方法1 |
加载Assets目录图片
不建议用Fresco加载本地图片,但它肯定是支持的(假设要加载的图片为assets目录下的a.png
1 | SimpleDraweeView simpleDraweeView = findViewById(R.id.xx); |
加载Drawable图片
1 | SimpleDraweeView simpleDraweeView = findViewById(R.id.xx); |
加载本地文件中图片
1 | SimpleDraweeView simpleDraweeView = findViewById(R.id.xx); |
加载ContentProvider中图片
1 | SimpleDraweeView simpleDraweeView = findViewById(R.id.xx); |
图片的加载流程
- Fresco初始化;
- 获取DataSource
- 绑定Controller与Hierarchy
- 从内存缓存/磁盘缓存/网络获取图片,并最终设置到对应的Drawable层;
1.Fresco初始化
在开始使用前,Fresco需要进行初始化设置,即调用Fresco的initialize(Context)
1 | public static void initialize(Context context, boolean useNativeCode) { ImagePipelineConfig imagePipelineConfig, DraweeConfig draweeConfig, |
注意,以Supplier结尾的类都实现了Supplier接口,表明会提供一个对象,对象就是Supplier前面的内容。
上面的代码主要做了三件事:
- 完成了NativeLoader的初始化;
- 完成了ImagePipeline初始化;
- 完成了Drawee的初始化;
NativeLoader初始化
主要加载一些so库,如gif支持、webp支持,这个暂不做详细分析。
ImagePipeline初始化
ImagePipeline是通过ImagePipelineConfig来配置,通过ImagePipelineFactory的createImagePipeline()
方法来创建的,ImagePipelineFactory中有三个静态成员,分别是:
1 | private static ImagePipelineFactory sInstance = null;// ImagePipelineFactory 静态实例 |
ImagePipelineConfig
ImagePipelineConfig
是ImagePipelineConfigInterface
的具体实现类,ImagePipelineConfig 必须通过Builder模式来构造(构造方法是私有的),先看看接口都定义了那些方法:
1 | public interface ImagePipelineConfigInterface { |
必要的配置选项Fresco都有默认的实现,如果需要定制,构造自己的ImagePipelineConfig传递进来即可。
ImagePipelineFactory
这个类的核心任务是创建ImagePipeline,先看看ImagePipeline是如何构造的,代码如下:
1 | public ImagePipelineFactory(ImagePipelineConfigInterface config) { |
没啥,主要就是给几个变量赋值,包括ImagePipelineConfig
、ThreadHandoffProducerQueue
、CloseableReferenceFactory
,构造函数虽然简单,但是通过构造出来的ImagePipelineFactory
可以获取到的对象就多了去了,包括:
DrawableFactory
CountingMemoryCache
(即mBitmapCountingMemoryCache
),内存缓存InstrumentedMemoryCache
(即mBitmapMemoryCache
),内存缓存,提供缓存命中回调监听CountingMemoryCache
(即mEncodedCountingMemoryCache
),内存缓存InstrumentedMemoryCache
(即mEncodedMemoryCache
),内存缓存,提供缓存命中回调监听BufferedDiskCache
(即mMainBufferedDiskCache
),带缓存的磁盘缓存FileCache
(即mMainFileCache
),主文件缓存,磁盘文件缓存(这里主要是大文件)ImagePipeline
(即sImagePipeline
),构建ImagePipelinePlatformBitmapFactory
(即mPlatformBitmapFactory
),bitmap对象创建工程,主要提供不同平台版本下bitmap的创建方式PlatformDecoder
(即mPlatformDecoder
),解码器,不同的平台、不同的Android系统版本采用不同的 DecoderFileCache
(即mSmallImageFileCache
),小图文件缓存(可见小图的缓存和主文件缓存是分开的,避免共用缓存导致主文件缓存文件大侵占小文件缓存的情况CloseableReferenceFactory
(即mCloseableReferenceFactory
),这个是在构造函数中赋值的
Drawee初始化
Drawee初始化的核心内容在PipelineDraweeControllerBuilderSupplier
这个类上,从命名上看,该类会提供一个用来构建PipelineDraweeController
的PipelineDraweeControllerBuilder
对象,看其构造函数。
PipelineDraweeControllerBuilder
1 | public PipelineDraweeControllerBuilderSupplier(Context context, ImagePipelineFactory imagePipelineFactory, Set<ControllerListener> boundControllerListeners, Set<ControllerListener2> boundControllerListeners2, { DraweeConfig draweeConfig) |
构造函数主要进行了以下两个动作:
- 获取ImagePipeline;
- 获取
PipelineDraweeControllerFactory
并初始化PipelineDraweeControllerFactory相关参数,主要包括:Resources
对象;DeferredReleaser.getInstance()
,延迟释放资源,等主线程处理完消息后再进行回收。mImagePipeline.getBitmapMemoryCache()
,已经解码的Bitmap缓存
至此,图片加载流程中的Fresco初始化环节就完成了。这个初始化过程一般只需要调用一次即可,一般会在Application的onCreate
中进行调用。
2.获取DataSource
什么是DataSource
在初始化完成后,就要看如何根据Url获取到显示的数据了,Fresco中数据由DataSource
接口表示。
1 | public interface DataSource<T> { |
DataSource
有三种状态,采用枚举变量DataSourceStatus
来表示,分别是IN_PROCESS
,SUCCESS
,FAILURE
。基本实现类为AbstractDataSource
,还有以下衍生类:
AbstractProducerToDataSourceAdapter
,Producer到DataSource的适配(适配器模式);CloseableProducerToDataSourceAdapter
,实现了AbstractDataSource
的closeResult()
方法;ProducerToDataSourceAdapter
,仅仅是AbstractProducerToDataSourceAdapter
,无特殊方法实现;FirstAvailableDataSource
,之所以是叫FirstAvailable,主要是因为该类是FirstAvailableDataSourceSupplier
的内部类,而FirstAvailableDataSourceSupplier
内部有一个List<Supplier<DataSource<T>>> mDataSourceSuppliers
的成员变量,可见其可以提供多个DataSource,我们使用的时候只取第一个可用的DataSource即可;IncreasingQualityDataSource
,内部维护一个CloseableProducerToDataSourceAdapter列表,按数据的清晰度从后往前递增,它为列表里的每个DataSourece绑定一个DataSubscriber,该类负责保证 每次获取清晰度更高的数据,获取数据的同时销毁清晰度更低的数据(HOW?)1
2
3
4
5
6if (supplier != null && this.mLowResImageRequest != null) {
List<Supplier<DataSource<IMAGE>>> suppliers = new ArrayList(2);
suppliers.add(supplier); // index为0,正常质量
suppliers.add(this.getDataSourceSupplierForRequest(controller, controllerId, this.mLowResImageRequest));// index为1,low质量
supplier = IncreasingQualityDataSourceSupplier.create(suppliers, false);
}FirstAvailableDataSource
:内部维护一个CloseableProducerToDataSourceAdapter
列表,它会返回列表里最先获取数据的DataSource,它为列表里的每个DataSource绑定一个DataSubscriber,如果 数据加载成功,则将当前成功的DataSource指定为目标DataSource,否则跳转到下一个DataSource继续尝试。SettableDataSource
/SimpleDataSource
:继承自AbstractDataSource
,并将重写settResult()
、setFailure()
、setProgress()
在内部调用父类的相应函数,但是修饰符变成了public(原来是protected)。即使 用SettableDataSource时可以在外部调用这三个函数设置DataSource状态。一般用于在获取DataSource失败时直接产生一个设置为Failure的DataSource。
DataSource是怎么生成的
SimpleDraweeView.setImageURI
基本使用中介绍了,初始化后调用SimpleDraweeView
的setImageUri(String url)
方法就可以实现图片的加载与现实,那么这个是怎么做到的呢?看下面的分析:
1 | public void setImageURI(Uri uri, { Object callerContext) |
setImageURI
有多个重载,最终都会调用到上面这个方法。可以看到这里会用mControllerBuilder
来构造一个DraweeController,然后设置给SimpleDraweeView。这个mControllerBuilder
就是PipelineDraweeControllerBuilderSupplier
.get()方法返回的PipelineDraweeControllerBuilder
(继承自AbstractDraweeControllerBuilder
,而该类实现了SimpleDraweeControllerBuilder
接口),看看setUri(Uri uri)
的最终实现:
1 | public PipelineDraweeControllerBuilder setUri( { Uri uri) |
setUri
完成了图片请求ImageRequest
的构造
PipelineDraweeControllerBuilder.obtainController()
再看看这个DraweeController是如何build的?build()方法最终会走到AbstractDraweeController
的buildController()
中:
1 | protected AbstractDraweeController buildController() { |
obtainController()
方法由AbstractDraweeController
定义,由PipelineDraweeController
实现:
1 | protected PipelineDraweeController obtainController() { |
这个方法里,看到了DataSource相关的内容了,因为Fresco的命名规则很规范,以Supplier结尾的表示实现了Supplier接口,其get方法会提供其前面类名的实例,这里的this.obtainDataSourceSupplier(...)
说明会获取到DataSource实例,看controller.initialize(...)
代码:
1 | public void initialize(Supplier<DataSource<CloseableReference<CloseableImage>>> dataSourceSupplier, |
PipelineDraweeControllerBuilder.obtainDataSourceSupplier
第一个参数由`this.obtainDataSourceSupplier(...)
获取,看代码:
1 | protected Supplier<DataSource<IMAGE>> obtainDataSourceSupplier(final DraweeController controller, |
这里的IMAGE
泛型指的是CloseableReference<CloseableImage>
类型,后面会提及。具体代码分析见注释。上面获取Supplier的一系列getXX方法最终都会走到PipelineDraweeControllerBuilder
的getDataSourceForRequest
中,看看这个getDataSourceForRequest
方法(由PipelineDraweeControllerBuilder实现)
PipelineDraweeControllerBuilder.getDataSourceForRequest
1 | protected DataSource<CloseableReference<CloseableImage>> getDataSourceForRequest(DraweeController controller, String controllerId, ImageRequest imageRequest, Object callerContext, CacheLevel cacheLevel) { |
结果就是调用ImagePipeline
的fetchDecodedImage
,mImagePipeline是通过ImagePipelineFactory来进行赋值的(在PipelineDraweeControllerBuilderSupplier
构造的时候),那么这个fetchDecodedImage
干了什么呢?
ImagePipeline.submitFetchRequest
1 | public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(ImageRequest imageRequest, Object callerContext, RequestLevel lowestPermittedRequestLevelOnSubmit, { RequestListener requestListener, String uiComponentId) |
这个方法先获取
Producer
,然后调用submitFetchRequest
方法来拉取图片请求,生成DataSource
并最终返回,所以DataSource
最终是有ImagePipeline
调用submitFetchRequest方法生成的。
看看这个最终生成DataSource
的方法:
1 | private <T> DataSource<CloseableReference<T>> submitFetchRequest(Producer<CloseableReference<T>> producerSequence, ImageRequest imageRequest, RequestLevel lowestPermittedRequestLevelOnSubmit, Object callerContext, { RequestListener requestListener, String uiComponentId) |
主要完成以下工作:
- 获取
RequestLevel
,即请求的级别,分别有FULL_FETCH(1)
,DISK_CACHE(2)
,ENCODED_MEMORY_CACHE(3)
,BITMAP_MEMORY_CACHE(4),对应的时从网络或本地获取、从磁盘缓存获取、从未解码的内存缓存获取、从已解码的缓存获取。 - 获取
SettableProducerContext
,获取Producer运行的上下文环境,通过ProducerContext可以控制Producer的内部状态。 - 创建DataSource(CloseableProducerToDataSourceAdapter是DataSource的一种)并返回。
DataSource的获取过程就到这了,它创建后就会被设置到PipelineDraweeController中。下面看看Controller和Hierarchy的绑定。
3.绑定Controller与Hierarchy
DraweeHolder.setController()
上面说了,在调用SimpleDraweeView的setImageURI()方法时,创建了DraweeController(即PipelineDraweeControllerBuilder),这个DraweeController完成了DataSource的获取,在该Controller获取后,SimpleDraweeView立马调用自身的setController方法来讲这个Controller设置进去,这个setController方法实际上调用的时DraweeHolder的setController方法:
1 | // SimpleDraweeView父类DraweeView的setController方法 |
- 如果Controller已经绑定了,先解绑;
- 如果Hierarchy已经绑定到了Controller,先解绑;
- 重设Hierarchy;
- 重设Controller
PipelineDraweeController.setHierarchy()
关于Hierarchy
和Controller
的重设,Hierarchy
的重设最终调用的是PipelineDraweeController
的setHierarchy
方法,参数Hierarchy
是在GenericDraweeView
的inflateHierarchy
方法中赋值的,看PipelineDraweeController
的setHierarchy
方法:
1 | public void setHierarchy( { DraweeHierarchy hierarchy) |
上面方法主要完成三个动作:
- 如果当前存在正在进行的请求,取消并释放掉;
- 清空已经存在的Hierarchy;
- 设置新的Hierarchy;
同样的,setController中的attachController其实最终调用的AbstractDraweeController的onAttach方法,该方法中工作:取消重在进行的请求,打上attach标记,重新提交请求;
这样Controller和Hierarchy的绑定工作就完成了,完成Controller和Hierarchy的绑定后,图片最终怎么显示出来呢?看下面。
4.获取图片并设置到对应的Drawable层
SimpleDraweeView.setImageURI发起,然后构造DraweeController,由Controller来完成请求(ImageRequest)的构造、发起(submitRequest)与请求结果的处理(onNewResultInternal),并最调用GenericDraweeHierarch的setImage方法完成图片的显示。
图片的最终设置,是调用GenericDraweeHierarchy的setImage方法来实现的,该方法是在AbstractDraweeController中submitRequest中调用的。
1 | public void setImage(Drawable drawable, float progress, boolean immediate) { |
mActualImageWrapper就是实际加载图片的那个图层,此处要设置的SimpleDraweeView最终要显示的图片。这样SimpleDraweeView的图片加载流程就完成了,当然还有不少细节需要继续分析。
Producer与Consumer
Fresco源码很好的利用了生产者和消费者模式,其中Producer负责从不同的来源(本地、网络等)获取图片数据,然后交由Consumer来消费处理,Producer的创建时机是在ImagePipeline调用fetchEncodedImage
和fetchDecodedImage
中,在调用ImagePipeline
的submitFetchRequest
之前,需要先构建好Producer
,Producer
是由ProducerSequenceFactory
的来进行构建的。
Producer
Producer接口的实现类有多种,按加载资源的种类可以分为以下几种:
1 | public interface Producer<T> { |
获取本地资源类Producer
从本地资源中获取数据
- LocalFetchProducer,本地资源类Producer的基类;提供一个抽象方法供其子类实现,就是根据自己的实际情况获取EncodedImage对象:
1 | protected abstract EncodedImage getEncodedImage(ImageRequest imageRequest) throws IOException; |
- DataFetchProducer,这个虽然继承自LocalFetchProducer,但不是严格意义上的本地资源,可以是一个Base64加密的图片资源字符串或者其他形式的加密串;
- LocalAssetFetchProducer,继承自LocalFetchProducer,从Asset里面的图片资源获取
- LocalContentUriFetchProducer,继承自LocalFetchProducer,从ContentUri形式的图片资源获取
- LocalContentUriThumbnailFetchProducer,继承自LocalFetchProducer,从ContentUrlThumbnail 形式的图片资源获取LocalFileFetchProducer,继承自LocalFetchProducer,从本地文件中的图片资源获取
- LocalResourceFetchProducer,继承自LocalFetchProducer,从本地res文件夹下的图片资源
- QualifierResourceFetchProducer,继承自LocalFetchProducer,从ContentResolver获取本地图片资源
- LocalExifThumbnailProducer,没继承LocalFetchProducer,获取本地Exif格式的图片
- LocalVideoThumbnailProducer,没有继承LocalFetchProducer,获取本地视频的缩略图;
获取网络数据类Producer
主要负责从网络层获取数据
- NetworkFetchProducer,实现Producer接口,从网络获取图片数据;
缓存类Producer
主要负责从缓存中获取数据
- BitmapMemoryCacheGetProducer 它是一个Immutable的Producer,仅用于包装后续Producer;
- BitmapMemoryCacheProducer 在已解码的内存缓存中获取数据;若未找到,则在nextProducer中获取数据,并在获取到数据的同时将其缓存;
- BitmapMemoryCacheKeyMultiplexProducer 是MultiplexProducer的子类,nextProducer为BitmapMemoryCacheProducer,将多个拥有相同已解码内存缓存键的ImageRequest进行“合并”,若缓存命中,它们都会获取到该数据;
- PostprocessedBitmapMemoryCacheProducer 在已解码的内存缓存中寻找PostProcessor处理过的图片。它的nextProducer都是PostProcessorProducer,因为如果没有获取到被PostProcess的缓存,就需要对获取的图片进行PostProcess。;若未找到,则在nextProducer中获取数据;
- EncodedMemoryCacheProducer 在未解码的内存缓存中寻找数据,如果找到则返回,使用结束后释放资源;若未找到,则在nextProducer中获取数据,并在获取到数据的同时将其缓存;
- EncodedCacheKeyMultiplexProducer 是MultiplexProducer的子类,nextProducer为EncodedMemoryCacheProducer,将多个拥有相同未解码内存缓存键的ImageRequest进行“合并”,若缓存命中,它们都会获取到该数据;
- DiskCacheProducer 在文件内存缓存中获取数据;若未找到,则在nextProducer中获取数据,并在获取到数据的同时将其缓存
功能类Producer
该类Producer在构建的时候回传入一个inputProducer,然后改Producer会对inputProducer进行相应的处理
- MultiplexProducer 将多个拥有相同CacheKey的ImageRequest进行“合并”,让他们从都从inputProducer中获取数据;
- ThreadHandoffProducer 将inputProducer的produceResult方法放在后台线程中执行(线程池容量为1);
- SwallowResultProducer 将inputProducer的获取的数据“吞”掉,会在Consumer的onNewResult中传入null值;
- ResizeAndRotateProducer 将inputProducer产生的EncodedImage根据EXIF的旋转、缩放属性进行变换(如果对象不是JPEG格式图像,则不会发生变换);
- PostProcessorProducer 将inputProducer产生的EncodedImage根据PostProcessor进行修改,关于PostProcessor详见修改图片;
- DecodeProducer 将inputProducer产生的EncodedImage解码。解码在后台线程中执行,可以在ImagePipelineConfig中通过setExecutorSupplier来设置线程池数量,默认为最大可用的处理器数;
- WebpTranscodeProducer 若inputProducer产生的EncodedImage为WebP格式,则将其解码成DecodeProducer能够处理的EncodedImage。解码在后代进程中进行。
ProducerSequenceFactory
意为Producer 序列工厂,所以ProducerSequenceFactory
用于获取Producer序列,这里序列指的是通过对Producer一层层的包装,从外到里一层层包装,最里层Producer处理后,交给外层处理,即Producer是由外向里一层层传递的。
- PostprocessedBitmapMemoryCacheProducer,非必须 ,在Bitmap缓存中查找被PostProcess过的数据。
- PostprocessorProducer,非必须,对下层Producer传上来的数据进行PostProcess。
- BitmapMemoryCacheGetProducer,必须,使Producer序列只读。
- ThreadHandoffProducer,必须,使下层Producer工作在后台进程中执行。
- BitmapMemoryCacheKeyMultiplexProducer,必须,使多个相同已解码内存缓存键的ImageRequest都从相同Producer中获取数据。
- BitmapMemoryCacheProducer,必须,从已解码的内存缓存中获取数据。
- DecodeProducer,必须,将下层Producer产生的数据解码。
- ResizeAndRotateProducer,非必须,将下层Producer产生的数据变换。
- EncodedCacheKeyMultiplexProducer,必须,使多个相同未解码内存缓存键的ImageRequest都从相同Producer中获取数据。
- EncodedMemoryCacheProducer,必须,从未解码的内存缓存中获取数据。
- DiskCacheProducer,必须,从文件缓存中获取数据。
- WebpTranscodeProducer,非必须,将下层Producer产生的Webp(如果是的话)进行解码。
- NetworkFetchProducer,必须,从网络上获取数据。
Consumer
Producer经过一些列的传递后,最终肯定会调用produceResult方法,然后在produceResult方法中调用Consumer来对产生的结果进行“消费”,那么Consumer是在什么时候创建的呢?
在AbstractProducerToDataSourceAdapter
的构造函数中,会调用createConsumer方法来创建Consumer,在请求经过Producer一层层由外到里传递后,到最里层最终会调用Consumer的onNewResultImpl
来消费产生的结果,并回调给上层。
缓存
Fresco是三级缓存结构,包括二级内存缓存,一级磁盘缓存,其中两级内存缓存包括:
- 已解码的内存缓存,由MemoryCache
类对象表示; - 未解码的内存缓存,由MemoryCache
类对象表示;
二者的区别:
已解码内存缓存 | 未解码内存缓存 | |
---|---|---|
缓存对象 | CloseableImage(CloseableBitmap、CloseableStaticBitmap) | EncodedImage |
对应的Producer | BitmapMemoryCacheProducer | EncodedMemoryCacheProducer |
解码内存缓存流程
解码内存缓存由BitmapMemoryCacheProducer
实现,主要在produceResult方法中,有以下几步:
- 从 MemoryCache 中 根据 CacheKey去取缓存;
- 判断缓存是否符合条件,符合条件的话,调用Consumer消费,直接反回,这个图片请求结束;
- 不符合条件,则将原始的consumer包装,得到一个wrapperConsumer,然后继续调用mInputProducer进行处理;
- 在mInputProducer的produceResult方法中,会调用wrapperConsumer来消费,在wrapperConsumer的onNewResultImpl中按需将请求得到的newResult(CloseableReference
对象)进行缓存; - 得到包装之前的Consumer,调用onNewResult通知上层Producer图片请求的结果
未解码内存缓存流程
未解码内存缓存由EncodedMemoryCacheProducer
实现,主要流程和解码内存缓存相似,不在赘述。
内存缓存的实现
有MemoryCache接口来实现,具体的实现类是LruCountingMemoryCache,里面有两个缓存集合:
1 | public class LruCountingMemoryCache<K, V> |
其中:
- mCachedEntries,它是用来存放所有缓存对象的集合;
- mExclusiveEntries,它主要用来存放当前没有被引用的对象,在缓存控件不足时,发生trim会删除掉该集合中的对象来释放缓存空间;
加入缓存:cache()
具体的缓存方法时cache,代码如下:
1 | public CloseableReference<V> cache(final K key, final CloseableReference<V> valueRef, |
cache的流程注释很清楚了,这里就不在一一说明了。
获取缓存:get()
1 | public CloseableReference<V> get(final K key) { |
缓存get的流程页比较简单。
磁盘缓存流程
磁盘缓存由于涉及到文件的读写,又包括以下几个过程:
- 缓冲缓存层,由BufferedDiskCache实现,提供缓冲功能;
- 文件缓存层,由DiskStorageCache实现,提供实际的缓存功能;
- 文件存储层,有DefaultDiskStorage实现,提供磁盘文件读写功能;
磁盘缓存存的过程
DiskCacheWriteProducer
磁盘缓存主要是通过DiskCacheWriteProducer
来完成的,缓存的具体实现是通过BufferedDiskCache
来完成的,看下该Producer中定义的Consumer:
1 | private static class DiskCacheWriteConsumer |
BufferedDiskCache
该类是磁盘缓存的缓冲层,其成员包括用于文件存储的mFileCache,用于读写缓存的mReadExecutor和mWriteExecutor,用于保存当前正在进行缓存操作的mStagingArea。
BufferedDiskCache.put()
1 | public void put(final CacheKey key, EncodedImage encodedImage) { |
BufferedDiskCache.writeToDiskCache()
1 | private void writeToDiskCache(final CacheKey key, final EncodedImage encodedImage) { |
核心代码就是将要缓存的内容插入到mFileCache中,mFileCache为DiskStorageCache实例。
DiskStorageCache.insert()
DiskStorageCache完成了文件实际缓存功能,而整个insert的过程代码比较多,主要包括以下几个步骤:
根据缓存的CacheKey得到resourceId
resourceId = CacheKeyUtil.getFirstResourceId(key);
调用DefaultDiskStorage的startInsert方法创建临时文件,并包装成DiskStorage.Inserter返回;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public Inserter insert(String resourceId, Object debugInfo) throws IOException {
// ensure that the parent directory exists
FileInfo info = new FileInfo(FileType.TEMP, resourceId);
File parent = getSubdirectory(info.resourceId);
if (!parent.exists()) {
mkdirs(parent, "insert");
}
try {
File file = info.createTempFile(parent);
return new InserterImpl(resourceId, file);
} catch (IOException ioe) {
mCacheErrorLogger.logError(
CacheErrorLogger.CacheErrorCategory.WRITE_CREATE_TEMPFILE, TAG, "insert", ioe);
throw ioe;
}
}调用DiskStorage.Inserter的writeData方法将图片内容写到临时文件中。
1
2
3
4
5
6
7public void writeData(WriterCallback callback, Object debugInfo) throws IOException {
FileOutputStream fileStream = new FileOutputStream(mTemporaryFile);
...
CountingOutputStream countingStream = new CountingOutputStream(fileStream);
callback.write(countingStream);
countingStream.flush();
}调用DiskStorageCache的endInsert,方法内部会通过DiskStorage.Inserter的commit方法,将临时文件重命名为resourceId;
重命名成功后,会工薪缓存的修改时间;
这样整个图片缓存的insert就完成了。
磁盘缓存读的过程
磁盘缓存读的过程其实就是磁盘缓存写的过程的逆过程
DiskCacheReadProducer
DiskCacheReadProducer中利用的开源的bolts-tasks-1.4.0这个库,类似于RxJava,但是比RxJava更轻便,支持链式编程。produceResults方法中,根据请求获取缓存的CacheKey和缓存配置,然后就是缓存读取成功后的处理,核心代码如下:
1 | final CacheKey cacheKey = |
缓存的获取部分代码:
BufferedDiskCache.get()
1 | public Task<EncodedImage> get(CacheKey key, AtomicBoolean isCancelled) { |
BufferedDiskCache.getAsync()
getSsync()步骤:
- 同样是在StagingArea中找;
- 找不到就调用readFromDiskCache从磁盘中找;
- readFromDiskCache最终调用DiskStorageCache.getResource(key)找到BinaryResource资源,然后通过BinaryResource来获取其二进制流返回;
以上就是读取磁盘缓存的大概过程。
小知识
Fresco默认内存缓存的大小
设置间下面代码:
DefaultBitmapMemoryCacheParamsSupplier.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17private int getMaxCacheSize() {
final int maxMemory =
Math.min(mActivityManager.getMemoryClass() * ByteConstants.MB, Integer.MAX_VALUE);
if (maxMemory < 32 * ByteConstants.MB) {
return 4 * ByteConstants.MB;
} else if (maxMemory < 64 * ByteConstants.MB) {
return 6 * ByteConstants.MB;
} else {
// We don't want to use more ashmem on Gingerbread for now, since it doesn't respond well to
// native memory pressure (doesn't throw exceptions, crashes app, crashes phone)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return 8 * ByteConstants.MB;
} else {
return maxMemory / 4;// 现在绝大多数命中这个分支了
}
}
}使用Fresco显示图片时SimpleDraweeView需要明确图片的显示尺寸,要么指定为MatchParent,要么指定宽和高;
Fresco自带的高斯模糊处理
注意:这里的图片是非加密的图片,Fresco自带的高斯模糊处理有两种:
- BlurPostProcessor,纯Java实现;
- IterativeBoxBlurPostProcessor,采用C实现,省内存更高效,性能更好
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public static void showBlurImg( DraweeView draweeView, String url,
int iterations, int blurRadius) {
try {
Uri uri = Uri.parse(url);
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(uri)
.setPostprocessor(new IterativeBoxBlurPostProcessor(iterations, blurRadius))
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setOldController(draweeView.getController())
.setImageRequest(imageRequest)
.build();
draweeView.setController(controller);
} catch (Exception e) {
LogHelper.printStackTrace(e);
}
}Fresco Bitmap对象的获取
- 方法一:获取已经解密的Bitmap对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29/**
* 加载图片成bitmap。
*
* @param imageUrl 图片地址。
*/
public static void loadToBitmap(String imageUrl, BaseBitmapDataSubscriber mDataSubscriber) {
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(imageUrl))
.setProgressiveRenderingEnabled(true)
.build();
ImagePipeline imagePipeline = Fresco.getImagePipeline();
DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage
(imageRequest, App.get());
dataSource.subscribe(mDataSubscriber, CallerThreadExecutor.getInstance());
}
// 然后这样调用这个方法
loadToBitmap(imageUrl, new BaseBitmapDataSubscriber() {
public void onNewResultImpl( { Bitmap bitmap)
// 读取成功。
}
public void onFailureImpl(DataSource dataSource) {
// 读取失败。
}
});方法二,直接利用当前展示的Bitmap对象(这个一定是解密后的Bitmap)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(uri)
.setImageDecodeOptions(imageDecodeOptions)
.setRotationOptions(RotationOptions.autoRotate())
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(imageRequest)
.setOldController(draweeView.getController())
.setControllerListener(new BaseControllerListener<Object>() {
public void onFinalImageSet(String id, Object imageInfo,
{ Animatable animatable)
super.onFinalImageSet(id, imageInfo, animatable);
// 这里的imageInfo就是解密之后的 CloseableStaticBitmap 对象,
if (imageInfo instanceof CloseableStaticBitmap) {
CloseableStaticBitmap staticBitmap = (CloseableStaticBitmap) imageInfo;
// 获取到真正的bitmap
Bitmap bitmap = staticBitmap.getUnderlyingBitmap();
}
}
}
.build();
draweeView.setController(controller);
Fresco 图片的下载
主要就是调用prefetchToDiskCache将图片保存在磁盘缓存中,然后DataSubscriber去订阅即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25DataSource<Boolean> dataSource;
ImageRequest imageRequest = ImageRequest.fromUri(Uri.parse(url));
ImagePipeline imagePipeline = Fresco.getImagePipeline();
dataSource = imagePipeline.isInDiskCache(imageRequest);
DataSubscriber<Boolean> subscriber = new BaseDataSubscriber<Boolean>() {
protected void onNewResultImpl(DataSource<Boolean> dataSource) {
if (!dataSource.isFinished()) {
return;
}
if (listener != null) {
listener.handleResult(dataSource.getResult());
}
// your code here
dataSource = null;
}
protected void onFailureImpl(DataSource<Boolean> dataSource) {
if (listener != null) {
listener.handleResult(false);
}
}
};
dataSource.subscribe(subscriber, CallerThreadExecutor.getInstance());
总结
本文简单的介绍了下Fresco的基本应用,并结合代码分析了期图片的加载流程,抛开框架本身强大的功能不谈,Fresco在代码设计的层面给人的启示就很深刻,大量的应用了设计模式,合理的进行了分层和解耦,是以后代码设计的一个很好的参考。
PS:经过这么长的时间,这篇文章总算完成了,希望自己以后能更加高效一点,更有执行力一点。
参考文章: