Facebook的Fresco图片框架性能优化最为出名,代码量也最大,拆出来的jar包也多,最基础的功能(gradle引入compile 'com.facebook.fresco:fresco:1.1.0')就包括drawee、fbcore、fresco、imagepipeline、imagepipeline-base5个jar包。
但我们看这5个jar包,其实功能界限很清晰:
drawee提供了功能丰富的图片控件;
fbcore和fresco两个提供了基础功能(各种通用基础工具类)和Fresco初始化功能;
imagepipeline的两个包提供了获取、加载、缓存和解码等功能。
所以如果不从jar包出发,而是从功能出发的话,我们就可以考虑从初始化、管理和控件这三个角度,来看待Fresco的整体结构:
一、整体结构
1.1 Fresco初始化
主要就是Fresco这个类的初始化操作,使用Fresco必须先执行初始化函数:
//Fresco源码
if (imagePipelineConfig == null) {
ImagePipelineFactory.initialize(context);
} else {
ImagePipelineFactory.initialize(imagePipelineConfig);
}
initializeDrawee(context, draweeConfig);
我们看到,其实就是ImagePipeline和Drawee的初始化,这两者都可以输入Config对象,对初始化过程进行配置。
由于可配置参数非常复杂,Fresco都是采用了工厂+建造者的模式实现的这部分功能,逻辑划分地比较清晰。
ImagePipelineConfig
ImagePipelineConfig主要用来配置如何获取图像对象。
Fresco使用ImagePipelineFactory生产ImagePipeline,而创建ImagePipelineFactory时可以使用自定义的ImagePipelineConfig对象,这个Config对象定义了图片下载、管理和获取的各种策略,如访问网络的NetworkFetcher,缓冲池PoolFactory等。
DraweeConfig
DraweeConfig主要用来配置如何生成DraweeController。
Fresco自己定义了一套功能强大的图片控件DraweeView,这个控件的主要实例是SimpleDraweeView,Drawee的初始化就是SimpleDraweeView的初始化。
但是Drawee是一个MVC格式,SimpleDraweeView是View,必须有对应的Controller,这个Controller需要使用PipelineDraweeControllerFactory工厂类来生产,DraweeConfig就定义了这个工厂。
通过这两个Config对象,Fresco完成图片管理模块和图片显示模块的初始化,为后续使用做好准备。
1.2 图片管理
图片对象在Android系统中始终是个重点问题,占用内存偏大,IO时间较长,解码耗费CPU等问题始终在困扰Android的App开发。
一般来说,解决思路就是缩小尺寸、多做缓存、少做解码等。
具体来说,Fresco用ImagePipeline就是负责图片下载、解码、缓存与回收等功能,实现了四级图片管理:
已解码图片内存--未解码图片内存--本地图片文件--在线图片。
其中,前三级都是缓存,而且有两级内存缓存,其中第一级已解码图片的内存缓存是比较特别的设计,采用已解码图片缓存,可以减少重复decode对CPU的销毁,避免因反复解码造成UI卡顿等现象。
通过缓存机制,可以缓解内存和IO问题,已解码图片的缓存可以缓解CPU问题。
1.3 图片控件
在App中,成熟的图片显示功能其实很复杂,比如,考虑到在线图片的不可靠性,需要显示错误图片和加载过程的中间图片;可能需要淡入淡出效果;可能需要剪裁画面为圆角图片等;可能需要显示动态图片等。
另外,图片控件也需要交互,需要响应onTouchEvent事件。
Fresco用DraweeView统一实现了这些功能,它提供6个图层,而且做了一定的优化,基本满足显示需要,同时也可以响应事件。
我们在初始化时操作的Drawee就是负责图片的绘制与显示。
二、初始化
Fresco的初始化其实就是ImagePipeline图片管理模块的初始化,以及Drawee图片控件的初始化:
public static void initialize(
Context context,
@Nullable ImagePipelineConfig imagePipelineConfig,
@Nullable DraweeConfig draweeConfig) {
...
if (imagePipelineConfig == null) {
ImagePipelineFactory.initialize(context);
} else {
ImagePipelineFactory.initialize(imagePipelineConfig);
}
initializeDrawee(context, draweeConfig);
...
}
问题在于,如何平衡高内聚的功能,与低耦合的可扩展性,Fresco的做法类似于模板方法,在抽象类中定义主要的功能逻辑,同时利用Config去来设置或调整一些细节的实现。
2.1 ImagePipeline的初始化
Fresco中获取ImagePipeline是通过Factory获取的:
//Fresco源码
public static ImagePipeline getImagePipeline() {
return getImagePipelineFactory().getImagePipeline();//通过Factory获取
}
所以ImagePipeline的初始化就是Factory的初始化:
//ImagePipelineFactory源码
public static void initialize(ImagePipelineConfig imagePipelineConfig) {
sInstance = new ImagePipelineFactory(imagePipelineConfig);
}
而Factory生产功能对象时,都需要Config的协助,例如在生产文件缓存时:
//ImagePipelineFactory源码
public FileCache getMainFileCache() {
if (mMainFileCache == null) {
DiskCacheConfig diskCacheConfig = mConfig.getMainDiskCacheConfig();
mMainFileCache = mConfig.getFileCacheFactory().get(diskCacheConfig);
}
return mMainFileCache;
}
所以,ImagePipeline的生产,核心在于开发者自己配置的ImagePipelineConfig,其中定义了可以影响生产的各类参数对象:
public class ImagePipelineConfig {
// There are a lot of parameters in this class. Please follow strict alphabetical order.
// 有趣的是,注释要求按字母顺序排列参数,然鹅,源码并没有做到 : )
@Nullable private final AnimatedImageFactory mAnimatedImageFactory;
private final Bitmap.Config mBitmapConfig;
//未解码缓存的参数
private final Supplier<MemoryCacheParams> mBitmapMemoryCacheParamsSupplier;
//未解码缓存工厂
private final CacheKeyFactory mCacheKeyFactory;
private final Context mContext;
private final boolean mDownsampleEnabled;
//文件缓存工厂
private final FileCacheFactory mFileCacheFactory;
//已解码缓存的参数
private final Supplier<MemoryCacheParams> mEncodedMemoryCacheParamsSupplier;
private final ExecutorSupplier mExecutorSupplier;
private final ImageCacheStatsTracker mImageCacheStatsTracker;
//解码器
@Nullable private final ImageDecoder mImageDecoder;
private final Supplier<Boolean> mIsPrefetchEnabledSupplier;
//磁盘缓存配置
private final DiskCacheConfig mMainDiskCacheConfig;
private final MemoryTrimmableRegistry mMemoryTrimmableRegistry;
//在线数据获取器
private final NetworkFetcher mNetworkFetcher;
@Nullable private final PlatformBitmapFactory mPlatformBitmapFactory;
private final PoolFactory mPoolFactory;
//渐进加载配置
private final ProgressiveJpegConfig mProgressiveJpegConfig;
//请求监听者
private final Set<RequestListener> mRequestListeners;
private final boolean mResizeAndRotateEnabledForNetwork;
//缩略图磁盘缓存配置
private final DiskCacheConfig mSmallImageDiskCacheConfig;
//解码器配置
@Nullable private final ImageDecoderConfig mImageDecoderConfig;
private final ImagePipelineExperiments mImagePipelineExperiments;
这些配置参数,为ImagePipelineFactory里的众多工厂提供参数。
整体来说,ImagePipeline的生产过程如下:
这样设计下,ImagePipeline所需的各种缓存、网络、磁盘等都集合在Factory中生产,同时通过Config保留了一定的可扩展性。
2.2 DraweeView的初始化
Fresco在初始化时,会对Drawee进行初始化:
//Fresco源码
private static void initializeDrawee(
Context context,
@Nullable DraweeConfig draweeConfig) {
sDraweeControllerBuilderSupplier =
new PipelineDraweeControllerBuilderSupplier(context, draweeConfig);
SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}
可见,分成了两步,先初始化ControllerBuilderSupplier,再根据这个Supplier去初始化DraweeView控件。
其实,第二步相对简单,因为Fresco的Drawee控件采用了MVC模式,初始化控件时,必须有Controller,需要依次调用Supplier-->Builder-->Controller,这样,在为控件设置url时,才能开始工作:
//SimpleDraweeView源码
public void setImageURI(Uri uri, @Nullable Object callerContext) {
DraweeController controller = mSimpleDraweeControllerBuilder
.setCallerContext(callerContext)
.setUri(uri)
.setOldController(getController())
.build();
setController(controller);//在setController函数中,会获取Drawable对象,绘制到控件上。
}
所以有Supplier后,初始化View比较容易。
可见,Builder是利用Config的参数,生产出Controller对象的,需要注意的是,提供Builder的Supplier,通过ImagePipelineFactory生产并引用了ImagePipeline的实例对象:
//PipelineDraweeControllerBuilderSupplier源码
public PipelineDraweeControllerBuilderSupplier(
Context context,
ImagePipelineFactory imagePipelineFactory,
Set<ControllerListener> boundControllerListeners,
@Nullable DraweeConfig draweeConfig) {
mContext = context;
mImagePipeline = imagePipelineFactory.getImagePipeline();
...
这样,图像控件DraweeView就可以通过Controller与图像管理ImagePipeline关联起来了。
三、图片管理
图片管理其实包括图片的下载、缓存、解码等功能,OkHttp使用了Intercepter分层的设计来解决复杂问题,Fresco也采用了分层思想,不过层次不像OkHttp那样明显,它使用的是分阶段的设计,每个阶段返回一个Producer。
所以Fresco中的核心设计就是Producer和Consumer,用Producer来层层处理,每层的处理结果通过Consumer向上传递。
另外,Fresco还是用Cache和Pool来缓存数据,用Decoder解码器来优化各Android版本的解码功能。
3.1 分层设计
Fresco的分层设计,从功能上,包括这样几步:
1.从已编码缓存中获取(Bitmap类型),如果是从已编码缓存中获取数据,Fresco直接在UI线程中完成处理。
2.从未编码缓存中获取(EncodedImage类型)
3.从磁盘缓存中获取
4.从在线网络中获取
如果要做好分层处理,每层返回数据的逻辑和格式应该是类似的,这样才能实现递归处理,Fresco源码中,从网络获取数据的过程就是建立了一个执行链的过程,每层函数返回的都是Producer<T>,所以这些Producer函数的逻辑就是链式调用的逻辑:
//ProducerSequenceFactory源码
private synchronized Producer<CloseableReference<CloseableImage>> getNetworkFetchSequence() {
if (mNetworkFetchSequence == null) {
mNetworkFetchSequence =
newBitmapCacheGetToDecodeSequence(getCommonNetworkFetchToEncodedMemorySequence());//缓存图片解码,其实前后有两个分支
}
return mNetworkFetchSequence;
}
//分支1-缓存图片解码
private Producer<CloseableReference<CloseableImage>> newBitmapCacheGetToDecodeSequence(
Producer<EncodedImage> inputProducer) {
DecodeProducer decodeProducer = mProducerFactory.newDecodeProducer(inputProducer);//图片解码工具
return newBitmapCacheGetToBitmapCacheSequence(decodeProducer);
}
//分支1-获取缓存图片
private Producer<CloseableReference<CloseableImage>> newBitmapCacheGetToBitmapCacheSequence(
Producer<CloseableReference<CloseableImage>> inputProducer) {
BitmapMemoryCacheProducer bitmapMemoryCacheProducer =
mProducerFactory.newBitmapMemoryCacheProducer(inputProducer);//从Bitmap复制缓存
BitmapMemoryCacheKeyMultiplexProducer bitmapKeyMultiplexProducer =
mProducerFactory.newBitmapMemoryCacheKeyMultiplexProducer(bitmapMemoryCacheProducer);//合并请求,避免产生多张图片
ThreadHandoffProducer<CloseableReference<CloseableImage>> threadHandoffProducer =
mProducerFactory.newBackgroundThreadHandoffProducer(//线程执行
bitmapKeyMultiplexProducer,
mThreadHandoffProducerQueue);
return mProducerFactory.newBitmapMemoryCacheGetProducer(threadHandoffProducer);//已解码图片缓存
}
//分支2-在线获取未解码图片
private synchronized Producer<EncodedImage> getCommonNetworkFetchToEncodedMemorySequence() {
if (mCommonNetworkFetchToEncodedMemorySequence == null) {
Producer<EncodedImage> inputProducer =
newEncodedCacheMultiplexToTranscodeSequence(//在线请求数据
mProducerFactory.newNetworkFetchProducer(mNetworkFetcher));
mCommonNetworkFetchToEncodedMemorySequence =
ProducerFactory.newAddImageTransformMetaDataProducer(inputProducer);//合并数据
mCommonNetworkFetchToEncodedMemorySequence =
mProducerFactory.newResizeAndRotateProducer(//大小/旋转控制
mCommonNetworkFetchToEncodedMemorySequence,
mResizeAndRotateEnabledForNetwork,
mUseDownsamplingRatio);
}
return mCommonNetworkFetchToEncodedMemorySequence;
}
//分支2-访问网络
private Producer<EncodedImage> newEncodedCacheMultiplexToTranscodeSequence(
Producer<EncodedImage> inputProducer) {
if (WebpSupportStatus.sIsWebpSupportRequired &&
(!mWebpSupportEnabled || WebpSupportStatus.sWebpBitmapFactory == null)) {
inputProducer = mProducerFactory.newWebpTranscodeProducer(inputProducer);//图片转码
}
inputProducer = newDiskCacheSequence(inputProducer);//磁盘缓存
EncodedMemoryCacheProducer encodedMemoryCacheProducer =
mProducerFactory.newEncodedMemoryCacheProducer(inputProducer);//未解码图片缓存
return mProducerFactory.newEncodedCacheKeyMultiplexProducer(encodedMemoryCacheProducer);//合并请求
}
因为层层return函数的设计,最先执行的其实是最内层的return,所以这个调用链的调用顺序和书写顺序正好是反的,就分支1而言,最先执行的是最里面的:
return mProducerFactory.newBitmapMemoryCacheGetProducer(threadHandoffProducer);//已解码图片缓存
只有当不存在已解码图片缓存时,才会去执行参数threadHandoffProducer所定义的子线程获取图片数据(所以说,Fresco获取已解码图片缓存是在UI线程操作的)。
以上就是Producer链的基本结构和原理,源代码比较多,但是设计其实很精炼。
3.2 Producer-Consumer功能结构
Producer链的设计虽然能实现链式处理,但只是实现了链式的调用,缺少一个反馈数据的机制,Fresco为此设计了Consumer,Producer负责链式传递调用,Consumer负责在执行成功或失败后,予以反馈,这就是Fresco中的Producer-Consumer结构。
例如,在获取未解码图片缓存的BitmapMemoryCacheProducer中,定义了这样的Consumer:
protected Consumer<CloseableReference<CloseableImage>> wrapConsumer(
final Consumer<CloseableReference<CloseableImage>> consumer,
final CacheKey cacheKey) {
return new DelegatingConsumer<
CloseableReference<CloseableImage>,
CloseableReference<CloseableImage>>(consumer) {
@Override
public void onNewResultImpl(CloseableReference<CloseableImage> newResult, boolean isLast) {
...
// cache and forward the new result
CloseableReference<CloseableImage> newCachedResult =
mMemoryCache.cache(cacheKey, newResult);
...
}
这个Consumer就可以在Producer执行过程中记录缓存数据。
有些时候,还会添加一些Listener,例如在AbstractProducerToDataSourceAdapter中定义的:
//AbstractProducerToDataSourceAdapter源码
private Consumer<T> createConsumer() {
return new BaseConsumer<T>() {
@Override
protected void onNewResultImpl(@Nullable T newResult, boolean isLast) {
AbstractProducerToDataSourceAdapter.this.onNewResultImpl(newResult, isLast);
}
@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);
}
};
}
//调用Listener处理
protected void onNewResultImpl(@Nullable T result, boolean isLast) {
if (super.setResult(result, isLast)) {
if (isLast) {
//Listener是在ImagePipeline中定义并传入Adapter的
mRequestListener.onRequestSuccess(
mSettableProducerContext.getImageRequest(),
mSettableProducerContext.getId(),
mSettableProducerContext.isPrefetch());
}
}
}
在具体实现中,Fresco还增加了Listener这样的角色,在Producer--Consumer体系之外,还可以通知订阅者相关的事件,实际上是一个Producer--Consumer+Listener的结构,这样扩展起来更加灵活。
3.3 Producer的生产
Producer是在ImagePipeline中发起生产的:
public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(
ImageRequest imageRequest,
Object callerContext,
ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit) {
...
Producer<CloseableReference<CloseableImage>> producerSequence =
mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);
...
}
在ProducerSequenceFactory中,可以看到,Factory会分情况创建多中FetchSequence:
//ProducerSequenceFactory源码
private Producer<CloseableReference<CloseableImage>> getBasicDecodedImageSequence(
ImageRequest imageRequest) {
Preconditions.checkNotNull(imageRequest);
Uri uri = imageRequest.getSourceUri();
Preconditions.checkNotNull(uri, "Uri is null.");
if (UriUtil.isNetworkUri(uri)) {
return getNetworkFetchSequence();
} else if (UriUtil.isLocalFileUri(uri)) {
if (MediaUtils.isVideo(MediaUtils.extractMime(uri.getPath()))) {
return getLocalVideoFileFetchSequence();
} else {
return getLocalImageFileFetchSequence();
}
} else if (UriUtil.isLocalContentUri(uri)) {
return getLocalContentUriFetchSequence();
} else if (UriUtil.isLocalAssetUri(uri)) {
return getLocalAssetFetchSequence();
} else if (UriUtil.isLocalResourceUri(uri)) {
return getLocalResourceFetchSequence();
} else if (UriUtil.isDataUri(uri)) {
return getDataFetchSequence();
} else {
throw new IllegalArgumentException(
"Unsupported uri scheme! Uri is: " + getShortenedUriString(uri));
}
}
3.4 Consumer的生产
Consumer不是在ImagePipeline中发起生产的,而是经过ImagePipeline-->CloseableProducerToDataSourceAdapter-->AbstractProducerToDataSourceAdapter的调用链,最终在抽象类中实现的:
//AbstractProducerToDataSourceAdapter源码
producer.produceResults(createConsumer(), settableProducerContext);//处理生产者
...
//定义消费者
private Consumer<T> createConsumer() {
return new BaseConsumer<T>() {
@Override
protected void onNewResultImpl(@Nullable T newResult, boolean isLast) {
AbstractProducerToDataSourceAdapter.this.onNewResultImpl(newResult, isLast);
}
...
//在消费者的回调中,调用Listener。
protected void onNewResultImpl(@Nullable T result, boolean isLast) {
if (super.setResult(result, isLast)) {
if (isLast) {
mRequestListener.onRequestSuccess(
mSettableProducerContext.getImageRequest(),
mSettableProducerContext.getId(),
mSettableProducerContext.isPrefetch());
}
}
}
可见,虽然Adapter适配器的主要作用是从Producer转为DataSource,但是Consumer可以做到从中间抓取Producer的执行结果,插入部分操作。
3.5 Listener的生产
ImagePipeline在请求图片时,会生产Listener。
//ImagePipeline源码
private RequestListener getRequestListenerForRequest(ImageRequest imageRequest) {
...
return new ForwardingRequestListener(mRequestListener, imageRequest.getRequestListener());
}
真正的生产Listener是在ForwardingRequestListener中:
//ForwardingRequestListener源码
public class ForwardingRequestListener implements RequestListener {
private final List<RequestListener> mRequestListeners;//列表
...
public ForwardingRequestListener(RequestListener... requestListeners) {
mRequestListeners = Arrays.asList(requestListeners);
}
ForwardingRequestListener本身是一个RequestLister,而且还持有一个RequestListener的列表,当ForwardingRequestListener被回调时,会遍历回调内部所有的Listener:
//ForwardingRequestListener源码
@Override
public void onRequestSuccess(ImageRequest request, String requestId, boolean isPrefetch) {
final int numberOfListeners = mRequestListeners.size();
for (int i = 0; i < numberOfListeners; ++i) {//遍历回调
RequestListener listener = mRequestListeners.get(i);
...
listener.onRequestSuccess(request, requestId, isPrefetch);
...
}
}
所以,每个Listener被回调时,都可以是一串回调。
3.6 Producer、Consumer、Listener如何协同工作
首先,准备好Producer后,提交请求。
//ImagePipeline源码
public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(
ImageRequest imageRequest,
Object callerContext,
ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit) {
try {
Producer<CloseableReference<CloseableImage>> producerSequence =
mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);
return submitFetchRequest(
producerSequence,
imageRequest,
lowestPermittedRequestLevelOnSubmit,
callerContext);
} catch (Exception exception) {
return DataSources.immediateFailedDataSource(exception);
}
}
然后,准备好RequestListener。
//ImagePipeline源码
private <T> DataSource<CloseableReference<T>> submitFetchRequest(
Producer<CloseableReference<T>> producerSequence,
ImageRequest imageRequest,
ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit,
Object callerContext) {
final RequestListener requestListener = getRequestListenerForRequest(imageRequest);
...
return CloseableProducerToDataSourceAdapter.create(
producerSequence,
settableProducerContext,
requestListener);
}
最后,定义Consumer,并把Producer、Consumer和Listener的业务逻辑关联起来。
//AbstractProducerToDataSourceAdapter源码
producer.produceResults(createConsumer(), settableProducerContext);//处理生产者
...
//定义消费者
private Consumer<T> createConsumer() {
return new BaseConsumer<T>() {
@Override
protected void onNewResultImpl(@Nullable T newResult, boolean isLast) {
AbstractProducerToDataSourceAdapter.this.onNewResultImpl(newResult, isLast);
}
...
//在消费者的回调中,调用Listener。
protected void onNewResultImpl(@Nullable T result, boolean isLast) {
if (super.setResult(result, isLast)) {
if (isLast) {
mRequestListener.onRequestSuccess(
mSettableProducerContext.getImageRequest(),
mSettableProducerContext.getId(),
mSettableProducerContext.isPrefetch());
}
}
}
3.7 Cache
为了提高图片加载性能,Fresco也采用了图片缓存的方式。
Cache代码具有普遍性,被分在imagepipeline-base里。
Fresco虽然有三级缓存,但从性质上就是内存缓存MemoryCache和磁盘缓存BufferedDiskCache两种,后者是一个全功能的缓存操作类。
内存缓存就比较复杂,为了兼顾不同类型的内存缓存,也为了优化内存使用,MemoryCache只是一个接口,真正为ImagePipeline生产的内存缓存,虽然Factory不同,但大都是个InstrumentedMemoryCache对象,该对象持有CountingMemoryCache,内存缓存的核心,就是这个CountingMemoryCache。
其实,管理内存缓存,重点就在于匹配、写入,和清理,CountingMemoryCache是这样实现的:
写入
为了方便查找和去重,使用一个CountingLruMap管理所有的内存缓存;
同时,需要检查是否超出空间上限,一个是数量上限,一个是内存空间上限,这都在MemoryCacheParams中配置,在ImagePipelineConfig时传入。
匹配
使用一个CountingLruMap(底层是一个LinkedHashMap),以k-v键值对的形式保存缓存,匹配时根据key即可匹配;
清理
清理需要兼顾空间效率和缓存命中,CountingMemoryCache的思路是专门管理未加载的图片。
具体来说,CountingMemoryCache另外做了一个CountingLruMap,专门存储未被加载过的图片,当空间不足时,CountingMemoryCache提供了一个trimExclusivelyOwnedEntries函数,根据清理数量或目标空间大小,清理部分数据。
在管理内存缓存时,使用两个CountingLruMap队列,其中一个队列中维持了
另外,缓存管理需要响应读写/命中等事件,所以Fresco定义了MemoryCacheTracker和ImageCacheStatsTracker两种跟踪器,可以跟踪处理缓存变化。
3.8 Pool
MemoryCache是通过缓存复用数据,这是数据层面的复用;在此基础上,Fresco做了更进一步的优化,就是在内存层面也做复用,因为图片内存空间一般较大,分配和回收内存空间也很耗资源,为此,Fresco使用内存池Pool来实现内存的复用。
Pool代码具有普遍性,被分在imagepipeline-base里。
图片内存复用时,一个很重要的变量就是图片的大小,为此,Fresco把Pool中的内存对象按照大小分别管理:
//BasePool源码
/**
* The buckets - representing different 'sizes'
*/
@VisibleForTesting
final SparseArray<Bucket<V>> mBuckets;
每个具体的业务类如BitmapPool,在get时都是执行了抽象父类BasePool的get方法(多态),从而实现从Pool中复用内存。
Pool和Cache虽然都是解决复用问题,但是应用场景不同,Cache是为业务层提供数据用的,Pool主要是为Decoder提供解码时的内存空间,如ArtDecoder就使用了BitmapPool。
3.9 Decoder
Decoder代码在imagepipeline包里。
因为Android系统变动,不同版本的Android系统下,各自有效率最优的Decoder方式,为此,Fresco使用PlatformDecoder接口定义了解码的抽象行为,然后针对不同平台,提供了不同的解码器:
GingerbreadPurgeableDecoder(继承DalvikPurgeableDecoder):通过MemoryFile来获取一个Ashmem内存,并在Ashmem内存中解码,因为Ashmem并不纳入Android虚拟机的Java内存管理,所以有占内存极低的”黑科技“的感觉。
KitKatPurgeableDecoder(继承DalvikPurgeableDecoder):因为Android系统不再允许通过Ashmem做复杂运算,解码需要在Java层开辟内存,这里使用了FlexByteArrayPool提供内存。
ArtDecoder:直接把未解码图片封装为流来解码,使用BitmapPool提供内存。
四、图片控件
Fresco与其他图片加载框架最大的不同就在于控件,Fresco提供了专门的图片控件DraweeView,包揽了动图播放、多级图层、渐进显示、画面裁剪等功能,而且性能控制地相当不错。
4.1 控件结构设计
先看DraweeView需要实现的功能:
1.需要显示图片;
2.需要管理所有需要显示的图片,并从中选择应当显示哪些;
3.需要更新这些图片;
4.控件是需要交互的,还需要处理onTouchEvent事件。
如果把这些功能看成一个APP,这其实是很典型的MVC结构,1是V,2是M,3+4是C。
在这个MVC结构中,View层显示数据时,需要从M层获取Drawable对象,也就是通过Hierarchy获取Drawable:
//DraweeView源码
super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());//super指的是ImageView
其中,Holder的函数是由Hierarchy处理的:
//DraweeHolder源码
public Drawable getTopLevelDrawable() {
return mHierarchy == null ? null : mHierarchy.getTopLevelDrawable();
}
此外,每次为DraweeView设置Hierarchy或Controller时,也会执行View的setImageDrawable更新显示内容,在Fresco里,实际上正是通过setImageURI/setHierarchy/setController这三个函数,实现的图片加载。
我们看到,Fresco的设计的V里有个DraweeHolder,这个Holder持有数据层Hierarchy和控制层Controller的实例,所以DraweeView里与C和M相关的操作,都是在这个Holder里操作的,这很像ContextWrapper里把所有跟Context相关的工作都交给ContextImpl的装饰模式,不过Fresco里这个设计属于合成复用原则,即尽量用聚合引用等关系,代替继承关系,这样解耦比较彻底,也方便用户。
利用这个Holder,我们实际上可以自己扩展DraweeView,只要引用一个DraweeHolder实例,就能继续套用Fresco设计的这套MVC结构。
4.2 MVC——M
Fresco在DraweeView中的Model层,主要管理和提供Drawable,所以,其实就是需要实现这样两个功能:
1.定义和管理6层图片的Drawable
2.提供合成后的图片Drawable
注意这里的Model层并不同于图像管理中的Model概念,并不试图获取图片数据,那是ImagePipeline的功能,这里的Model只是为了管理图片的分层。
具体来说,Fresco对于Model层的实现,核心接口就是DraweeHierarchy,定义了getTopLevelDrawbale函数,来提供Drawable数据:
public interface DraweeHierarchy {
Drawable getTopLevelDrawable();
}
为了实现多级图片的层次设计,扩展了一个SettableDraweeHierarchy:
public interface SettableDraweeHierarchy extends DraweeHierarchy {
void reset();
void setImage(Drawable drawable, float progress, boolean immediate);
void setProgress(float progress, boolean immediate);
void setFailure(Throwable throwable);
void setRetry(Throwable throwable);
void setControllerOverlay(Drawable drawable);
}
在这两个接口的基础上,才是我们真正在开发时使用的GenericDraweeHierarchy:
* <p>
* Example hierarchy with a placeholder, retry, failure and the actual image:
* <pre>
* 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)
* </pre>
*
* <p>
public class GenericDraweeHierarchy implements SettableDraweeHierarchy {
...
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; //覆盖图
我们说的6个层,就是在这个类中实现的,它使用一个Drawable数组管理这六层数据,并合并为一个RootDrawable类型的mTopLevelDrawble:
//GenericDraweeHierarchy源码
Drawable[] layers = new Drawable[numLayers];
...
//在数组中定义6层Drawable
...
mFadeDrawable = new FadeDrawable(layers);//组合6个图层
...
Drawable maybeRoundedDrawable =
WrappingUtils.maybeWrapWithRoundedOverlayColor(mFadeDrawable, mRoundingParams);//图像裁剪,如圆角
mTopLevelDrawable = new RootDrawable(maybeRoundedDrawable);//
Hierarchy最终向View提供一个RootDrawable类型的mTopLevelDrawable对象,作为V的数据源。
出于职责分工的设计,Hierarchy中的图层管理和V中的事件响应,都是在Controller中实现的。
4.3 MVC——C
对于控制层Controller来说,需要做这样几件事:
1.与View建立关联;
2.与Model建立关联;
3.生产DraweeController;
4.实现业务功能;
1.与View建立关联
为View设置Controller是开发者自己写代码的:
PipelineDraweeController controller =
(PipelineDraweeController) Fresco.newDraweeControllerBuilder()
.setUri(uri)
.build();
imageView.setController(controller);
这个很简单
2.与Model建立关联
我们知道,DraweeView的DraweeHolder持有数据层Hierarchy和控制层Controller的实例:
//DraweeHolder源码
public class DraweeHolder<DH extends DraweeHierarchy>
implements VisibilityCallback {
...
private DH mHierarchy;
private DraweeController mController = null;
在Holder设置Controller和Hierarchy时,这两者会被关联起来:
//DraweeHolder源码
public void setController(@Nullable DraweeController draweeController) {
...
mController.setHierarchy(mHierarchy);
}
...
public void setHierarchy(DH hierarchy) {
...
mController.setHierarchy(mHierarchy);
}
借此,Controller可以操作Model层。
3.生产DraweeController
为了保留扩展性,Fresco并没有使用限定的Controller,而是定义了Supplier、Builder、Factory等一批辅助类,以便扩展,前面说过,这些接口/类的关系是这样的:
我们看到,Fresco提供了PipelineDraweeController,为了方便扩展,可以向Fresco中传入自定义的Supplier,从Supplier中取出Builder,利用Builder的Factory生产Controller。
在这套体系之下,我们可以沿用Fresco的结构,自己扩展Builder、Factory和Controller,只要继承AbstractDraweeControllerBuilder就能很方便地实现Builder,只要继承AbstractDraweeController就能很方便地实现Controller,核心业务功能这两个抽象类都有实现过,开发者只要根据自身需要去扩展即可。
4.实现业务功能
Controller的业务功能都在DraweeController中定义好了:
public interface DraweeController {
DraweeHierarchy getHierarchy();
void setHierarchy(@Nullable DraweeHierarchy hierarchy);
void onAttach();
void onDetach();
void onViewportVisibilityHint(boolean isVisibleInViewportHint);
boolean onTouchEvent(MotionEvent event);
Animatable getAnimatable();
void setContentDescription(String contentDescription);
String getContentDescription();
}
具体来说,Controller的很多核心功能在抽象类AbstractDraweeController里已经写好了,onAttach、onDetach、事件处理等都有实现,这样可以把框架内部交互的工作都提前定义好;也有些核心功能如createDrawable是空的,需要子类如PipelineDraweeController自己实现。
例如,onAttach和onClick时默认需要刷新图像数据,这时会调用submitRequest函数,需要通过DataSource获取数据,这里的分工是这样的:
抽象类:submitResuest函数,具体业务逻辑已写好,但缺少DataSource实例。
实现类:提供DataSource,通过Builder的getDataSourceForRequest函数中获取。
因为DataSource实际调用的是ImagePipeline的fetchDecodedImage函数,所以Controller是通过ImagePipeline获取图片数据。
这个过程是这样的:
4.4 MVC——V
V其实非常简单,受益于MVC的设计,输入和输出都不在V中实现,onTouchEvent事件交给了Controller,setImageDrawable图像则是由Hierarchy数据层来负责提供Drawable,V只作为ImageView,把Drawable绘制到控件上。
至于Fresco对于6层图像的合并,前面在M层Hierarchy中已经看过,是Hierarchy负责合并Drawable,并调用Drawable的invalidateSelf函数重绘界面。
另外,DraweeView实际上还起到了外观类的作用,例如SimpleDraweeView的setImageURI函数,实际上是通过Holder交给Controller去做的,这是典型的外观模式。
除此之外,V的主要作用就是输入和输入,输入onTouchEvent事件,输出setImageDrawable图像。
五、Fresco的一些优化措施
为了缓解图片占用内存高,IO慢,渲染耗费CPU等问题,Fresco采用了大量优化手段。
5.1 解码器的优化
解码器最令人担心的问题,在于频繁解码会耗费大量的内存,这些内存频繁分配和回收,会造成GC卡顿。
所以,Fresco做了很多针对解码器的优化,如通过Pool内存池复用内寸空间。
而且,因为Android系统各平台的变化,不同平台的最优解码方式不一样,为此,Fresco是在ImagePipeline中生产解码器,生产解码器的代码如下:
//ImagePipelineFactory源码
public static PlatformDecoder buildPlatformDecoder(
PoolFactory poolFactory,
boolean directWebpDirectDecodingEnabled) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int maxNumThreads = poolFactory.getFlexByteArrayPoolMaxNumThreads();
return new ArtDecoder(
poolFactory.getBitmapPool(),
maxNumThreads,
new Pools.SynchronizedPool<>(maxNumThreads));
} else {
if (directWebpDirectDecodingEnabled
&& Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return new GingerbreadPurgeableDecoder();
} else {
return new KitKatPurgeableDecoder(poolFactory.getFlexByteArrayPool());
}
}
}
Fresco早期的解码器非常亮眼,它通过深入挖掘Ashmem的潜能,做到了解码、缓存和Java层极低的内存占用,但是后来的Android平台做了变更(4.4以后,dumpsys meminfo增加了Ashmem,就是一并计入App内存,以免滥用Android系统内存),后期的平台是在Java堆上管理内存了。
后期的解码,是BitmapFactory.decodeByteArray;
在Art平台上,是BitmapFactory.decodeStream。
5.2 CloseableReference
因为图片内存空间大,容易造成内存紧张,所以图片内存需要尽量主动回收,为此,Fresco使用CloseableReference包装了图片对象,例如,ImagePipeline获取已解码图片时,就使用CloseableReference:
//ImagePipeline源码
public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(
CloseableReference持有一个SharedReference,一方面存放对象,一方面引用计数,如果没有对象yin
参考
Fresco官网
Fresco图片库研读分析
Fresco图片框架内部实现原理探索
Fresco原理分析
Fresco源码赏析 之 基本流程
Fresco源码解析 - Hierarchy / View / Controller
浅析fresco
Fresco源码赏析之后台获取流程
谈谈fresco的bitmap内存分配