关于做者java
郭孝星,程序员,吉他手,主要从事Android平台基础架构方面的工做,欢迎交流技术方面的问题,能够去个人Github提issue或者发邮件至guoxiaoxingse@163.com与我交流。android
文章目录git
更多Android开源框架源码分析文章请参见Android open framework analysis。程序员
这个系列的文章原来叫作《Android开源框架源码分析》,后来这些优秀开源库的代码看的多了,感受大佬们代码写的真真美如画👍,因此就改名为《Android开源框架源码鉴赏》了。闲话 很少说,咱们进入正题,今天分析的开源库是Fresco。github
Fresco是一个功能完善的图片加载框架,在Android开发中有着普遍的应用,那么它做为一个图片加载框架,有哪些特点让它备受推崇呢?编程
好,又吹了一波Fresco(人家好像也不给广告费T_T),可是光知道人家好并无用,咱们还须要为何这么好,怎么实现的,往后在作咱们的框架的时候偷师一手,岂不美哉。 Fresco的源码仍是比较多的,看起来会比较费劲,可是不怕,Android的系统源码都被咱们啃下来了,还怕一个小小的Fresco吗😎。要更好的去理解Fresco的实现,仍是要从 总体入手,了解它的模块和层次划分,层层推动,逐个理解,才能达到融会贯通的效果。设计模式
因为Fresco比较大,咱们先来看一下它的总体结构,有个总体的把握,Fresco的总体架构以下图所示:数组
👉 点击图片查看大图缓存
纵观整个Fresco的架构,DraweeView是门面,和用户进行交互,DraweeHierarchy是视图层级,管理图层,DraweeController是控制器,管理数据。它们构成了整个Fresco框架的三驾马车。固然还有咱们 幕后英雄Producer,全部的脏活累活都是它干的,最佳劳模👍bash
理解了Fresco总体的架构,咱们还有了解在这套矿建里发挥重要做用的几个关键角色,以下所示:
注:Fresco源码里的类的名字都比较长,可是都是按照必定的命令规律来的,例如:以Supplier结尾的类都实现了Supplier接口,它能够提供某一个类型的对象(factory, generator, builder, closure等)。 以Builder结尾的固然就是以构造者模式建立对象的类。
经过上面的描述,想必你们都Fresco有了一个总体的认识,那面对这样庞大的一个库,咱们在去分析它的时候须要重点关注哪些点呢?🤔
👉 注: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里的类虽多,类名虽长,但都是基于接口和Abstract类的设计,每一个模块自成一套继承体系,因此只要掌握了它们的继承关系以及不一样模块之间的联系,整个 流程仍是比较简单的。
因为序列图设计具体细节,为了辅助理解,咱们再提供一张总结新的流程图,以下所示:
👉 点击图片查看大图
接下来,咱们就针对这两张图结合具体细节来一一分析。
👉 序列图 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在初始化的过程当中,主要作了三件事情:
这里面咱们须要重点关注三个对象:
咱们先来看ImagePipelineConfig,ImagePipelineConfig经过建造者模式来构建传递给ImagePipeline的参数,以下所示:
上述参数基本不须要咱们手动配置,除非项目上有定制性的需求。
咱们能够发现,在初始化方法的最后调用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;
}
}
复制代码
能够发如今这个方法里初始化了两个重要的对象:
这个PipelineDraweeControllerFactory就是用来构建PipelineDraweeController,咱们前面说过PipelineDraweeController继承于AbstractDraweeController,用来控制图片 数据的获取和加载,这个PipelineDraweeControllerFactory()的init()方法也是将参数里的遍历传入PipelineDraweeControllerFactory中,用来准备构建PipelineDraweeController。 咱们来看一下它都传入哪些东西进去。
👉 注:所谓拔出萝卜带出泥,在分析图片加载流程的时候不免会带进来各类各样的类,若是一时理不清它们的关系也不要紧,第一步只是要掌握总体的加载流程便可,后面 咱们会对这些类逐一分析。
该方法执行完成后调用SimpleDraweeView的initizlize()方法将PipelineDraweeControllerBuilderSupplier对象设置进SimpleDraweeView的静态对象sDraweeControllerBuilderSupplier中 整个初始化流程便完成了。
👉 序列图 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类:
了解了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调用get()方法得到,咱们前面已经说过sDraweeControllerBuilderSupplier是在 SimpleDraweeView的initialize()被传递进来的,咱们接着来看PipelineDraweeController的构建过程。
SimpleDraweeControllerBuilder是调用器父类AbstractDraweeControllerBuilder的build()方法来进行构建,而该build()方法又反过来调用其子类SimpleDraweeControllerBuilder 的obtainController()方法来完成具体子类SimpleDraweeControllerBuilder的构建,咱们来看看它的实现。
👉 注:Fresco的设计很好的体现了面向接口编程这一点,大部分功能都基于接口设计,而后设计出抽象类AbstractXXX,用来封装通用的功能,个别具体的功能交由其子类实现。
public class PipelineDraweeControllerBuilder extends AbstractDraweeControllerBuilder< PipelineDraweeControllerBuilder, ImageRequest, CloseableReference<CloseableImage>, ImageInfo> {
@Override
protected PipelineDraweeController obtainController() {
DraweeController oldController = getOldController();
PipelineDraweeController controller;
//若是已经有PipelineDraweeController,则进行复用,不然构建新的PipelineDraweeController。
if (oldController instanceof PipelineDraweeController) {
controller = (PipelineDraweeController) oldController;
controller.initialize(
obtainDataSourceSupplier(),
generateUniqueControllerId(),
getCacheKey(),
getCallerContext(),
mCustomDrawableFactories);
} else {
controller = mPipelineDraweeControllerFactory.newController(
obtainDataSourceSupplier(),
generateUniqueControllerId(),
getCacheKey(),
getCallerContext(),
mCustomDrawableFactories);
}
return controller;
}
}
复制代码
能够发现上述函数的逻辑也很简单,若是已经有PipelineDraweeController,则进行复用,不然调用PipelineDraweeControllerFactory.newController()方法构建 新的PipelineDraweeController。PipelineDraweeControllerFactory.newController()方法最终调用PipelineDraweeController的构造方法完成PipelineDraweeController 对象的构建,后续的流程很简单,咱们重点关注在构建的过程当中传入了哪些对象,这些对象是如何生成的。
其余的实现都比较简单,咱们重点关注obtainDataSourceSupplier()的实现,以下所示:
public class PipelineDraweeControllerBuilder extends AbstractDraweeControllerBuilder< PipelineDraweeControllerBuilder, ImageRequest, CloseableReference<CloseableImage>, ImageInfo> {
protected Supplier<DataSource<IMAGE>> obtainDataSourceSupplier() {
if (mDataSourceSupplier != null) {
return mDataSourceSupplier;
}
Supplier<DataSource<IMAGE>> supplier = null;
//1. 生成最终的image supplier。
if (mImageRequest != null) {
supplier = getDataSourceSupplierForRequest(mImageRequest);
} else if (mMultiImageRequests != null) {
supplier = getFirstAvailableDataSourceSupplier(mMultiImageRequests, mTryCacheOnlyFirst);
}
//2. 生成一个ncreasing-quality supplier,这里会有两级的清晰度,高清晰度的supplier优先。
if (supplier != null && mLowResImageRequest != null) {
List<Supplier<DataSource<IMAGE>>> suppliers = new ArrayList<>(2);
suppliers.add(supplier);
suppliers.add(getDataSourceSupplierForRequest(mLowResImageRequest));
supplier = IncreasingQualityDataSourceSupplier.create(suppliers);
}
//若是没有图片请求,则提供一个空的supplier。
if (supplier == null) {
supplier = DataSources.getFailedDataSourceSupplier(NO_REQUEST_EXCEPTION);
}
return supplier;
}
}
复制代码
getDataSourceSupplierForRequest()方法最终调用(具体调用链能够参照序列图,这里就再也不赘述)的是PipelineDraweeControllerBuilder的getDataSourceForRequest()
public class PipelineDraweeControllerBuilder extends AbstractDraweeControllerBuilder< PipelineDraweeControllerBuilder, ImageRequest, CloseableReference<CloseableImage>, ImageInfo> {
@Override
protected DataSource<CloseableReference<CloseableImage>> getDataSourceForRequest(
ImageRequest imageRequest,
Object callerContext,
AbstractDraweeControllerBuilder.CacheLevel cacheLevel) {
//调用ImagePipeline的fetchDecodedImage()方法获取DataSource
return mImagePipeline.fetchDecodedImage(
imageRequest,
callerContext,
convertCacheLevelToRequestLevel(cacheLevel));
}
}
复制代码
ImagePipeline是Fresco Image Pipeline的入口类,前面也说过ImagePipeline是Fresco的核心模块,用来以各类方式(内存、磁盘、网络等)获取图像。
这个mImagePipeline就是在PipelineDraweeControllerBuilderSupplier中调用ImagePipelineFactory的getImagePipeline()方法建立的。 咱们接着来看ImagePipeline的fetchDecodedImage()方法,以下所示:
public class ImagePipeline {
public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(
ImageRequest imageRequest,
Object callerContext,
ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit) {
try {
//1. 获取Producer序列,为DataSource提供不一样的数据输入管道。
Producer<CloseableReference<CloseableImage>> producerSequence =
mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);
//2. 调用submitFetchRequest()方法生成DataSource。
return submitFetchRequest(
producerSequence,
imageRequest,
lowestPermittedRequestLevelOnSubmit,
callerContext);
} catch (Exception exception) {
return DataSources.immediateFailedDataSource(exception);
}
}
}
复制代码
关于什么是Producer,咱们前面也已经说过。
Producer用来加载与处理图片,它有多种实现,例如:NetworkFetcherProducer,LocalAssetFetcherProducer,LocalFileFetchProducer。从这些类的名字咱们就能够知道它们是干什么的。 Producer由ProducerFactory这个工厂类构建的,并且全部的Producer都是像Java的IO流那样,能够一层嵌套一层,最终只获得一个结果,
关于Producer的更多内容,咱们后面会专门讲,这个方法主要作了两件事情:
能够发现该方法最终调用submitFetchRequest()方法生成了DataSource,以下所示:
public class ImagePipeline {
private <T> DataSource<CloseableReference<T>> submitFetchRequest(
Producer<CloseableReference<T>> producerSequence,
ImageRequest imageRequest,
ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit,
Object callerContext) {
final RequestListener requestListener = getRequestListenerForRequest(imageRequest);
try {
//1. 获取缓存级别,RequestLevel将缓存分为四级 FULL_FETCH(1) 从网络或者本地存储获取,DISK_CACHE(2) 从磁盘缓存获取,ENCODED_MEMORY_CACHE(3)从
//未接吗的内存缓存获取,BITMAP_MEMORY_CACHE(4)已解码的内存缓存获取。
ImageRequest.RequestLevel lowestPermittedRequestLevel =
ImageRequest.RequestLevel.getMax(
imageRequest.getLowestPermittedRequestLevel(),
lowestPermittedRequestLevelOnSubmit);
//2. 将ImageRequest、RequestListener等信息封装进SettableProducerContext,ProducerContext是Producer
//的上下文环境,利用ProducerContext能够改变Producer内部的状态。
SettableProducerContext settableProducerContext = new SettableProducerContext(
imageRequest,
generateUniqueFutureId(),
requestListener,
callerContext,
lowestPermittedRequestLevel,
/* isPrefetch */ false,
imageRequest.getProgressiveRenderingEnabled() ||
imageRequest.getMediaVariations() != null ||
!UriUtil.isNetworkUri(imageRequest.getSourceUri()),
imageRequest.getPriority());
//3. 建立CloseableProducerToDataSourceAdapter,CloseableProducerToDataSourceAdapter是DataSource的一种。
return CloseableProducerToDataSourceAdapter.create(
producerSequence,
settableProducerContext,
requestListener);
} catch (Exception exception) {
return DataSources.immediateFailedDataSource(exception);
}
}
}
复制代码
该方法主要作了三件事情:
接着CloseableProducerToDataSourceAdapter调用了本身create()方法构建一个CloseableProducerToDataSourceAdapter对象。至此DataSource已经完成完成了,而后把它设置到 PipelineDraweeController里。
咱们接着来看绑定Controller与Hierarchy的流程。👇
👉 序列图 3.1 -> 3.7
前面提到在SimpleDraweeView的setImageURI()方法里会为SimpleDraweeView设置前面构建好的PipelineDraweeController,以下所示:
public void setImageURI(Uri uri, @Nullable Object callerContext) {
DraweeController controller = mSimpleDraweeControllerBuilder
.setCallerContext(callerContext)
.setUri(uri)
.setOldController(getController())
.build();
setController(controller);
}
复制代码
从上面的序列图得知,setController()方法通过层层调用,最终调用的是DraweeHolder的setController()方法,DraweeHolder用来统筹管理Controller与Hierarchy,它是DraweeView的一个 成员变量,在DraweeHolder对象初始化的时候被构建,咱们来看看它的setController()方法,以下所示:
public class DraweeHolder<DH extends DraweeHierarchy> implements VisibilityCallback {
public void setController(@Nullable DraweeController draweeController) {
boolean wasAttached = mIsControllerAttached;
//1. 若是已经和Controller创建联系,则先detach。
if (wasAttached) {
detachController();
}
//2. 清楚旧的Controller。
if (isControllerValid()) {
mEventTracker.recordEvent(Event.ON_CLEAR_OLD_CONTROLLER);
mController.setHierarchy(null);
}
//3. 为Controller从新设置Hierarchy)创建新的Controller。
mController = draweeController;
if (mController != null) {
mEventTracker.recordEvent(Event.ON_SET_CONTROLLER);
mController.setHierarchy(mHierarchy);
} else {
mEventTracker.recordEvent(Event.ON_CLEAR_CONTROLLER);
}
//4. 对DraweeHolder和Controller进行attach操做。
if (wasAttached) {
attachController();
}
}
}
复制代码
上述方法的流程也十分简单,以下所示:
上述流程里有两个关键的地方:设置Hierarchy和attch操做,咱们分别来看看,
从上面的序列图能够看出,这个mHierarchy是在GenricDraweeView的构造方法里调用inflateHierarchy()方法建立的,它其实是一个GenericDraweeHierarchy对象,而setHierarchy()方法 最终调用的是AbstractDraweeController的setHierarchy()方法,以下所示:
public abstract class AbstractDraweeController<T, INFO> implements DraweeController, DeferredReleaser.Releasable, GestureDetector.ClickListener {
public void setHierarchy(@Nullable DraweeHierarchy hierarchy) {
//... log
mEventTracker.recordEvent(
(hierarchy != null) ? Event.ON_SET_HIERARCHY : Event.ON_CLEAR_HIERARCHY);
//1. 释放掉当前正在进行的请求。
if (mIsRequestSubmitted) {
mDeferredReleaser.cancelDeferredRelease(this);
release();
}
//2. 清除已经存在的Hierarchy。
if (mSettableDraweeHierarchy != null) {
mSettableDraweeHierarchy.setControllerOverlay(null);
mSettableDraweeHierarchy = null;
}
//3. 设置新的Hierarchy。
if (hierarchy != null) {
Preconditions.checkArgument(hierarchy instanceof SettableDraweeHierarchy);
mSettableDraweeHierarchy = (SettableDraweeHierarchy) hierarchy;
mSettableDraweeHierarchy.setControllerOverlay(mControllerOverlay);
}
}
}
复制代码
这个mSettableDraweeHierarchy实际的实现类是GenericDraweeHierarchy,
走到这里,DraweeController与DraweeHierarchy的绑定流程就完成了。
👉 序列图 4.1 -> 4.14
这一块的内容主要执行上面建立的各类Producer,从从内存缓存/磁盘缓存/网络获取图片,并调用对应的Consumer消费结果,最终 不一样的Drawable设置到对应的图层中去,关于DraweeHierarchy与Producer咱们下面都会详细的讲,咱们先来看看上面层层请求到 图片最终是如何设置到SimpleDraweeView中去的,以下所示:
public class GenericDraweeHierarchy implements SettableDraweeHierarchy {
@Override
public void setImage(Drawable drawable, float progress, boolean immediate) {
drawable = WrappingUtils.maybeApplyLeafRounding(drawable, mRoundingParams, mResources);
drawable.mutate();
//mActualImageWrapper就是实际加载图片的那个图层,此处要设置的SimpleDraweeView最终要显示的图片。
mActualImageWrapper.setDrawable(drawable);
mFadeDrawable.beginBatchMode();
fadeOutBranches();
fadeInLayer(ACTUAL_IMAGE_INDEX);
setProgress(progress);
if (immediate) {
mFadeDrawable.finishTransitionImmediately();
}
mFadeDrawable.endBatchMode();
}
}
复制代码
mActualImageWrapper就是实际加载图片的那个图层,此处要设置的SimpleDraweeView最终要显示的图片。
如此,一个SimpleDraweeView的图片加载流程就完成了,面对如此长的流程,读者难免疑惑,咱们只要掌握了总体流程,就能够 分而治之,逐个击破。
Fresco的图片效果是依赖于Drawee实现的,也就是Drawable层级。
DraweeHierarchy是Fresco里的Drawable层级,它是一层一层叠加在DraweeView上的来实现各类效果,例如:占位图、失败图、加载进度图等,DraweeHierarchy是一个接口,它还有个 子接口SettableDraweeHierarchy,它们的实现类是GenericDraweeHierarchy。
DraweeHierarchy接口与SettableDraweeHierarchy接口以下所示:
public interface DraweeHierarchy {
//获取顶层的Drawable,也就是其父节点的图层
Drawable getTopLevelDrawable();
}
public interface SettableDraweeHierarchy extends DraweeHierarchy {
//由DraweeController调用,重置DraweeHierarchy状态
void reset();
//由DraweeController调用,设置图片数据,progress在渐进式JPEG里使用,immediate表示是否当即显示这张图片
void setImage(Drawable drawable, float progress, boolean immediate);
//由DraweeController调用,更新图片加载进度【0, 1】,progress为1或者immediate为true时的时候会隐藏进度条。
void setProgress(float progress, boolean immediate);
//由DraweeController调用,设置失败缘由,DraweeHierarchy能够根据不一样的缘由展现不一样的失败图片。
void setFailure(Throwable throwable);
//由DraweeController调用,设置重试缘由,DraweeHierarchy能够根据不一样的缘由展现不一样的重试图片。
void setRetry(Throwable throwable);
//由DraweeController调用,设置其余的Controller覆盖层
void setControllerOverlay(Drawable drawable);
}
复制代码
理解了DraweeHierarchy的大体接口,咱们继续从如下几个角度来解析DraweeHierarchy:
Fresco里定义了许多Drawable,它们都直接或者间接的继承了Drawable,来实现不一样的功能。它们的图层层级以下所示:
o RootDrawable (top level drawable)
|
+--o FadeDrawable
|
+--o ScaleTypeDrawable (placeholder branch, optional)
| |
| +--o Drawable (placeholder image)
|
+--o ScaleTypeDrawable (actual image branch)
| |
| +--o ForwardingDrawable (actual image wrapper)
| |
| +--o Drawable (actual image)
|
+--o null (progress bar branch, optional)
|
+--o Drawable (retry image branch, optional)
|
+--o ScaleTypeDrawable (failure image branch, optional)
|
+--o Drawable (failure image)
复制代码
Fresco里的Drawable子类有不少,按照功能划分能够分为三大类:
容器类Drawable
容器类Drawable
视图类Drawable
除了这些Drawable类觉得,还有一个Drawable接口,凡是作matrix变换和圆角处理的Drawable都实现了这个接口,这是为了子Drawable能够父Drawable的 变换矩阵和圆角节点,以即可以正确的绘制本身。
以下所示:
public interface TransformAwareDrawable {
//设置TransformCallback回调
void setTransformCallback(TransformCallback transformCallback);
}
public interface TransformCallback {
//获取应用在Drawable上的全部matrices矩阵,存储在transform中
void getTransform(Matrix transform);
//获取Drawable的根节点边界,存储在bounds中。
void getRootBounds(RectF bounds);
}
复制代码
从用户的角度,SimpleDraweeView上的图层主要被分红了如下几层:
理解了图层的层级构造,咱们接着来看看图层的建立流程。👇
咱们前面说过在GenericDraweeView的构造方法里,调用了它的inflateHierarchy()方法构建了一个GenericDraweeHierarchy对象,GenericDraweeHierarchy的实际 是由GenericDraweeHierarchyBuild调用build()方法来完成的。
GenericDraweeHierarchy是负责加载每一个图层信息的载体,咱们来看看它的构造方法的实现,以下所示:
public class GenericDraweeHierarchy implements SettableDraweeHierarchy {
//就跟咱们上面说的同样,7个图层。
//背景图层
private static final int BACKGROUND_IMAGE_INDEX = 0;
//占位图层
private static final int PLACEHOLDER_IMAGE_INDEX = 1;
//加载的图片图层
private static final int ACTUAL_IMAGE_INDEX = 2;
//进度条
private static final int PROGRESS_BAR_IMAGE_INDEX = 3;
//重试加载的图片
private static final int RETRY_IMAGE_INDEX = 4;
//失败图片
private static final int FAILURE_IMAGE_INDEX = 5;
//叠加图
private static final int OVERLAY_IMAGES_INDEX = 6;
GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) {
mResources = builder.getResources();
mRoundingParams = builder.getRoundingParams();
//实际加载图片的Drawable
mActualImageWrapper = new ForwardingDrawable(mEmptyActualImageDrawable);
int numOverlays = (builder.getOverlays() != null) ? builder.getOverlays().size() : 1;
numOverlays += (builder.getPressedStateOverlay() != null) ? 1 : 0;
//图层数量
int numLayers = OVERLAY_IMAGES_INDEX + numOverlays;
// array of layers
Drawable[] layers = new Drawable[numLayers];
//1. 构建背景图层Drawable。
layers[BACKGROUND_IMAGE_INDEX] = buildBranch(builder.getBackground(), null);
//2. 构建占位图层Drawable。
layers[PLACEHOLDER_IMAGE_INDEX] = buildBranch(
builder.getPlaceholderImage(),
builder.getPlaceholderImageScaleType());
//3. 构建加载的图片图层Drawable。
layers[ACTUAL_IMAGE_INDEX] = buildActualImageBranch(
mActualImageWrapper,
builder.getActualImageScaleType(),
builder.getActualImageFocusPoint(),
builder.getActualImageColorFilter());
//4. 构建进度条图层Drawable。
layers[PROGRESS_BAR_IMAGE_INDEX] = buildBranch(
builder.getProgressBarImage(),
builder.getProgressBarImageScaleType());
//5. 构建从新加载的图片图层Drawable。
layers[RETRY_IMAGE_INDEX] = buildBranch(
builder.getRetryImage(),
builder.getRetryImageScaleType());
//6. 构建失败图片图层Drawable。
layers[FAILURE_IMAGE_INDEX] = buildBranch(
builder.getFailureImage(),
builder.getFailureImageScaleType());
if (numOverlays > 0) {
int index = 0;
if (builder.getOverlays() != null) {
for (Drawable overlay : builder.getOverlays()) {
//7. 构建叠加图图层Drawable。
layers[OVERLAY_IMAGES_INDEX + index++] = buildBranch(overlay, null);
}
} else {
index = 1; // reserve space for one overlay
}
if (builder.getPressedStateOverlay() != null) {
layers[OVERLAY_IMAGES_INDEX + index] = buildBranch(builder.getPressedStateOverlay(), null);
}
}
// fade drawable composed of layers
mFadeDrawable = new FadeDrawable(layers);
mFadeDrawable.setTransitionDuration(builder.getFadeDuration());
// rounded corners drawable (optional)
Drawable maybeRoundedDrawable =
WrappingUtils.maybeWrapWithRoundedOverlayColor(mFadeDrawable, mRoundingParams);
// top-level drawable
mTopLevelDrawable = new RootDrawable(maybeRoundedDrawable);
mTopLevelDrawable.mutate();
resetFade();
}
}
复制代码
这个方法主要是构建各个图层的Drawable对象,以下所示:
而构建的方法设计到两个方法
public class GenericDraweeHierarchy implements SettableDraweeHierarchy {
@Nullable
private Drawable buildActualImageBranch( Drawable drawable, @Nullable ScaleType scaleType, @Nullable PointF focusPoint, @Nullable ColorFilter colorFilter) {
drawable.setColorFilter(colorFilter);
drawable = WrappingUtils.maybeWrapWithScaleType(drawable, scaleType, focusPoint);
return drawable;
}
/** Applies scale type and rounding (both if specified). */
@Nullable
private Drawable buildBranch(@Nullable Drawable drawable, @Nullable ScaleType scaleType) {
//若是须要为Drawable设置Round,RoundedBitmapDrawable或者RoundedColorDrawable。
drawable = WrappingUtils.maybeApplyLeafRounding(drawable, mRoundingParams, mResources);
//若是须要为Drawable设置ScaleType,则将它包装成一个ScaleTypeDrawable。
drawable = WrappingUtils.maybeWrapWithScaleType(drawable, scaleType);
return drawable;
}
}
复制代码
构建Drawable的过程当中都要应用相应的缩放类型和圆角角度,以下所示:
这样一个图层的载体GenericDraweeHierarchy就构建完成了,后续GenericDraweeHierarchy里的各类操做都是调用器内部的各类Drawable的方法来完成的。
咱们前面说过Producer是Fresco的最佳劳模,全部的脏话累活都是它干的,咱们来看看它的实现。
public interface Producer<T> {
//开始处理任务,执行的结果右Consumer进行消费。
void produceResults(Consumer<T> consumer, ProducerContext context);
}
复制代码
Fresco里实现了多个Producer,按照功能划分能够分为如下几类:
本地数据获取P类roducer,这类Producer负责从本地获取数据。
LocalFetchProducer:实现了Producer接口,全部本地数据Producer获取的基类。
LocalAssetFetchProducer 继承于LocalFetchProducer,经过AssetManager获取ImageRequest对象的输入流及对象字节码长度,将它转换为EncodedImage;
LocalContentUriFetchProducer 继承于LocalFetchProducer,若Uri指向联系人,则获取联系人头像;若指向相册图片,则会根据是否传入ResizeOption进行必定缩放(这里不是彻底按ResizeOption缩放);若止这两个条件都不知足,则直接调用ContentResolver的函数openInputStream(Uri uri)获取输入流并转化为EncodedImage;
LocalFileFetchProducer 继承于LocalFetchProducer,直接经过指定文件获取输入流,从而转化为EncodedImage;
LocalResourceFetchProducer 继承于LocalFetchProducer,经过Resources的函数openRawResources获取输入流,从而转化为EncodedImage。
LocalExifThumbnailProducer 没有继承于LocalFetchProducer,能够获取Exif图像的Producer;
LocalVideoThumbnailProducer 没有继承于LocalFetchProducer,能够获取视频缩略图的Producer。
网络数据获取类Producer,这类Producer负责从网络获取数据。
缓存数据获取类Producer,这类Producer负责从缓存中获取数据。
功能类Producer,这类Producer在初始化的时候会传入一个nextProducer,它们会对nextProducer产生的结果进行处理。
那么这些Producer是在哪里构建的呢?🤔
咱们前面说过,在构建DataSource的时候,会调用ProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);方法为指定的ImageRequest构建 想要的Producer序列,事实上,ProducerSequenceFactory里除了getDecodedImageProducerSequence()方法觉得,还有几个针对其余状况获取序列的方法,这里咱们 列一下从网络获取图片的时候Producer序列是什么样的。
以下所示:
咱们上面说道Producer产生的结果由Consumer来消费,那它又是如何建立的呢?🤔
Producer在处理数据时是向下传递的,而Consumer来接收结果时则是向上传递的,基本上Producer在接收上层传递的Consumer进行包装,咱们举个小例子。
在上面的流程分析中,咱们说过最终建立的DataSource是CloseableProducerToDataSourceAdapter,CloseableProducerToDataSourceAdapter的父类是AbstractProducerToDataSourceAdapter,在它的 构造方法中会调用createConsumer()来建立第一层Consumer,以下所示:
public abstract class AbstractProducerToDataSourceAdapter<T> extends AbstractDataSource<T> {
private Consumer<T> createConsumer() {
return new BaseConsumer<T>() {
@Override
protected void onNewResultImpl(@Nullable T newResult, @Status int status) {
AbstractProducerToDataSourceAdapter.this.onNewResultImpl(newResult, status);
}
@Override
protected void onFailureImpl(Throwable throwable) {
AbstractProducerToDataSourceAdapter.this.onFailureImpl(throwable);
}
@Override
protected void onCancellationImpl() {
AbstractProducerToDataSourceAdapter.this.onCancellationImpl();
}
@Override
protected void onProgressUpdateImpl(float progress) {
AbstractProducerToDataSourceAdapter.this.setProgress(progress);
}
};
}
}
复制代码
从上面列出的Producer序列能够看出,第一层Producer就是PostprocessedBitmapMemoryCacheProducer,在它的produceResults()方法中,会对上面传递下来的Consumer进行包装,以下所示:
public class PostprocessedBitmapMemoryCacheProducer implements Producer<CloseableReference<CloseableImage>> {
@Override
public void produceResults( final Consumer<CloseableReference<CloseableImage>> consumer, final ProducerContext producerContext) {
//...
final boolean isRepeatedProcessor = postprocessor instanceof RepeatedPostprocessor;
Consumer<CloseableReference<CloseableImage>> cachedConsumer = new CachedPostprocessorConsumer(
consumer,
cacheKey,
isRepeatedProcessor,
mMemoryCache);
listener.onProducerFinishWithSuccess(
requestId,
getProducerName(),
listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "false") : null);
mInputProducer.produceResults(cachedConsumer, producerContext);
//...
}
}
复制代码
当PostprocessedBitmapMemoryCacheProducer调用本身的produceResults()处理本身的任务时,会继续调用下一层的Producer,当全部的Producer都完成本身的工做 之后,结果就由下至上层层返回到最上层的Consumer回调中,最终将结果返回给调用者。
Fresco里的Producer是按照必定的顺序进行排列,一个执行完了,继续执行下一个。
以上即是Fresco里整个Producer/Consumer结构。
Fresco里有三级缓存,两级内存缓存,一级磁盘缓存,以下图所示:
👉 点击图片查看大图
磁盘缓存由于涉及到文件读写要比内存缓存复杂一些,从下至上能够将磁盘缓存分为三层:
咱们先来看看Fresco的缓存键值的设计,Fresco为缓存键设计了一个接口,以下所示:
public interface CacheKey {
String toString();
boolean equals(Object o);
int hashCode();
//是不是由Uri构建而来的
boolean containsUri(Uri uri);
//获取url string
String getUriString();
}
复制代码
CacheKey有两个实现类:
好,咱们继续来分析内存缓存和磁盘缓存的实现。
咱们前面说到,内存缓存分为两级:
它们的区别在于缓存的数据格式不一样,未编码图片内存缓存使用的是CloseableReference,已编码图片内存缓存使用的是CloseableReference,它们的区别在于资源 的测量和释放方式是不一样,它们使用VauleDescriptor来描述不一样资源的数据大小,使用不一样的ResourceReleaser来释放资源。
内部的数据结构使用的是CountingLruMap,咱们以前在文章07Android开源框架源码赏析:LruCache与DiskLruCache中 提到,LruCache与DiskLruCache都使用的是LinkedHashMap,Fresco没有直接使用LinkedHashMap,而是对它作了一层封装,这个就是CountingLruMap,它内部有一个双向链表,在查找的时候,能够从最 早插入的单位开始查询,这样就能够快速删除掉最先插入的数据,提升效率。
咱们接着来看内存缓存是如何实现的,内存缓存的实现源于一个共同的接口,以下所示:
public interface MemoryCache<K, V> {
//缓存指定的key-value对,该方法会返回一份新的缓存拷贝用来代码原有的引用,但不须要的时候须要关闭这个缓存引用
CloseableReference<V> cache(K key, CloseableReference<V> value);
//获取缓存
CloseableReference<V> get(K key);
//根据指定的key移除缓存
public int removeAll(Predicate<K> predicate);
//查询是否包含该key对应的缓存
public boolean contains(Predicate<K> predicate);
}
复制代码
和内存缓存相关的还有一个接口MemoryTrimmable,实现该接口,并将本身注册的MemoryTrimmableRegistry中,当内存变化时,能够 通知到本身,以下所示:
public interface MemoryTrimmable {
//内存发生变化
void trim(MemoryTrimType trimType);
}
复制代码
咱们来看看有哪些类直接或者间接实现了该缓存接口。
在CountingMemoryCache内部使用Entry对象来描述缓存对,它包含如下信息:
static class Entry<K, V> {
//缓存key
public final K key;
//缓存对象
public final CloseableReference<V> valueRef;
// The number of clients that reference the value.
//缓存的引用计数
public int clientCount;
//该Entry对象是否被其所描述的缓存所追踪
public boolean isOrphan;
//缓存状态监听器
@Nullable public final EntryStateObserver<K> observer;
}
复制代码
👉 注:只有引用数量(clientCount)为0,且没有被缓存追踪(isOrphan = true)时缓存对象才能够被释放。
咱们接着开看看CountingMemoryCache是如何插入、获取和删除缓存的。
首先咱们要了解缓存的操做涉及到两个集合:
//待移除缓存集合,这里面的缓存没有被外面使用
@VisibleForTesting
final CountingLruMap<K, Entry<K, V>> mExclusiveEntries;
//全部缓存的集合,包括待移除的缓存
@GuardedBy("this")
@VisibleForTesting
final CountingLruMap<K, Entry<K, V>> mCachedEntries;
复制代码
咱们接着来看插入缓存的实现。
public class CountingMemoryCache<K, V> implements MemoryCache<K, V>, MemoryTrimmable {
public CloseableReference<V> cache( final K key, final CloseableReference<V> valueRef, final EntryStateObserver<K> observer) {
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(valueRef);
//1. 检查是否须要更新缓存参数。
maybeUpdateCacheParams();
Entry<K, V> oldExclusive;
CloseableReference<V> oldRefToClose = null;
CloseableReference<V> clientRef = null;
synchronized (this) {
//2. 在缓存中查找要插入的对象,若存在则将其从待移除缓存集合移除,并调用它的close()方法
//当该缓存对象的引用数目为0的时候会释放掉该对象。
oldExclusive = mExclusiveEntries.remove(key);
Entry<K, V> oldEntry = mCachedEntries.remove(key);
if (oldEntry != null) {
makeOrphan(oldEntry);
oldRefToClose = referenceToClose(oldEntry);
}
//3. 检查是否缓存对象达到最大显示或者缓存池已满,若是都为否,则插入新缓存对象。
if (canCacheNewValue(valueRef.get())) {
Entry<K, V> newEntry = Entry.of(key, valueRef, observer);
mCachedEntries.put(key, newEntry);
//4. 将插入的对象包装成一个CloseableReference,从新包装对象主要是为了重设
//一下ResourceReleaser,它会在释放资源的时候减小Entry的clientCount,并将该缓存对象
// 加入到mExclusiveEntries中,mExclusiveEntries里存放的是已经被使用过的缓存(等待被释放),
// 若是缓存对象能够释放,则直接释放缓存对象。
clientRef = newClientReference(newEntry);
}
}
CloseableReference.closeSafely(oldRefToClose);
maybeNotifyExclusiveEntryRemoval(oldExclusive);
//5. 判断是否须要释放资源,当超过了EvictEntries最大容量或者缓存池已满,则移除EvictEntries最先插入的对象。
maybeEvictEntries();
return clientRef;
}
}
复制代码
插入缓存主要作了如下几件事情:
public class CountingMemoryCache<K, V> implements MemoryCache<K, V>, MemoryTrimmable {
@Nullable
public CloseableReference<V> get(final K key) {
Preconditions.checkNotNull(key);
Entry<K, V> oldExclusive;
CloseableReference<V> clientRef = null;
synchronized (this) {
//1. 查询该缓存,说明该缓存可能要被使用,则尝试将其从待移除缓存集合移除。
oldExclusive = mExclusiveEntries.remove(key);
//2. 从缓存集合中查询该缓存。
Entry<K, V> entry = mCachedEntries.get(key);
if (entry != null) {
//3. 若是查询到该缓存,将该缓存对象包装成一个CloseableReference,从新包装对象主要是为了重设
//一下ResourceReleaser,它会在释放资源的时候减小Entry的clientCount,并将该缓存对象
// 加入到mExclusiveEntries中,mExclusiveEntries里存放的是已经被使用过的缓存(等待被释放),
// 若是缓存对象能够释放,则直接释放缓存对象。
clientRef = newClientReference(entry);
}
}
//4. 判断是否须要通知待删除集合里的元素被移除了。
maybeNotifyExclusiveEntryRemoval(oldExclusive);
//5. 判断是否须要更新缓存参数。
maybeUpdateCacheParams();
//6. 判断是否须要释放资源,当超过了EvictEntries最大容量或者缓存池已满,则移除EvictEntries最先插入的对象。
maybeEvictEntries();
return clientRef;
}
}
复制代码
获取缓存主要执行了如下操做:
移除缓存就是调用集合的removeAll()方法移除全部的元素,以下所示:
public class CountingMemoryCache<K, V> implements MemoryCache<K, V>, MemoryTrimmable {
public int removeAll(Predicate<K> predicate) {
ArrayList<Entry<K, V>> oldExclusives;
ArrayList<Entry<K, V>> oldEntries;
synchronized (this) {
oldExclusives = mExclusiveEntries.removeAll(predicate);
oldEntries = mCachedEntries.removeAll(predicate);
makeOrphans(oldEntries);
}
maybeClose(oldEntries);
maybeNotifyExclusiveEntryRemoval(oldExclusives);
maybeUpdateCacheParams();
maybeEvictEntries();
return oldEntries.size();
}
}
复制代码
这个方法比较简单,咱们重点关注的是一个屡次出现的方法:maybeEvictEntries(),它是用来调节总缓存的大小的,保证缓存不超过最大缓存个数和最大容量,以下所示:
public class CountingMemoryCache<K, V> implements MemoryCache<K, V>, MemoryTrimmable {
private void maybeEvictEntries() {
ArrayList<Entry<K, V>> oldEntries;
synchronized (this) {
int maxCount = Math.min(
//待移除集合最大持有的缓存个数
mMemoryCacheParams.maxEvictionQueueEntries,
//缓存集合最大持有的缓存个数 - 当前正在使用的缓存个数
mMemoryCacheParams.maxCacheEntries - getInUseCount());
int maxSize = Math.min(
//待移除集合最大持有的缓存容量
mMemoryCacheParams.maxEvictionQueueSize,
//缓存集合最大持有的缓存容量 - 当前正在使用的缓存容量
mMemoryCacheParams.maxCacheSize - getInUseSizeInBytes());
//1. 根据maxCount和maxSize,不断的从mExclusiveEntries移除队头的元素,知道知足缓存限制规则。
oldEntries = trimExclusivelyOwnedEntries(maxCount, maxSize);
//2. 将缓存Entry的isOrphan置为true,表示该Entry对象再也不被追踪,等待被删除。
makeOrphans(oldEntries);
}
//3. 关闭缓存。
maybeClose(oldEntries);
//4. 通知缓存被关闭。
maybeNotifyExclusiveEntryRemoval(oldEntries);
}
}
复制代码
整个调整容量的流程就是根据当前缓存的个数和容量进行调整直到知足最大缓存个数和最大缓存容量的限制,以下所示:
以上就是内存缓存的所有内容,咱们接着来看磁盘缓存的实现。👇
咱们前面已经说过,磁盘缓存也分为三层,咱们再来回顾一下,以下图所示:
👉 点击图片查看大图
磁盘缓存由于涉及到文件读写要比内存缓存复杂一些,从下至上能够将磁盘缓存分为三层:
咱们来看看相关的接口。
磁盘缓存的接口是FileCache,以下所示:
public interface FileCache extends DiskTrimmable {
//是否能够进行磁盘缓存,主要是本地存储是否存在以及是否能够读写。
boolean isEnabled();
//返回缓存的二进制资源
BinaryResource getResource(CacheKey key);
//是否包含该缓存key,异步调用。
boolean hasKeySync(CacheKey key);
//是否包含该缓存key,同步调用。
boolean hasKey(CacheKey key);
boolean probe(CacheKey key);
//插入缓存
BinaryResource insert(CacheKey key, WriterCallback writer) throws IOException;
//移除缓存
void remove(CacheKey key);
//获取缓存总大小
long getSize();
//获取缓存个数
long getCount();
//清除过时的缓存
long clearOldEntries(long cacheExpirationMs);
//清除全部缓存
void clearAll();
//获取磁盘dump信息
DiskStorage.DiskDumpInfo getDumpInfo() throws IOException;
}
复制代码
能够发现FileCahce接口继承于DisTrimmable,它是一个用来监听磁盘容量变化的接口,以下所示:
public interface DiskTrimmable {
//当磁盘只有不多的空间可使用的时候回调。
void trimToMinimum();
//当磁盘没有空间可使用的时候回调
void trimToNothing();
}
复制代码
除了缓存接口DiskStorageCache,Fresco还定义了DiskStorage接口来封装文件IO的读写逻辑,以下所示:
public interface DiskStorage {
class DiskDumpInfoEntry {
public final String path;
public final String type;
public final float size;
public final String firstBits;
protected DiskDumpInfoEntry(String path, String type, float size, String firstBits) {
this.path = path;
this.type = type;
this.size = size;
this.firstBits = firstBits;
}
}
class DiskDumpInfo {
public List<DiskDumpInfoEntry> entries;
public Map<String, Integer> typeCounts;
public DiskDumpInfo() {
entries = new ArrayList<>();
typeCounts = new HashMap<>();
}
}
//文件存储是否可用
boolean isEnabled();
//是否包含外部存储
boolean isExternal();
//获取文件描述符指向的文件
BinaryResource getResource(String resourceId, Object debugInfo) throws IOException;
//检查是否包含文件描述符所指的文件
boolean contains(String resourceId, Object debugInfo) throws IOException;
//检查resourceId对应的文件是否存在,若是存在则更新上次读取的时间戳。
boolean touch(String resourceId, Object debugInfo) throws IOException;
void purgeUnexpectedResources();
//插入
Inserter insert(String resourceId, Object debugInfo) throws IOException;
//获取磁盘缓存里全部的Entry。
Collection<Entry> getEntries() throws IOException;
//移除指定的缓存Entry。
long remove(Entry entry) throws IOException;
//根据resourceId移除对应的磁盘缓存文件
long remove(String resourceId) throws IOException;
//清除全部缓存文件
void clearAll() throws IOException;
DiskDumpInfo getDumpInfo() throws IOException;
//获取存储名
String getStorageName();
interface Entry {
//ID
String getId();
//时间戳
long getTimestamp();
//大小
long getSize();
//Fresco使用BinaryResource对象来描述磁盘缓存对象,经过该对象能够获取文件的输入流、字节码等信息。
BinaryResource getResource();
}
interface Inserter {
//写入数据
void writeData(WriterCallback callback, Object debugInfo) throws IOException;
//提交写入的数据
BinaryResource commit(Object debugInfo) throws IOException;
//取消这次插入操做
boolean cleanUp();
}
}
复制代码
理解了主要接口的功能咱们就看看看主要的实现类:
BufferedDiskCache主要提供了三个方面的功能:
咱们来看看它们的实现细节。
上面DiskStorage里定义了个接口Entry来描述磁盘缓存对象的信息,真正持有缓存对象的是BinaryResource接口,它的实现类是FileBinaryResource,该类主要定义了 File的一些操做,能够经过它获取文件的输入流和字节码等。
此外,Fresco定义了每一个文件的惟一描述符,此描述符由CacheKey的toString()方法导出字符串的SHA-1哈希码,而后该哈希码再通过Base64加密得出。
咱们来看看磁盘缓存的插入、查找和删除的实现。
public class DiskStorageCache implements FileCache, DiskTrimmable {
@Override
public BinaryResource insert(CacheKey key, WriterCallback callback) throws IOException {
//1. 先将磁盘缓存写入到缓存文件,这能够提供写缓存的并发速度。
SettableCacheEvent cacheEvent = SettableCacheEvent.obtain()
.setCacheKey(key);
mCacheEventListener.onWriteAttempt(cacheEvent);
String resourceId;
synchronized (mLock) {
//2. 获取缓存的resoucesId。
resourceId = CacheKeyUtil.getFirstResourceId(key);
}
cacheEvent.setResourceId(resourceId);
try {
//3. 建立要插入的文件(同步操做),这里构建了Inserter对象,该对象封装了具体的写入流程。
DiskStorage.Inserter inserter = startInsert(resourceId, key);
try {
inserter.writeData(callback, key);
//4. 提交新建立的缓存文件到缓存中。
BinaryResource resource = endInsert(inserter, key, resourceId);
cacheEvent.setItemSize(resource.size())
.setCacheSize(mCacheStats.getSize());
mCacheEventListener.onWriteSuccess(cacheEvent);
return resource;
} finally {
if (!inserter.cleanUp()) {
FLog.e(TAG, "Failed to delete temp file");
}
}
} catch (IOException ioe) {
//... 异常处理
} finally {
cacheEvent.recycle();
}
}
}
复制代码
整个插入缓存的流程以下所示:
咱们重点来看看这两个方法startInsert()与endInsert()。
public class DiskStorageCache implements FileCache, DiskTrimmable {
//建立一个临时文件,后缀为.tmp
private DiskStorage.Inserter startInsert( final String resourceId, final CacheKey key) throws IOException {
maybeEvictFilesInCacheDir();
//调用DefaultDiskStorage的insert()方法建立一个临时文件
return mStorage.insert(resourceId, key);
}
//将缓存文件提交到缓存中,如何缓存文件已经存在则尝试删除原来的文件
private BinaryResource endInsert( final DiskStorage.Inserter inserter, final CacheKey key, String resourceId) throws IOException {
synchronized (mLock) {
BinaryResource resource = inserter.commit(key);
//将resourceId添加点resourceId集合中,DiskStorageCache里只维护了这一个集合
//来记录缓存
mResourceIndex.add(resourceId);
mCacheStats.increment(resource.size(), 1);
return resource;
}
}
}
复制代码
DiskStorageCache里只维护了这一个集合Set mResourceIndex来记录缓存的Resource ID,而DefaultDiskStorage负责对磁盘上 的缓存就行管理,体为DiskStorageCache提供索引功能。
咱们接着来看看查找缓存的实现。
根据CacheKey查找缓存BinaryResource,若是缓存以及存在,则更新它的LRU访问时间戳,若是缓存不存在,则返回空。
public class DiskStorageCache implements FileCache, DiskTrimmable {
@Override
public BinaryResource getResource(final CacheKey key) {
String resourceId = null;
SettableCacheEvent cacheEvent = SettableCacheEvent.obtain()
.setCacheKey(key);
try {
synchronized (mLock) {
BinaryResource resource = null;
//1. 获取缓存的ResourceId,这里是一个列表,由于可能存在MultiCacheKey,它wrap多个CacheKey。
List<String> resourceIds = CacheKeyUtil.getResourceIds(key);
for (int i = 0; i < resourceIds.size(); i++) {
resourceId = resourceIds.get(i);
cacheEvent.setResourceId(resourceId);
//2. 获取ResourceId对应的BinaryResource。
resource = mStorage.getResource(resourceId, key);
if (resource != null) {
break;
}
}
if (resource == null) {
//3. 缓存没有命中,则执行onMiss()回调,并将resourceId从mResourceIndex移除。
mCacheEventListener.onMiss(cacheEvent);
mResourceIndex.remove(resourceId);
} else {
//4. 缓存命中,则执行onHit()回调,并将resourceId添加到mResourceIndex。
mCacheEventListener.onHit(cacheEvent);
mResourceIndex.add(resourceId);
}
return resource;
}
} catch (IOException ioe) {
//... 异常处理
return null;
} finally {
cacheEvent.recycle();
}
}
}
复制代码
整个查找的流程以下所示:
这里会调用DefaultDiskStorage的getReSource()方法去查询缓存文件的路径并构建一个BinaryResource对象。
Fresco在本地保存缓存文件的路径以下所示:
parentPath + File.separator + resourceId + type;
复制代码
parentPath是根目录,type分为两种:
以上就是查询缓存的逻辑,咱们接着来看看删除缓存的逻辑。
public class DiskStorageCache implements FileCache, DiskTrimmable {
@Override
public void remove(CacheKey key) {
synchronized (mLock) {
try {
String resourceId = null;
//获取Resoucesid,根据resouceId移除缓存,并将本身从mResourceIndex移除。
List<String> resourceIds = CacheKeyUtil.getResourceIds(key);
for (int i = 0; i < resourceIds.size(); i++) {
resourceId = resourceIds.get(i);
mStorage.remove(resourceId);
mResourceIndex.remove(resourceId);
}
} catch (IOException e) {
//...移除处理
}
}
}
}
复制代码
删除缓存的逻辑也很简单,获取Resoucesid,根据resouceId移除缓存,并将本身从mResourceIndex移除。
磁盘缓存也会本身调节本身的缓存大小来知足缓存最大容量限制条件,咱们也来简单看一看。
Fresco里的磁盘缓存过载时,会以不超过缓存容量的90%为目标进行清理,具体清理流程以下所示:
public class DiskStorageCache implements FileCache, DiskTrimmable {
@GuardedBy("mLock")
private void evictAboveSize( long desiredSize, CacheEventListener.EvictionReason reason) throws IOException {
Collection<DiskStorage.Entry> entries;
try {
//1. 获取缓存目录下全部文件的Entry的集合,以最近被访问的时间为序,最近被访问的Entry放在后面。
entries = getSortedEntries(mStorage.getEntries());
} catch (IOException ioe) {
//... 捕获异常
}
//要删除的数据量
long cacheSizeBeforeClearance = mCacheStats.getSize();
long deleteSize = cacheSizeBeforeClearance - desiredSize;
//记录删除数据数量
int itemCount = 0;
//记录删除数据大小
long sumItemSizes = 0L;
//2. 循环遍历,从头部开始删除元素,直到剩余容量达到desiredSize位置。
for (DiskStorage.Entry entry: entries) {
if (sumItemSizes > (deleteSize)) {
break;
}
long deletedSize = mStorage.remove(entry);
mResourceIndex.remove(entry.getId());
if (deletedSize > 0) {
itemCount++;
sumItemSizes += deletedSize;
SettableCacheEvent cacheEvent = SettableCacheEvent.obtain()
.setResourceId(entry.getId())
.setEvictionReason(reason)
.setItemSize(deletedSize)
.setCacheSize(cacheSizeBeforeClearance - sumItemSizes)
.setCacheLimit(desiredSize);
mCacheEventListener.onEviction(cacheEvent);
cacheEvent.recycle();
}
}
//3. 更新容量,删除不须要的临时文件。
mCacheStats.increment(-sumItemSizes, -itemCount);
mStorage.purgeUnexpectedResources();
}
}
复制代码
整个清理流程能够分为如下几步:
关于Fresco的源码分析就到这里了,原本还想再讲一讲Fresco内存管理方面的知识,可是这牵扯到Java Heap以及Android匿名共享内存方面的知识,相对比较深刻,因此 等着后续分析《Android内存管理框架》的时候结合着一块讲。