如果你是第一次看我的Fresco的源码分析系列文章,这里强烈推荐你先阅读我的前面两篇文章Fresco源码分析之DraweeView与Fresco源码分析之Hierarchy。好了,下面进入正题。在上篇文章中我们提到,在Fresco中关于图片的缓存、请求与显示逻辑处理都在Controller中。那么Controller到底是如何贯穿这些功能的呢?我们先从它的出生开始。
Suppiler
PipelineDraweeControllerBuilderSupplier是一个供应商,主要实现了Supplier<T>
接口,它只有一个方法T get(),用来获取相关的提供实现。因此该供应类提供的就是PipelineDraweeControllerBuilder实例。
@Override
public PipelineDraweeControllerBuilder get() {
return new PipelineDraweeControllerBuilder(
mContext,
mPipelineDraweeControllerFactory,
mImagePipeline,
mBoundControllerListeners);
}
在生成的builder中有4个参数,第一个是Context再熟悉不过了;第二个是Controller的工厂;第三个是数据管道ImagePipeline;第四个是listener的set集合,主要用在图片请求之后的监听回调。下面详细说明后面三个参数内容与作用。
PipelineDraweeControllerFactory
在这个类中主要就两个方法,分别为internalCreateController与newController,对外的方法就一个newController。这个两个方法都是用来创建PipelineDraweeController对象。其中newController内部就是调用了internalCreateController来进行创建PipelineDraweeController实例。
protected PipelineDraweeController internalCreateController(
Resources resources,
DeferredReleaser deferredReleaser,
DrawableFactory animatedDrawableFactory,
Executor uiThreadExecutor,
MemoryCache<CacheKey, CloseableImage> memoryCache,
@Nullable ImmutableList<DrawableFactory> globalDrawableFactories,
@Nullable ImmutableList<DrawableFactory> customDrawableFactories,
Supplier<DataSource<CloseableReference<CloseableImage>>> dataSourceSupplier,
String id,
CacheKey cacheKey,
Object callerContext) {
PipelineDraweeController controller = new PipelineDraweeController(
resources,
deferredReleaser,
animatedDrawableFactory,
uiThreadExecutor,
memoryCache,
dataSourceSupplier,
id,
cacheKey,
callerContext,
globalDrawableFactories);
controller.setCustomDrawableFactories(customDrawableFactories);
return controller;
}
其中DeferredReleaser是用来管理推迟资源释放的。我们在之前的文章已经提到,在onAttach中会进行加载资源,而onDetach中又会释放资源。因为在Fresco中往往会在onDetach与onAttach之间频繁切换(view的显隐、绘制与Controller的设置都会调用),并且它们都处于在同一个looper(其实就是主进程的looper)中。如果在onDetach时马上释放资源的话,这样会造成资源的滥用,导致不必要的资源加载与释放回收。所以就用了这个资源推迟释放的机制(内部原理是使用了set集合的唯一性的特性)。
dataSourceSupplier是DataSource的供应商,用来提供DataSource实例。而DataSource是用来获取与存储请求结果的,相当与图片数据源。这些都会在后续的Controller中使用到。
ImagePipeline
既然它是数据管道,自然是与网络请求与缓存数据有关。其实我们可以把它理解为多个管道的集合,最终显示的图片资源就是来自于它们中的其中一个。下面介绍其中的主要方法:
- fetchDecodedImage() 发送请求,返回decode image的数据源。
- fetchEncodedImage() 发送请求,返回encoded image的数据源。
- prefetchToBitmapCache() 发送预处理请求,获取预处理的bitmap缓存数据。
- prefetchToDiskCache() 发送预处理请求,获取预处理的磁盘缓存数据。
- submitFetchRequest() 发送请求,获取相应类型的数据源。
- submitPrefetchRequest() 发送预处理请求,获取相应类型的缓存数据。
这里用的最多的还是fetchDecodedImage()
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);
}
}
这里主要涉及到Producer,这是一个生产者,内部只有一个公共接口方法void produceResults(Consumer<T> consumer, ProducerContext context),用来获取数据源。其实我们会发现submitFetchRequest方法中的producer传入的其实是一个队列,因为内部会递归调用produceResults()来获取最终的数据源。
关于Producer后续有时间的话会单独开篇文章详细分析。
ControllerListener
如果你对ControllerListener不熟悉的话,那么BaseControllerListener应该或多或少使用过吧。它其实就是ControllerListener的空实现。既然是监听回调,那么来看下它提供的回调方法与调用时机。
- void onSubmit(String id, Object callerContext) 在发送请求的时候回调
- void onFinalImageSet(String id, @Nullable INFO imageInfo, @Nullable Animatable animatable) 在最终设置image图片时回调,其中imageInfo包含图片的相关基本信息(width、height与quality)
- void onIntermediateImageSet(String id, @Nullable INFO imageInfo) 在发送请求与最终图片设置的过程中回调
- void onIntermediateImageFailed(String id, Throwable throwable) 在发送请求与最终失败的过程中回调
- void onFailure(String id, Throwable throwable) 发送请求失败时回调
- void onRelease(String id) 资源释放时回调
ControllerBuilder
既然是builder模式,最终的目的自然就是用来创建Controller,所以我们可以直接奔着它的目的来分析。在这里Controller的builder类是PipelineDraweeControllerBuilder。我们找到它的build()发现在它的父类AbstractDraweeControllerBuilder中。但最终的创建实例方法还是调用了obtainController()抽象方法。所以经过反转还是回到了PipelineDraweeControllerBuilder,那么我们直接来看下它创建方式。
@Override
protected PipelineDraweeController obtainController() {
DraweeController oldController = getOldController();
PipelineDraweeController controller;
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;
}
通过上面的代码,逻辑已经很明显了。首先判断是否已经存在Controller,如果存在的话就无需创建新的实例,只需调用initialize()方法进行重写初始化;如果不存在,那么就调用我们文章之前分析的PipelineDraweeControllerFactory中的newController()来创建新的实例。这里主要的参数还是obtainDataSourceSupplier(),之前也简单提到了,它是DataSource的供应者。那么我们来看下Supplier的创建
protected Supplier<DataSource<IMAGE>> getDataSourceSupplierForRequest(
final REQUEST imageRequest,
final CacheLevel cacheLevel) {
final Object callerContext = getCallerContext();
return new Supplier<DataSource<IMAGE>>() {
@Override
public DataSource<IMAGE> get() {
return getDataSourceForRequest(imageRequest, callerContext, cacheLevel);
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("request", imageRequest.toString())
.toString();
}
};
}
在这个方法中,我们一眼就看到了Supplier的创建,之前也提到它只有一个get()方法,就是用来提供所以需要的DataSource。在这里也是如此,这里它调用了getDataSourceForRequest()方法,该方法是一个抽象方法,细节实现由它的子类实现,所以我们可以再次回到getDataSourceForRequest,在其中就能够搜索到getDataSourceForRequest()方法
@Override
protected DataSource<CloseableReference<CloseableImage>> getDataSourceForRequest(
ImageRequest imageRequest,
Object callerContext,
CacheLevel cacheLevel) {
return mImagePipeline.fetchDecodedImage(
imageRequest,
callerContext,
convertCacheLevelToRequestLevel(cacheLevel));
}
看到上面的方法实现方法是否眼熟呢?这也是我们上面所提到的ImagePipleline中的方法,这里就不在多做分析了。这样Controller就与获取数据的通道建立了联系。那么下面我们就转战到Controller中,看看它到底做了什么。
Controller
PipelineDraweeController继承于AbstractDraweeController,在PipelineDraweeController中主要的方法有三个
- Drawable createDrawable(CloseableImage closeableImage) 这是内部类DrawableFactory中的方法,是一个工厂,不言而喻它是用来创建Drawable的,在数据源返回的时候回调,进而显示到Hierarchy层。
- getDataSource() 获取数据源通道,与其建立联系。
- void setHierarchy(@Nullable DraweeHierarchy hierarchy) 设置Hierarchy图层,内部持有的其实是SettableDraweeHierarchy接口对象。所以内部调用的也就是它的6个接口方法。之前的文章也有提及,用来控制图片加载过程中的显示逻辑。
其余的逻辑处理都在它的父类AbstractDraweeController中。在之前我们多次提及到onAttach与onDetach方法,它们分别是处理数据加载与释放。
onAttach
@Override
public void onAttach() {
if (FLog.isLoggable(FLog.VERBOSE)) {
FLog.v(
TAG,
"controller %x %s: onAttach: %s",
System.identityHashCode(this),
mId,
mIsRequestSubmitted ? "request already submitted" : "request needs submit");
}
//事件记录器
mEventTracker.recordEvent(Event.ON_ATTACH_CONTROLLER);
Preconditions.checkNotNull(mSettableDraweeHierarchy);
//取消资源推迟释放机制,防止资源被释放
mDeferredReleaser.cancelDeferredRelease(this);
mIsAttached = true;
if (!mIsRequestSubmitted) {
submitRequest();
}
}
在这个方法中mEventTracker是事件记录器,默认是开启的,如果要关闭则需要在Fresco.initialize()之前调用DraweeEventTracker.disable()关闭;然后就是将其从资源推迟释放机制中取消;最后就是调用submitRequest()发送数据源请求。
protected void submitRequest() {
final T closeableImage = getCachedImage();
//1.判断内存缓存中是否存在
if (closeableImage != null) {
mDataSource = null;
mIsRequestSubmitted = true;
mHasFetchFailed = false;
mEventTracker.recordEvent(Event.ON_SUBMIT_CACHE_HIT);
//1.1数据获取中通知回调
getControllerListener().onSubmit(mId, mCallerContext);
//1.2数据处理
onNewResultInternal(mId, mDataSource, closeableImage, 1.0f, true, true);
return;
}
mEventTracker.recordEvent(Event.ON_DATASOURCE_SUBMIT);
//2.通过DataSource获取数据源
//2.1数据获取中通知回调
getControllerListener().onSubmit(mId, mCallerContext);
mSettableDraweeHierarchy.setProgress(0, true);
mIsRequestSubmitted = true;
mHasFetchFailed = false;
mDataSource = getDataSource();
if (FLog.isLoggable(FLog.VERBOSE)) {
FLog.v(
TAG,
"controller %x %s: submitRequest: dataSource: %x",
System.identityHashCode(this),
mId,
System.identityHashCode(mDataSource));
}
final String id = mId;
final boolean wasImmediate = mDataSource.hasResult();
//内部请求数据回调,当数据源返回时回调
final DataSubscriber<T> dataSubscriber =
new BaseDataSubscriber<T>() {
@Override
public void onNewResultImpl(DataSource<T> dataSource) {
// isFinished must be obtained before image, otherwise we might set intermediate result
// as final image.
boolean isFinished = dataSource.isFinished();
float progress = dataSource.getProgress();
T image = dataSource.getResult();
//2.2数据处理
if (image != null) {
//成功处理
onNewResultInternal(id, dataSource, image, progress, isFinished, wasImmediate);
} else if (isFinished) {
//失败处理
onFailureInternal(id, dataSource, new NullPointerException(), /* isFinished */ true);
}
}
@Override
public void onFailureImpl(DataSource<T> dataSource) {
//失败处理
onFailureInternal(id, dataSource, dataSource.getFailureCause(), /* isFinished */ true);
}
@Override
public void onProgressUpdate(DataSource<T> dataSource) {
boolean isFinished = dataSource.isFinished();
float progress = dataSource.getProgress();
//数据进度处理
onProgressUpdateInternal(id, dataSource, progress, isFinished);
}
};
//数据源订阅回调注册
mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor);
}
逻辑方面的处理,上面代码中已经详细注释了,总的来说就是先从内存中获取如果存在就直接拿来用,否则就通过DataSource从网络或者是本地资源中获取。使用DataSource方式会使用到DataSubscriber,即订阅方式。当数据源已经获取到时,发送通知给订阅者,因此分别回调订阅者的方法。上述两种方式只要成功了都会交由onNewResultInternal()处理,而失败则由onFailureInternal()处理,同时请求进度处理由onProgressUpdateInternal()处理。
private void onNewResultInternal(
String id,
DataSource<T> dataSource,
@Nullable T image,
float progress,
boolean isFinished,
boolean wasImmediate) {
// ignore late callbacks (data source that returned the new result is not the one we expected)
if (!isExpectedDataSource(id, dataSource)) {
logMessageAndImage("ignore_old_datasource @ onNewResult", image);
releaseImage(image);
dataSource.close();
return;
}
mEventTracker.recordEvent(
isFinished ? Event.ON_DATASOURCE_RESULT : Event.ON_DATASOURCE_RESULT_INT);
// create drawable
Drawable drawable;
try {
drawable = createDrawable(image);
} catch (Exception exception) {
logMessageAndImage("drawable_failed @ onNewResult", image);
releaseImage(image);
onFailureInternal(id, dataSource, exception, isFinished);
return;
}
T previousImage = mFetchedImage;
Drawable previousDrawable = mDrawable;
mFetchedImage = image;
mDrawable = drawable;
try {
// set the new image
if (isFinished) {
logMessageAndImage("set_final_result @ onNewResult", image);
mDataSource = null;
//通过hierarchy(GenericDraweeHierarchy)来设置image
mSettableDraweeHierarchy.setImage(drawable, 1f, wasImmediate);
getControllerListener().onFinalImageSet(id, getImageInfo(image), getAnimatable());
// IMPORTANT: do not execute any instance-specific code after this point
} else {
logMessageAndImage("set_intermediate_result @ onNewResult", image);
mSettableDraweeHierarchy.setImage(drawable, progress, wasImmediate);
getControllerListener().onIntermediateImageSet(id, getImageInfo(image));
// IMPORTANT: do not execute any instance-specific code after this point
}
} finally {
if (previousDrawable != null && previousDrawable != drawable) {
releaseDrawable(previousDrawable);
}
if (previousImage != null && previousImage != image) {
logMessageAndImage("release_previous_result @ onNewResult", previousImage);
releaseImage(previousImage);
}
}
}
这里我们主要就看里面的两个try。
第一个使用createDrawable(image)将拿到的数据源转变成Drawable,这个方法的具体实现是在子类中实现(上面也有提及)。
第二个分为两种情况,一方面如果数据源已经全部获取完,则直接调用SettableDraweeHierarchy接口的setImage()方法将图片设置到Hierarchy图层上,同时调用Listener的回调方法onFinalImageSet();另一方面如果数据源还在获取中,也是调用SettableDraweeHierarchy接口的setImage()方法,只是其中的参数progress根据进度来设置而已,由于还处于资源获取中所以调用onIntermediateImageSet()回调。
这样Controller就与Hierarchy联系起来了,将需要的图片设置到显示的图片中。
对于SettableDraweeHierarchy中的这些方法如果不理解的可以回过头去看我之前的这篇文章Fresco源码分析之Hierarchy
由于源码太多,对于onFailureInternal()与onProgressUpdateInternal()这里就不贴出源码来进行分析了。原理与调用的方法基本类似,如果想看源码的可以点这里
onDetach
@Override
public void onDetach() {
if (FLog.isLoggable(FLog.VERBOSE)) {
FLog.v(TAG, "controller %x %s: onDetach", System.identityHashCode(this), mId);
}
mEventTracker.recordEvent(Event.ON_DETACH_CONTROLLER);
mIsAttached = false;
mDeferredReleaser.scheduleDeferredRelease(this);
}
相对于之前的分析onDetach()就简单多了,这里它只是对资源进行释放,释放的策略也是推迟释放策略DeferredReleaser。
End
本篇文章主要分析了Fresco中的Controller相关处理逻辑,它控制着Hierarchy显示逻辑,同时它是数据源的获取桥梁通过DataSource来链接数据源的获取。那么问题又来了,DataSource又是如何产生的呢?同时它的内部逻辑又是如何的呢?这就涉及到Producer了,敬请关注下篇文章Fresco源码分析之Producer
关注
Recommend
Fresco源码分析之DraweeView
Fresco源码分析之Hierarchy
Android共享动画兼容实现
Kotlin最佳实践
RecyclerView下拉刷新与上拉更多
Android高仿微信之mvp实现(四)
tensorflow-梯度下降,有这一篇就足够了
博客
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。