关于作者
郭孝星,程序员,吉他手,主要从事Android平台基础架构方面的工作,欢迎交流技术方面的问题,可以去我的Github提issue或者发邮件至guoxiaoxingse@163.com与我交流。
文章目录
- 一 图片加载流程
- 1.1 初始化Fresco
- 1.2 获取DataSource
- 1.3 绑定DraweeController与DraweeHierarchy
- 1.4 从内存缓存/磁盘缓存/网络获取图片,并设置到对应的Drawable层
- 二 DraweeController与DraweeHierarchy
- 2.1 图层的层级构造
- 2.2 图层的构建流程
- 三 Producer与Consumer
- 四 缓存机制
- 3.1 内存缓存
- 3.2 磁盘缓存
更多Android开源框架源码分析文章请参见Android open framework analysis。
这个系列的文章原来叫做《Android开源框架源码分析》,后来这些优秀开源库的代码看的多了,感觉大佬们代码写的真真美如画👍,所以就更名为《Android开源框架源码鉴赏》了。闲话 不多说,我们进入正题,今天分析的开源库是Fresco。
Fresco是一个功能完善的图片加载框架,在Android开发中有着广泛的应用,那么它作为一个图片加载框架,有哪些特色让它备受推崇呢?
- 完善的内存管理功能,减少图片对内存的占用,即便在低端机器上也有着不错的表现。
- 自定义图片加载的过程,可以先显示低清晰度图片或者缩略图,加载完成后再显示高清图,可以在加载的时候缩放和旋转图片。
- 自定义图片绘制的过程,可以自定义谷中焦点、圆角图、占位图、overlay、进图条。
- 渐进式显示图片。
- 支持Gif。
- 支持Webp。
好,又吹了一波Fresco(人家好像也不给广告费T_T),但是光知道人家好并没有用,我们还需要为什么这么好,怎么实现的,日后在做我们的框架的时候偷师一手,岂不美哉。 Fresco的源码还是比较多的,看起来会比较费劲,但是不怕,Android的系统源码都被我们啃下来了,还怕一个小小的Fresco吗😎。要更好的去理解Fresco的实现,还是要从 整体入手,了解它的模块和层次划分,层层推进,逐个理解,才能达到融会贯通的效果。
由于Fresco比较大,我们先来看一下它的整体结构,有个整体的把握,Fresco的整体架构如下图所示:
👉 点击图片查看大图
- DraweeView:继承于ImageView,只是简单的读取xml文件的一些属性值和做一些初始化的工作,图层管理交由Hierarchy负责,图层数据获取交由负责。
- DraweeHierarchy:由多层Drawable组成,每层Drawable提供某种功能(例如:缩放、圆角)。
- DraweeController:控制数据的获取与图片加载,向pipeline发出请求,并接收相应事件,并根据不同事件控制Hierarchy,从DraweeView接收用户的事件,然后执行取消网络请求、回收资源等操作。
- DraweeHolder:统筹管理Hierarchy与DraweeHolder。
- ImagePipeline:Fresco的核心模块,用来以各种方式(内存、磁盘、网络等)获取图像。
- Producer/Consumer:Producer也有很多种,它用来完成网络数据获取,缓存数据获取、图片解码等多种工作,它产生的结果由Consumer进行消费。
- IO/Data:这一层便是数据层了,负责实现内存缓存、磁盘缓存、网络缓存和其他IO相关的功能。
纵观整个Fresco的架构,DraweeView是门面,和用户进行交互,DraweeHierarchy是视图层级,管理图层,DraweeController是控制器,管理数据。它们构成了整个Fresco框架的三驾马车。当然还有我们 幕后英雄Producer,所有的脏活累活都是它干的,最佳劳模👍
理解了Fresco整体的架构,我们还有了解在这套矿建里发挥重要作用的几个关键角色,如下所示:
- Supplier:提供一种特定类型的对象,Fresco里有很多以Supplier结尾的类都实现了这个接口。
- SimpleDraweeView:这个我们就很熟悉了,它接收一个URL,然后调用Controller去加载图片。该类继承于GenericDraweeView,GenericDraweeView又继承于DraweeView,DraweeView是Fresco的顶层View类。
- PipelineDraweeController:负责图片数据的获取与加载,它继承于AbstractDraweeController,由PipelineDraweeControllerBuilder构建而来。AbstractDraweeController实现了DraweeController接口,DraweeController 是Fresco的数据大管家,所以的图片数据的处理都是由它来完成的。
- GenericDraweeHierarchy:负责SimpleDraweeView上的图层管理,由多层Drawable组成,每层Drawable提供某种功能(例如:缩放、圆角),该类由GenericDraweeHierarchyBuilder进行构建,该构建器 将placeholderImage、retryImage、failureImage、progressBarImage、background、overlays与pressedStateOverlay等 xml文件或者Java代码里设置的属性信息都传入GenericDraweeHierarchy中,由GenericDraweeHierarchy进行处理。
- DraweeHolder:该类是一个Holder类,和SimpleDraweeView关联在一起,DraweeView是通过DraweeHolder来统一管理的。而DraweeHolder又是用来统一管理相关的Hierarchy与Controller
- DataSource:类似于Java里的Futures,代表数据的来源,和Futures不同,它可以有多个result。
- DataSubscriber:接收DataSource返回的结果。
- ImagePipeline:用来调取获取图片的接口。
- Producer:加载与处理图片,它有多种实现,例如:NetworkFetcherProducer,LocalAssetFetcherProducer,LocalFileFetchProducer。从这些类的名字我们就可以知道它们是干什么的。 Producer由ProducerFactory这个工厂类构建的,而且所有的Producer都是像Java的IO流那样,可以一层嵌套一层,最终只得到一个结果,这是一个很精巧的设计👍
- Consumer:用来接收Producer产生的结果,它与Producer组成了生产者与消费者模式。
注:Fresco源码里的类的名字都比较长,但是都是按照一定的命令规律来的,例如:以Supplier结尾的类都实现了Supplier接口,它可以提供某一个类型的对象(factory, generator, builder, closure等)。 以Builder结尾的当然就是以构造者模式创建对象的类。
通过上面的描述,想必大家都Fresco有了一个整体的认识,那面对这样庞大的一个库,我们在去分析它的时候需要重点关注哪些点呢?🤔
- 图片加载流程
- DraweeController与DraweeHierarchy
- Producer与Consumer
- 缓存机制
👉 注:Fresco里还大量运用各种设计模式,例如:Builder、Factory、Wrapper、Producer/Consumer、Adapter等,在阅读源码的时候,大家也要留心这些设计模式的应用与实践。
接下来我们就带着这4个问题去源码中一探究竟。
一 图片加载流程
至于分析的手段,还是老套路,先从一个简单的例子入手,展示Fresco是如何加载图片的,然后去分析它的图片加载流程,让大家有个整体的理解,然后再逐个去分析Fresco每个 子模块的功能实现。
好,我们先来写一个小例子。
👉 举例
初始化
Fresco.initialize(this);
加载图片
String url = "https://github.com/guoxiaoxing/android-open-framwork-analysis/raw/master/art/fresco/scenery.jpg";
SimpleDraweeView simpleDraweeView = findViewById(R.id.drawee_view);
simpleDraweeView.setImageURI(Uri.parse(url));
我们来看一下它的调用流程,序列图如下所示:
👉 点击图片查看大图
嗯,图看起来有点大,但是不要紧,我们按照颜色将整个流程分为了四大步:
- 初始化Fresco。
- 获取DataSource。
- 绑定Controller与Hierarchy。
- 从内存缓存/磁盘缓存/网络获取图片,并设置到对应的Drawable层。
👉 注:Fresco里的类虽多,类名虽长,但都是基于接口和Abstract类的设计,每个模块自成一套继承体系,所以只要掌握了它们的继承关系以及不同模块之间的联系,整个 流程还是比较简单的。
由于序列图设计具体细节,为了辅助理解,我们再提供一张总结新的流程图,如下所示:
👉 点击图片查看大图
接下来,我们就针对这两张图结合具体细节来一一分析。
1.1 初始化Fresco
👉 序列图 1.1 -> 1.11
public class Fresco {
public static void initialize(
Context context,
@Nullable ImagePipelineConfig imagePipelineConfig,
@Nullable DraweeConfig draweeConfig) {
//... 重复初始化检验
try {
//1. 加载so库,这个主要是一些第三方的native库,例如:giflib,libjpeg,libpng,
//主要用来做图片解码。
SoLoader.init(context, 0);
} catch (IOException e) {
throw new RuntimeException("Could not initialize SoLoader", e);
}
//2. 设置传入的配置参数magePipelineConfig。
context = context.getApplicationContext();
if (imagePipelineConfig == null) {
ImagePipelineFactory.initialize(context);
} else {
ImagePipelineFactory.initialize(imagePipelineConfig);
}
//3. 初始化SimpleDraweeView。
initializeDrawee(context, draweeConfig);
}
private static void initializeDrawee(
Context context,
@Nullable DraweeConfig draweeConfig) {
//构建PipelineDraweeControllerBuilderSupplier对象,并传给SimpleDraweeView。
sDraweeControllerBuilderSupplier =
new PipelineDraweeControllerBuilderSupplier(context, draweeConfig);
SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}
}
可以发现,Fresco在初始化的过程中,主要做了三件事情:
- 加载so库,这个主要是一些第三方的native库,例如:giflib,libjpeg,libpng,主要用来做图片解码。
- 设置传入的配置参数magePipelineConfig。
- 初始化SimpleDraweeView。
这里面我们需要重点关注三个对象:
- ImagePipelineConfig:ImagePipeline参数配置。
- DraweeControllerBuilderSupplier:提供DraweeControllerBuilder用来构建DraweeController。
我们先来看ImagePipelineConfig,ImagePipelineConfig通过建造者模式来构建传递给ImagePipeline的参数,如下所示:
- Bitmap.Config mBitmapConfig; 图片质量。
- Supplier mBitmapMemoryCacheParamsSupplier; 内存缓存的配置参数提供者。
- CountingMemoryCache.CacheTrimStrategy mBitmapMemoryCacheTrimStrategy; 内存缓存的削减策略。
- CacheKeyFactory mCacheKeyFactory; CacheKey的创建工厂。
- Context mContext; 上下文环境。
- boolean mDownsampleEnabled; 是否开启图片向下采样。
- FileCacheFactory mFileCacheFactory; 磁盘缓存创建工厂。
- Supplier mEncodedMemoryCacheParamsSupplier; 未解码图片缓存配置参数提供者。
- ExecutorSupplier mExecutorSupplier; 线程池提供者。
- ImageCacheStatsTracker mImageCacheStatsTracker; 图片缓存状态追踪器。
- ImageDecoder mImageDecoder; 图片解码器。
- Supplier mIsPrefetchEnabledSupplier; 是否开启预加载。
- DiskCacheConfig mMainDiskCacheConfig; 磁盘缓存配置。
- MemoryTrimmableRegistry mMemoryTrimmableRegistry; 内存变化监听注册表,那些需要监听系统内存变化的对象需要添加到这个表中类。
- NetworkFetcher mNetworkFetcher; 下载网络图片,默认使用内置的HttpUrlConnectionNetworkFetcher,也可以自定义。
- PlatformBitmapFactory mPlatformBitmapFactory; 根据不同的Android版本生成不同的Bitmap的工厂,主要的区别在Bitmap在内存中的位置,Android 5.0以下存储在Ashmem中,Android 5.0以上存在Java Heap中。
- PoolFactory mPoolFactory; Bitmap池等各种池的构建工厂。
- ProgressiveJpegConfig mProgressiveJpegConfig; 渐进式JPEG配置。
- Set mRequestListeners; 请求监听器集合,监听请求过程中的各种事件。
- boolean mResizeAndRotateEnabledForNetwork; 是否开启网络图片的压缩和旋转。
- DiskCacheConfig mSmallImageDiskCacheConfig; 磁盘缓存配置
- ImageDecoderConfig mImageDecoderConfig; 图片解码配置
- ImagePipelineExperiments mImagePipelineExperiments; Fresco提供的关于Image Pipe的实验性功能。
上述参数基本不需要我们手动配置,除非项目上有定制性的需求。
我们可以发现,在初始化方法的最后调用initializeDrawee()给SimpleDraweeView传入了一个PipelineDraweeControllerBuilderSupplier,这是一个很重要的对象,我们 来看看它都初始化了哪些东西。
public class PipelineDraweeControllerBuilderSupplier implements
Supplier<PipelineDraweeControllerBuilder> {
public PipelineDraweeControllerBuilderSupplier(
Context context,
ImagePipelineFactory imagePipelineFactory,
Set<ControllerListener> boundControllerListeners,
@Nullable DraweeConfig draweeConfig) {
mContext = context;
//1. 获取ImagePipeline
mImagePipeline = imagePipelineFactory.getImagePipeline();
if (draweeConfig != null && draweeConfig.getPipelineDraweeControllerFactory() != null) {
mPipelineDraweeControllerFactory = draweeConfig.getPipelineDraweeControllerFactory();
} else {
mPipelineDraweeControllerFactory = new PipelineDraweeControllerFactory();
}
//2. 获取PipelineDraweeControllerFactory,并初始化。
mPipelineDraweeControllerFactory.init(
context.getResources(),
DeferredReleaser.getInstance(),
imagePipelineFactory.getAnimatedDrawableFactory(context),
UiThreadImmediateExecutorService.getInstance(),
mImagePipeline.getBitmapMemoryCache(),
draweeConfig != null
? draweeConfig.getCustomDrawableFactories()
: null,
draweeConfig != null
? draweeConfig.getDebugOverlayEnabledSupplier()
: null);
mBoundControllerListeners = boundControllerListeners;
}
}
可以发现在这个方法里初始化了两个重要的对象:
- 获取ImagePipeline。
- 获取PipelineDraweeControllerFactory,并初始化。
这个PipelineDraweeControllerFactory就是用来构建PipelineDraweeController,我们前面说过PipelineDraweeController继承于AbstractDraweeController,用来控制图片 数据的获取和加载,这个PipelineDraweeControllerFactory()的init()方法也是将参数里的遍历传入PipelineDraweeControllerFactory中,用来准备构建PipelineDraweeController。 我们来看一下它都传入哪些东西进去。
- context.getResources():Android的Resources对象。
- DeferredReleaser.getInstance():延迟释放资源,等主线程处理完消息后再进行回收。
- mImagePipeline.getBitmapMemoryCache():已解码的图片缓存。
👉 注:所谓拔出萝卜带出泥,在分析图片加载流程的时候难免会带进来各种各样的类,如果一时理不清它们的关系也没关系,第一步只是要掌握整体的加载流程即可,后面 我们会对这些类逐一分析。
该方法执行完成后调用SimpleDraweeView的initizlize()方法将PipelineDraweeControllerBuilderSupplier对象设置进SimpleDraweeView的静态对象sDraweeControllerBuilderSupplier中 整个初始化流程便完成了。
1.2 获取DataSource
👉 序列图 2.1 -> 2.12
在分析如何生成DataSource之前,我们得先了解什么DataSource。
DataSource是一个接口其实现类是AbstractDataSource,它可以提交数据请求,并能获取progress、fail result与success result等信息,类似于Java里的Future。
DataSource接口如下所示:
public interface DataSource<T> {
//数据源是否关闭
boolean isClosed();
//异步请求的结果
@Nullable T getResult();
//是否有结果返回
boolean hasResult();
//请求是否结束
boolean isFinished();
//请求是否发生错误
boolean hasFailed();
//发生错误的原因
@Nullable Throwable getFailureCause();
//请求的进度[0, 1]
float getProgress();
//结束请求,释放资源。
boolean close();
//发送并订阅请求,等待请求结果。
void subscribe(DataSubscriber<T> dataSubscriber, Executor executor);
}
AbstractDataSource实现了DataSource接口,它是一个基础类,其他DataSource类都扩展自该类。AbstractDataSource实现了上述接口里的方法,维护这DataSource的success、progress和fail的状态。 除此之外还有以下DataSource类:
- AbstractProducerToDataSourceAdapter:继承自AbstractDataSource,包装了Producer取数据的过程,也就是创建了一个Consumer,详细的过程我们后面还会说。
- CloseableProducerToDataSourceAdapter:继承自AbstractProducerToDataSourceAdapter,实现了closeResult()方法,绘制自己销毁时同时销毁Result,这个是最主要使用的DataSource。
- ProducerToDataSourceAdapter:没有实现额外的方法,仅仅用于预加载图片。
- IncreasingQualityDataSource:内部维护一个CloseableProducerToDataSourceAdapter列表,按数据的清晰度从后往前递增,它为列表里的每个DataSour测绑定一个DataSubscriber,该类负责保证 每次获取清晰度更高的数据,获取数据的同时销毁清晰度更低的数据。
- FirstAvailableDataSource:内部维护一个CloseableProducerToDataSourceAdapter列表,它会返回列表里最先获取数据的DataSource,它为列表里的每个DataSour测绑定一个DataSubscriber,如果 数据加载成功,则将当前成功的DataSource指定为目标DataSource,否则跳转到下一个DataSource继续尝试。
- SettableDataSource:继承自AbstractDataSource,并将重写settResult()、setFailure()、setProgress()在内部调用父类的相应函数,但是修饰符变成了public(原来是protected)。即使 用SettableDataSource时可以在外部调用这三个函数设置DataSource状态。一般用于在获取DataSource失败时直接产生一个设置为Failure的DataSource。
了解了DataSource,我们再来看看它是如何生成的。
我们知道,在使用Fresco展示图片的时候,只需要调用setImageURI()设置图片URL即可,我们就以这个方法为入口开始分析,如下所示:
public class SimpleDraweeView extends GenericDraweeView {
public void setImageURI(Uri uri, @Nullable Object callerContext) {
DraweeController controller = mSimpleDraweeControllerBuilder
.setCallerContext(callerContext)
.setUri(uri)
.setOldController(getController())
.build();
setController(controller);
}
}
可以发现,SimpleDraweeView将外面传递的URL数据封装进了DraweeController,并调用mSimpleDraweeControllerBuilder构造了一个DraweeController对象,这个 DraweeController对象实际上就是PipelineDraweeController。
我们来看看它是如何构建的,mSimpleDraweeControllerBuilder由sDraweeControllerBuilderSupplier调用ge