Fresco(一)Fresco的使用介绍
Fresco是一个出自Facebook的功能强大的图片加载库。本文就来介绍一下它的使用
(1)引入包
implementation 'com.facebook.fresco:fresco:2.1.0'
最新的是 2.4.0,之所以没用是因为android studio 拉依赖的时候出了点问题,一直没解决,所以降了版本
(2)初始化
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Fresco.initialize(this);
}
}
初始化两个configura。ImagePipelineConfig 和 DraweeConfig。源码解析放后面说,这里先用默认的。
(3)添加控件 SimpleDraweeView
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="20dp"
android:layout_height="20dp"
fresco:fadeDuration="300"
fresco:actualImageScaleType="focusCrop"
fresco:placeholderImage="@color/wait_color"
fresco:placeholderImageScaleType="fitCenter"
fresco:failureImage="@drawable/error"
fresco:failureImageScaleType="centerInside"
fresco:retryImage="@drawable/retrying"
fresco:retryImageScaleType="centerCrop"
fresco:progressBarImage="@drawable/progress_bar"
fresco:progressBarImageScaleType="centerInside"
fresco:progressBarAutoRotateInterval="1000"
fresco:backgroundImage="@color/blue"
fresco:overlayImage="@drawable/watermark"
fresco:pressedStateOverlayImage="@color/red"
fresco:roundAsCircle="false"
fresco:roundedCornerRadius="1dp"
fresco:roundTopLeft="true"
fresco:roundTopRight="false"
fresco:roundBottomLeft="false"
fresco:roundBottomRight="true"
fresco:roundWithOverlayColor="@color/corner_color"
fresco:roundingBorderWidth="2dp"
fresco:roundingBorderColor="@color/border_color"
/>
以上就是 SimpleDraweeView 可以配置的各种选项。
注意,大小不支持 wrap_content,为什么Fresco中不可以使用wrap_content?主要的原因是,Drawee永远会在getIntrinsicHeight/getIntrinsicWidth中返回-1。Drawee 不像 ImageView 一样。它同一时刻可能会显示多个元素。比如在从占位图渐变到目标图时,两张图会有同时显示的时候。再比如可能有多张目标图片(低清晰度、高清晰度两张)。如果这些图像都是不同的尺寸,那么很难定义”intrinsic”尺寸。(留着这个疑问,我们到源码解析部分去看)
一般情况下,在XML设置显示效果即可, 如果想更多定制化,可以创建一个 builder 然后设置给 DraweeView
List<Drawable> backgroundsList;
List<Drawable> overlaysList;
GenericDraweeHierarchyBuilder builder =
new GenericDraweeHierarchyBuilder(getResources());
GenericDraweeHierarchy hierarchy = builder
.setFadeDuration(300)
.setPlaceholderImage(new MyCustomDrawable())
.setBackgrounds(backgroundList)
.setOverlays(overlaysList)
.build();
mSimpleDraweeView.setHierarchy(hierarchy);
点进去看 GenericDraweeHierarchy。里面一共有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;
通过builder把对应的drawable设置进去。比如常见的占位图(setPlaceholderImage),失败占位图(setFailureImage),进度条(setProgressBarImage)等等。还可以设置圆角(setRoundingParams)
当然,GenericDraweeHierarchy 是对所有的配置图片的说明。如果需要对加载显示的图片做更多的控制和定制,那就需要用到DraweeController
Postprocessor myPostprocessor = new Postprocessor() {
@Override
public CloseableReference<Bitmap> process(Bitmap sourceBitmap, PlatformBitmapFactory bitmapFactory) {
for (int x = 0; x < sourceBitmap.getWidth(); x+=2) {
for (int y = 0; y < sourceBitmap.getHeight(); y+=2) {
sourceBitmap.setPixel(x, y, Color.RED);
}
}
// 举例,给图片加了红色网格
return bitmapFactory.createBitmap(sourceBitmap);
}
@Override
public String getName() {
return null;
}
@Override
public CacheKey getPostprocessorCacheKey() {
return null;
}
};
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setProgressiveRenderingEnabled(true)//手动开启渐进式加载
.setPostprocessor(myPostprocessor)//如果需要修改图片,可使用后处理器(Postprocessor)
.build();//还可以设置 setCacheChoice,缩放和旋转图片setResizeOptions
ControllerListener listener = new BaseControllerListener() {
@Override
public void onFinalImageSet(String id, Object imageInfo, Animatable animatable) {
}
@Override
public void onIntermediateImageSet(String id, Object imageInfo) {
// 渐进式图片的回调
}
@Override
public void onIntermediateImageFailed(String id, Throwable throwable) {
// 渐进式图片的回调
}
@Override
public void onFailure(String id, Throwable throwable) {
}
};
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setTapToRetryEnabled(true)
.setOldController(draweeView.getController())//使用setOldController,这可节省不必要的内存分配
.setControllerListener(listener)//监听下载事件
.setImageRequest(request)//自定义ImageRequest
.setAutoPlayAnimations(true)//设置动画图自动播放
.build();
draweeView.setController(controller);
本人把常用用法大体都整理了出来。由此可以看到DraweeController功能还是挺强大的
总结:SimpleDraweeView 的配置主要在 GenericDraweeHierarchy(所有图层,包括占位图) 和 DraweeController(定制要显示的图片) 里面
(4)加载图像之 ImagePipelineConfig
对于大多数的应用,Fresco的初始化,只需要以下一句代码:
Fresco.initialize(context);
当然,我们也可以自定义配置,就在 ImagePipelineConfig 里面
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
.setBitmapMemoryCacheParamsSupplier(bitmapCacheParamsSupplier)
.setCacheKeyFactory(cacheKeyFactory)
.setDownsampleEnabled(true)
.setWebpSupportEnabled(true)
.setEncodedMemoryCacheParamsSupplier(encodedCacheParamsSupplier)
.setExecutorSupplier(executorSupplier)
.setImageCacheStatsTracker(imageCacheStatsTracker)
.setMainDiskCacheConfig(mainDiskCacheConfig)
.setMemoryTrimmableRegistry(memoryTrimmableRegistry)
.setNetworkFetchProducer(networkFetchProducer)
.setPoolFactory(poolFactory)
.setProgressiveJpegConfig(progressiveJpegConfig)
.setRequestListeners(requestListeners)
.setSmallImageDiskCacheConfig(smallImageDiskCacheConfig)
.build();
Fresco.initialize(context, config);
代码应该能看懂,本人就简单讲讲每个参数都有啥。先谈谈 Supplier
许多配置的Builder都接受一个Supplier 类型的参数而不是一个配置的实例。
public interface Supplier<T> {
/**
* Retrieves an instance of the appropriate type. The returned object may or may not be a new
* instance, depending on the implementation.
*
* @return an instance of the appropriate type
*/
T get();
}
它其实是一个接口。举个最简单的在freso库里面就有个实现看看,比如 DefaultBitmapMemoryCacheParamsSupplier
public class DefaultBitmapMemoryCacheParamsSupplier implements Supplier<MemoryCacheParams> {
private static final int MAX_CACHE_ENTRIES = 256;
private static final int MAX_EVICTION_QUEUE_SIZE = Integer.MAX_VALUE;
private static final int MAX_EVICTION_QUEUE_ENTRIES = Integer.MAX_VALUE;
private static final int MAX_CACHE_ENTRY_SIZE = Integer.MAX_VALUE;
private static final long PARAMS_CHECK_INTERVAL_MS = TimeUnit.MINUTES.toMillis(5);
private final ActivityManager mActivityManager;
public DefaultBitmapMemoryCacheParamsSupplier(ActivityManager activityManager) {
mActivityManager = activityManager;
}
@Override
public MemoryCacheParams get() {
return new MemoryCacheParams(
getMaxCacheSize(),
MAX_CACHE_ENTRIES,
MAX_EVICTION_QUEUE_SIZE,
MAX_EVICTION_QUEUE_ENTRIES,
MAX_CACHE_ENTRY_SIZE,
PARAMS_CHECK_INTERVAL_MS);
}
private int getMaxCacheSize() { ... }
}
其实很好理解,就是通过统一的接口来实现对参数的管理。好了,接下来就看看各个参数配置了啥
(1)setBitmapMemoryCacheParamsSupplier
默认 DefaultBitmapMemoryCacheParamsSupplier,就是上面贴出来的
最大cache项是256,PARAMS_CHECK_INTERVAL_MS 就是 每隔5分钟就可检查一下Supplier。
getMaxCacheSize 根据系统为你整个app分配的memory大小来决定。系统给的小于32M,就是4M,系统给的小于64M,那就8M
(2)setCacheKeyFactory
默认 DefaultCacheKeyFactory。它提供了4种key
@Override
public CacheKey getBitmapCacheKey(ImageRequest request, Object callerContext) {
return new BitmapMemoryCacheKey(
getCacheKeySourceUri(request.getSourceUri()).toString(),
request.getResizeOptions(),
request.getRotationOptions(),
request.getImageDecodeOptions(),
null,
null,
callerContext);
}
@Override
public CacheKey getPostprocessedBitmapCacheKey(ImageRequest request, Object callerContext) {
final Postprocessor postprocessor = request.getPostprocessor();
final CacheKey postprocessorCacheKey;
final String postprocessorName;
if (postprocessor != null) {
postprocessorCacheKey = postprocessor.getPostprocessorCacheKey();
postprocessorName = postprocessor.getClass().getName();
} else {
postprocessorCacheKey = null;
postprocessorName = null;
}
return new BitmapMemoryCacheKey(
getCacheKeySourceUri(request.getSourceUri()).toString(),
request.getResizeOptions(),
request.getRotationOptions(),
request.getImageDecodeOptions(),
postprocessorCacheKey,
postprocessorName,
callerContext);
}
@Override
public CacheKey getEncodedCacheKey(ImageRequest request, @Nullable Object callerContext) {
return getEncodedCacheKey(request, request.getSourceUri(), callerContext);
}
@Override
public CacheKey getEncodedCacheKey(
ImageRequest request, Uri sourceUri, @Nullable Object callerContext) {//点进去看就是sourceUri
return new SimpleCacheKey(getCacheKeySourceUri(sourceUri).toString());
}
protected Uri getCacheKeySourceUri(Uri sourceUri) {
return sourceUri;
}
(3)setEncodedMemoryCacheParamsSupplier
默认 DefaultEncodedMemoryCacheParamsSupplier.挺简单的,不贴代码了
(4)setExecutorSupplier
默认 DefaultExecutorSupplier
public class DefaultExecutorSupplier implements ExecutorSupplier {
// Allows for simultaneous reads and writes.
private static final int NUM_IO_BOUND_THREADS = 2;
private static final int NUM_LIGHTWEIGHT_BACKGROUND_THREADS = 1;
private final Executor mIoBoundExecutor;
private final Executor mDecodeExecutor;
private final Executor mBackgroundExecutor;
private final Executor mLightWeightBackgroundExecutor;
public DefaultExecutorSupplier(int numCpuBoundThreads) {
mIoBoundExecutor =
Executors.newFixedThreadPool(
NUM_IO_BOUND_THREADS,
new PriorityThreadFactory(
Process.THREAD_PRIORITY_BACKGROUND, "FrescoIoBoundExecutor", true));
mDecodeExecutor =
Executors.newFixedThreadPool(
numCpuBoundThreads,
new PriorityThreadFactory(
Process.THREAD_PRIORITY_BACKGROUND, "FrescoDecodeExecutor", true));
mBackgroundExecutor =
Executors.newFixedThreadPool(
numCpuBoundThreads,
new PriorityThreadFactory(
Process.THREAD_PRIORITY_BACKGROUND, "FrescoBackgroundExecutor", true));
mLightWeightBackgroundExecutor =
Executors.newFixedThreadPool(
NUM_LIGHTWEIGHT_BACKGROUND_THREADS,
new PriorityThreadFactory(
Process.THREAD_PRIORITY_BACKGROUND, "FrescoLightWeightBackgroundExecutor", true));
}
}
简单介绍一下
- mIoBoundExecutor---io线程池,用于磁盘操作,本地文件的读取,默认2个线程
- mDecodeExecutor---解析图片的,线城数可以自己设置
- mBackgroundExecutor---后台工作的,线城数可以自己设置,暂时不知道干嘛的,等后面的代码解析
- mLightWeightBackgroundExecutor---轻量级后台工作,等解析,默认1个线程
(5)setMainDiskCacheConfig
默认 DiskCacheConfig。我摘出了重要的代码
private long mMaxCacheSize = 40 * ByteConstants.MB;
private long mMaxCacheSizeOnLowDiskSpace = 10 * ByteConstants.MB;
private long mMaxCacheSizeOnVeryLowDiskSpace = 2 * ByteConstants.MB;
private EntryEvictionComparatorSupplier mEntryEvictionComparatorSupplier =
new DefaultEntryEvictionComparatorSupplier();
注意 DefaultEntryEvictionComparatorSupplier。里面就是 Comparator,根据时间戳来决定是否排除响应cache,先排除时间久的
(4)加载图片 setImageURI
Uri uri = Uri.parse("https://t7.baidu.com/it/u=4036010509,3445021118&fm=193&f=GIF");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);
大致流程如下:
- 检查内存缓存,如有,返回
- 后台线程开始后续工作
- 检查是否在未解码内存缓存中。如有,解码,变换,返回,然后缓存到内存缓存中。
- 检查是否在磁盘缓存中,如果有,变换,返回。缓存到未解码缓存和内存缓存中。
- 从网络或者本地加载。加载完成后,解码,变换,返回。存到各个缓存中。
具体源码下一篇说