Fresco是facebook开源的图片加载框架。
关于 Fresco Fresco 是一个强大的图片加载组件。
Fresco 中设计有一个叫做 image pipeline
的模块。它负责从网络,从本地文件系统,本地资源加载图片。为了最大限度节省空间和CPU时间,它含有3级缓存设计(2级内存,1级文件)。Fresco 中设计有一个叫做 Drawees 模块,方便地显示loading图,当图片不再显示在屏幕上时,及时地释放内存和空间占用。
Fresco 支持 Android2.3(API level 9) 及其以上系统。
相比其他的图片加载框架功能强大得多,如果还不了解同学可以点击上面的链接学习了解。
它的整个框架设计涉及到很多的设计模式,作为开发者如果不了解足够多的设计模式对开源框架分析理解是在有点困难,所以我的博客会经常更新一些有关设计模式的内容和大家一起学习。
Fresco功能强大。如果只是普通的使用很简单。
如果需要使用它真正强大的功能就很复杂,这是因为它的框架中包含了太多的功能类和设计模式,导致使用起来非常麻烦,相比其他的一行代码就可以解决图片加载,Fresco就麻烦多了。所以我封装了一个ImageLoadFresco类方便我在项目中使用,现在开源出来给作为使用大家参考。
为什么Fresco配置这么复杂?
因为对整个Fresco图片加载框架 使用了MVC模式
M层上有很多的图片资源,比如占位图,loading图,error图
C层决定调用M层上哪些图
V层对应视图View层,最后绑定M/C层。所以在使用时需要配置M/C层需要很多代码。
封装之后调用图片加载就相对简单,简单的使用是没有问题的,只是在Adapter中使用存在控件重复配置的问题,会对内存有些影响。这个等我有时间再深究解决。
问题提示在这里
在指定一个新的controller的时候,使用setOldController,这可节省不必要的内存分配。
ImageLoadBuilder.Start(getContext(),mImageView,url)
.setIsCircle(true)
.build();
针对这个配置属性比较多的类,我采用Builder生成器模式,简化使用时候的代码量。类里面配置很多默认属性。如果需要添加新的属性也不会影响现有代码编译。同时在使用的时候不涉及具体的Fresco代码,后期如果有需求替换图片加载框架,也会方便。
最新的代码点这里,这个封装类我自己也在项目中使用,会根据需求添加新的功能。
抽象出两个类
设置默认配置以及修改配置的方法等
/**
* Created by 李可乐 on 2017/2/17 0017.
* 图片加载的封装builder类
* 使用示例
*
* ImageLoadBuilder.Start(getContext(),mImageUser,url_head)
* .setIsCircle(true)
* .build();
*
*/
public class ImageLoadBuilder {
//必要参数
Context mContext;
SimpleDraweeView mSimpleDraweeView;
String mUrl;
//非必要参数
String mUrlLow;//低分率图地址
Drawable mPlaceHolderImage;//占位图
Drawable mProgressBarImage;//loading图
Drawable mRetryImage;//重试图
Drawable mFailureImage;//失败图
Drawable mBackgroundImage;//背景图
ScalingUtils.ScaleType mActualImageScaleType = ScalingUtils.ScaleType.CENTER_CROP;
boolean mIsCircle = false;//是否圆形图片
boolean mIsRadius = false;//是否圆角
boolean mIsBorder = false;//是否有包边
float mRadius = 10;//圆角度数 默认10
ResizeOptions mResizeOptions = new ResizeOptions(3000, 3000);//图片的大小限制
ControllerListener mControllerListener;//图片加载的回调
BaseBitmapDataSubscriber mBitmapDataSubscriber;
/**
* 构造器的构造方法 传入必要参数
*
* @param mContext
* @param mSimpleDraweeView
* @param mUrl
*/
public ImageLoadBuilder(Context mContext, SimpleDraweeView mSimpleDraweeView, String mUrl) {
this.mContext = mContext;
this.mSimpleDraweeView = mSimpleDraweeView;
this.mUrl = mUrl;
}
public static ImageLoadBuilder Start(Context mContext, SimpleDraweeView mSimpleDraweeView, String mUrl) {
return new ImageLoadBuilder(mContext, mSimpleDraweeView, mUrl);
}
/**
* 构造器的build方法 构造真正的对象 并返回
* 构造之前需要检查
*
* @return
*/
public ImageLoadFresco build() {
Logger.d("图片开始加载 viewId=" + this.mSimpleDraweeView.getId() + " url" + this.mUrl);
// if (TextUtils.isEmpty(mUrl)) {
// throw new IllegalArgumentException("URL不能为空");
// }
//不能同时设定 圆形圆角
if (mIsCircle && mIsRadius) {
throw new IllegalArgumentException("图片不能同时设置圆角和圆形");
}
return new ImageLoadFresco(this);
}
public ImageLoadBuilder setBitmapDataSubscriber(BaseBitmapDataSubscriber mBitmapDataSubscriber) {
this.mBitmapDataSubscriber = mBitmapDataSubscriber;
return this;
}
public ImageLoadBuilder setUrlLow(String urlLow) {
this.mUrlLow = urlLow;
return this;
}
public ImageLoadBuilder setActualImageScaleType(ScalingUtils.ScaleType mActualImageScaleType) {
this.mActualImageScaleType = mActualImageScaleType;
return this;
}
public ImageLoadBuilder setPlaceHolderImage(Drawable mPlaceHolderImage) {
this.mPlaceHolderImage = mPlaceHolderImage;
return this;
}
public ImageLoadBuilder setProgressBarImage(Drawable mProgressBarImage) {
this.mProgressBarImage = mProgressBarImage;
return this;
}
public ImageLoadBuilder setRetryImage(Drawable mRetryImage) {
this.mRetryImage = mRetryImage;
return this;
}
public ImageLoadBuilder setFailureImage(Drawable mFailureImage) {
this.mFailureImage = mFailureImage;
return this;
}
public ImageLoadBuilder setBackgroundImage(Drawable mBackgroundImage) {
this.mBackgroundImage = mBackgroundImage;
return this;
}
public ImageLoadBuilder setBackgroupImageColor(int colorId) {
Drawable color = ContextCompat.getDrawable(mContext, colorId);
this.mBackgroundImage = color;
return this;
}
public ImageLoadBuilder setIsCircle(boolean mIsCircle) {
this.mIsCircle = mIsCircle;
return this;
}
public ImageLoadBuilder setIsCircle(boolean mIsCircle, boolean mIsBorder) {
this.mIsBorder = mIsBorder;
this.mIsCircle = mIsCircle;
return this;
}
public ImageLoadBuilder setIsRadius(boolean mIsRadius) {
this.mIsRadius = mIsRadius;
return this;
}
public ImageLoadBuilder setIsRadius(boolean mIsRadius, float mRadius) {
this.mRadius = mRadius;
return setIsRadius(mIsRadius);
}
public ImageLoadBuilder setRadius(float mRadius) {
this.mRadius = mRadius;
return this;
}
public ImageLoadBuilder setResizeOptions(ResizeOptions mResizeOptions) {
this.mResizeOptions = mResizeOptions;
return this;
}
public ImageLoadBuilder setControllerListener(ControllerListener mControllerListener) {
this.mControllerListener = mControllerListener;
return this;
}
}
把真正图片加载实现类抽象成一个类。
/**
* Created by LiCola on 2016/01/16 15:26
* 封装的Fresco图片加载类
* 使用构造器传入配置好的值
*/
public class ImageLoadFresco {
private static final String TAG = "ImageLoadFresco";
//必要参数
private SimpleDraweeView mSimpleDraweeView;
private Context mContext;
/**
* 私有化的构造函数 得到builder的参数 构造对象
*
* @param frescoBuilder 构造器
*/
ImageLoadFresco(ImageLoadBuilder frescoBuilder) {
this.mContext = frescoBuilder.mContext;
this.mSimpleDraweeView = frescoBuilder.mSimpleDraweeView;
//初始化M层 用于初始化图片中包含的数据
GenericDraweeHierarchyBuilder builderM = new GenericDraweeHierarchyBuilder(mContext.getResources());
//请求参数 主要配置url 和C层相关
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(frescoBuilder.mUrl))
.setResizeOptions(frescoBuilder.mResizeOptions)
.build();
//初始化C层 用于控制图片的加载 是主要的实现控制类
PipelineDraweeControllerBuilder builderC = Fresco.newDraweeControllerBuilder()
// .setOldController(mSimpleDraweeView.getController())
;
if (frescoBuilder.mUrlLow != null) {
builderC.setLowResImageRequest(ImageRequest.fromUri(frescoBuilder.mUrlLow));
}
builderC.setImageRequest(request);
setViewPerformance(frescoBuilder, builderM, builderC);
if (frescoBuilder.mControllerListener != null) {
builderC.setControllerListener(frescoBuilder.mControllerListener);
}
DraweeController draweeController = builderC.build();
if (frescoBuilder.mBitmapDataSubscriber != null) {
ImagePipeline imagePipeline = Fresco.getImagePipeline();
DataSource> dataSource =
imagePipeline.fetchDecodedImage(request, mSimpleDraweeView.getContext());
dataSource.subscribe(frescoBuilder.mBitmapDataSubscriber, CallerThreadExecutor.getInstance());
}
mSimpleDraweeView.setHierarchy(builderM.build());
mSimpleDraweeView.setController(draweeController);
}
/**
* 配置DraweeView的各种表现效果
* 如 失败图 重试图 圆角或圆形
*
* @param frescoBuilder
* @param builderM
* @param builderC
*/
private void setViewPerformance(ImageLoadBuilder frescoBuilder, GenericDraweeHierarchyBuilder builderM, PipelineDraweeControllerBuilder builderC) {
//设置图片的缩放形式
builderM.setActualImageScaleType(frescoBuilder.mActualImageScaleType);
if (frescoBuilder.mActualImageScaleType == ScalingUtils.ScaleType.FOCUS_CROP) {
builderM.setActualImageFocusPoint(new PointF(0f, 0f));
}
;
if (frescoBuilder.mPlaceHolderImage != null) {
// builderM.setPlaceholderImage(ContextCompat.getDrawable(mContext, R.drawable.ic_account_circle_gray_48dp), ScalingUtils.ScaleType.CENTER);
builderM.setPlaceholderImage(frescoBuilder.mPlaceHolderImage, ScalingUtils.ScaleType.CENTER);
}
if (frescoBuilder.mProgressBarImage != null) {
Drawable progressBarDrawable = new AutoRotateDrawable(frescoBuilder.mProgressBarImage, 2000);
builderM.setProgressBarImage(progressBarDrawable);
//// TODO: 2016/3/18 0018 直接设置无效 是自定义Drawable setColor知识为了类里面的取值
// MyProgressBarDrawable progressBarDrawable=new MyProgressBarDrawable();
// builderM.setProgressBarImage(progressBarDrawable);
}
//设置重试图 同时需要C层支持点击控制
if (frescoBuilder.mRetryImage != null) {
builderC.setTapToRetryEnabled(true);
builderM.setRetryImage(frescoBuilder.mRetryImage);
}
if (frescoBuilder.mFailureImage != null) {
builderM.setFailureImage(frescoBuilder.mFailureImage);
}
if (frescoBuilder.mBackgroundImage != null) {
builderM.setBackground(frescoBuilder.mBackgroundImage);
}
if (frescoBuilder.mIsCircle) {
if (frescoBuilder.mIsBorder) {
//默认白色包边
builderM.setRoundingParams(RoundingParams.asCircle().setBorder(0xFFFFFFFF, 2));
} else {
builderM.setRoundingParams(RoundingParams.asCircle());
}
// builderM.setRoundingParams(RoundingParams.asCircle());
}
//如果圆角取默认值10 或者是已经修改过的mRadius值
if (frescoBuilder.mIsRadius) {
builderM.setRoundingParams(RoundingParams.fromCornersRadius(frescoBuilder.mRadius));
}
}
}
在使用Fresco存在这样一个问题,把图片缓存封装得太好,导致很难直接得到缓存中的Bitmap对象,当然也不是不能。
根据:数据源和数据订阅者文中说明的代码。
我也在上面的封装代码中加入了获取Bitmap对象的回调功能。使用如下在回调中得到对象。
ImageLoadBuilder.Start(getApplicationContext(), mImageUser, url)
.setBitmapDataSubscriber(new BaseBitmapDataSubscriber() {
@Override
protected void onNewResultImpl(Bitmap bitmap) {
if (bitmap == null) {
Logger.d("bitmap is null");
} else {
Logger.d("bitmap is not null");
Drawable backDrawable = new BitmapDrawable(getResources(), FastBlurUtil.doBlur(bitmap, 25, false));
if (Looper.getMainLooper() != Looper.myLooper()) {
mAppBar.post(new Runnable() {
@Override
public void run() {
mAppBar.setBackground(backDrawable);
}
});
} else {
mAppBar.setBackground(backDrawable);
}
}
}
@Override
protected void onFailureImpl(DataSource> dataSource) {
}
})
.build();
提示:
千万不要把bitmap复制给onNewResultImpl函数范围之外的任何变量。订阅者执行完操作之后,image pipeline 会回收这个bitmap,释放内存。在这个函数范围内再次使用这个Bitmap对象进行绘制将会导致IllegalStateException。
Bitmap对象对象回调线程有可能是在UI主线程回调,也有可能在子线程中回调,如果需要更新UI,需要做判断进行不同的逻辑处理。
我上面的代码,用bitmap对象构造Drawable对象使用是不会发生异常,已经足够我的开发使用,以后想到什么再更新。