一、基础用法
1.1 添加依赖
首先在build.gradle
文件中添加Fresco依赖:
dependencies {
// fresco核心库;必须依赖
implementation 'com.facebook.fresco:fresco:2.6.0'
// 以下为fresco扩展功能,可根据业务需要选择是否依赖
// 支持 GIF 动图
implementation 'com.facebook.fresco:animated-gif:0.12.0'
// 在 API < 14 上的机器支持 WebP
implementation 'com.facebook.fresco:animated-base-support:0.12.0'
// 支持 WebP 静态图
implementation 'com.facebook.fresco:webpsupport:0.12.0'
// 支持 WebP 动图
implementation 'com.facebook.fresco:animated-webp:0.12.0'
}
1.2 初始化
在加载图片之前,必须先初始化Fresco
类。Fresco.initialize
初始化只调用一次即可,建议在Application
中完成初始化,如下所示:
public class BCApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Fresco初始化方法,方法内提供了默认的ImagePipelineConfig配置,默认打开useNativeCode
Fresco.initialize(this);
}
}
记得在manifest中声明自定义Application:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.bc.example">
<!-- 网络权限,normal权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<application android:name=".BCApplication">
<!-- ...省略 -->
</application>
</manifest>
1.3 加载图片
Fresco中用SimpleDraweeView(继承自ImageView)表示图片控件,首先在布局文件中添加如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="vertical" tools:context=".OpenCVActivity2">
<!-- fresco的 layout_width 和 layout_height不支持 wrap_content,
除非设置了fresco:viewAspectRatio -->
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/fresco_demo_drawee_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
fresco:placeholderImage="@drawable/ic_launcher_background"
/>
</LinearLayout>
最后Fresco加载图片的方法如下:
SimpleDraweeView draweeView = findViewById(R.id.fresco_demo_drawee_view);
Uri uri = Uri.parse("https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d3093e58d6e4434ea30c3f241d5d275f~tplv-k3u1fbpfcp-watermark.image");
// 使用Fresco加载图片
draweeView.setImageURI(uri);
加载结果如下所示,至此已经完成了Fresco的基础用法。
1.4 支持的URI
Fresco支持多种URI格式,注意URI不支持相对路径(例如"…/a.png"),并且必须带上URI的scheme。
类型 | SCHEME |
---|---|
远程图片 | http://, https:// |
本地文件 | file:// |
Content provider | content:// |
asset目录下的资源 | asset:// |
res目录下的资源 | res:// |
Uri中指定图片数据 | data:mime/type;base64, |
二、关键概念
2.1 MVC模式
Fresco的API类似MVC模式,其中DraweeView负责View展示,DraweeHierarchy负责Drawable数据的维护和组织,DraweeController负责加载图片(图片加载功能由ImagePipeline实现)。
- DraweeView:继承于 ImageView,负责图片的显示。 一般情况下,使用 SimpleDraweeView 即可。
- DraweeHierarchy:用于组织和维护最终绘制和呈现的 Drawable 对象,相当于MVC中的M。
- DraweeController:负责和图片加载模块的交互,Fresco中图片加载功能默认由ImagePipeline实现。
2.2 控件属性
Fresco的DraweeView控件支持多种属性,包括占位图、加载失败占位图、进度条、背景、圆角、进场动画、叠加图等。设置DraweeView控件属性有两种方法:
(1)XML属性。以圆角为例,DraweeView控件设置了roundedCornerRadius如下所示:
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/fresco_demo_drawee_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
fresco:roundedCornerRadius="30dp"
/>
(2)自定义DraweeHierarchy。同样以圆角为例,通过给控件设置自定义DraweeHierarchy来实现:
// 圆角参数
RoundingParams roundingParams = RoundingParams.fromCornersRadius(30f);
GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(getResources());
// 自定义DraweeHierarchy
GenericDraweeHierarchy hierarchy = builder.setRoundingParams(roundingParams).build();
Uri uri = Uri.parse("https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d3093e58d6e4434ea30c3f241d5d275f~tplv-k3u1fbpfcp-watermark.image");
// 给控件设置DraweeHierarchy
draweeView.setHierarchy(hierarchy);
draweeView.setImageURI(uri);
此外,也可以在运行时获取控件的DraweeHierarchy来设置圆角:
Uri uri = Uri.parse("https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d3093e58d6e4434ea30c3f241d5d275f~tplv-k3u1fbpfcp-watermark.image");
draweeView.setImageURI(uri);
// 获取控件的DraweeHierarchy
GenericDraweeHierarchy hierarchy = draweeView.getHierarchy();
RoundingParams roundingParams = RoundingParams.fromCornersRadius(30f);
hierarchy.setRoundingParams(roundingParams);
2.3 图片加载控制
Fresco基础用法中通过draweeView.setImageURI(uri)
来加载显示图片,如果需要对加载显示的图片做更多的控制和定制,可通过自定义DraweeController来实现。
// 自定义DraweeController;设置新的Controller时,使用`setOldController`可节省内存分配
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setTapToRetryEnabled(true)
.setOldController(mSimpleDraweeView.getController())
.setControllerListener(listener)
.build();
// 加载图片;DraweeController中已经设置了uri,所以不用再调用draweeView.setImageURI(uri)
mSimpleDraweeView.setController(controller);
通过自定义DraweeController可实现多种对图片加载的控制及定制功能:
2.3.1 多图请求及图片复用
常用于以下场景:(1)设置两个图片URI,一个是低分辨率的缩略图,一个是高分辨率的图;(2)如果本地JPEG图有EXIF的缩略图,支持先返回一个缩略图;(3)当一张图有多个URI时,比如既有本地URI也有网络URI,支持设置优先加载的URI;
// 1. 设置一个低分辨率、一个高分辨率 setLowResImageRequest()
Uri lowResUri, highResUri;
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setLowResImageRequest(ImageRequest.fromUri(lowResUri))
.setImageRequest(ImageRequest.fromUri(highResUri))
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
// 2.设置开启缩略图 setLocalThumbnailPreviewsEnabled()
Uri uri;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setLocalThumbnailPreviewsEnabled(true)
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
// 3.设置优先加载的URI setFirstAvailableImageRequests()
Uri uri1, uri2;
ImageRequest request = ImageRequest.fromUri(uri1);
ImageRequest request2 = ImageRequest.fromUri(uri2);
ImageRequest[] requests = { request1, request2 };
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setFirstAvailableImageRequests(requests)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
2.3.2 渐进式加载
对于渐进式编码的JPEG图片(并不是所有JPEG图片都是渐进式编码),支持开启渐进式加载setProgressiveRenderingEnabled()。
Uri uri;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setProgressiveRenderingEnabled(true)
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
2.3.3 监听下载事件
通过给DraweeController设置ControllerListener来监听图片加载:
ControllerListener controllerListener = new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable anim) {
// 加载完成
}
@Override
public void onIntermediateImageSet(String id, @Nullable ImageInfo imageInfo) {
// 对于渐进式图片,会在每个图片扫描被解码后回调
}
@Override
public void onFailure(String id, Throwable throwable) {
// 加载失败
}
};
Uri uri;
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setControllerListener(controllerListener)
.setUri(uri)
.build();
mSimpleDraweeView.setController(controller);
2.3.4 图片缩放
- ScaleType:与ImageView的ScaleType类似,可通过设置SimpleDraweeView的缩放类型ScaleType来控制图片的显示方式,这种方式不会改变Bitmap对象的内存占用大小。
- ResizeOptions:创建ImageRequest时,可通过设置ResizeOptions改变bitmap的内存占用大小 :
Uri uri = "file:///mnt/sdcard/MyApp/myfile.jpg";
int width = 50, height = 50;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setResizeOptions(new ResizeOptions(width, height))
.build();
PipelineDraweeController controller = Fresco.newDraweeControllerBuilder()
.setOldController(mDraweeView.getController())
.setImageRequest(request)
.build();
mSimpleDraweeView.setController(controller);
- Downsample:实验中的特性,用来代替ResizeOptions。除了支持 JPEG 图片,还支持 PNG 和 WebP(除动画外) 图片。
2.3.5 旋转图片
创建ImageRequest时,可通过设置RotationOptions旋转图片:
final ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(uri)
.setRotationOptions(RotationOptions.forceRotation(RotationOptions.ROTATE_90))
.build();
部分JPEG 文件的 EXIF数据中包含图片的方向,如果对于这类图片需要在解码时自动旋转,可通过如下代码进行设置:
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setAutoRotateEnabled(true)
.build();
2.3.6 图片后处理
创建ImageRequest时,可以设置Postprocessor来对图片进行后处理。Fresco提供了IterativeBoxBlurPostProcessor、BlurPostProcessor等后处理实现,也可以重写BasePostprocessor的process方法来实现自定义的后处理效果(有多个process方法,根据需要重写一个即可),以下是一个灰度后处理的例子:
public class FastGreyScalePostprocessor extends BasePostprocessor {
@Override
public void process(Bitmap bitmap) {
final int w = bitmap.getWidth();
final int h = bitmap.getHeight();
final int[] pixels = new int[w * h];
bitmap.getPixels(pixels, 0, w, 0, 0, w, h);
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
final int offset = y * w + x;
// 设置为灰度值
pixels[offset] = getGreyColor(pixels[offset]);
}
}
bitmap.setPixels(pixels, 0, w, 0, 0, w, h);
}
static int getGreyColor(int color) {
final int alpha = color & 0xFF000000;
final int r = (color >> 16) & 0xFF;
final int g = (color >> 8) & 0xFF;
final int b = color & 0xFF;
final int luminance = (int) (0.2126 * r + 0.7152 * g + 0.0722 * b);
return alpha | luminance << 16 | luminance << 8 | luminance;
}
}
然后,在创建ImageRequest时,设置后处理Postprocessor即可:
Uri uri = Uri.parse("https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d3093e58d6e4434ea30c3f241d5d275f~tplv-k3u1fbpfcp-watermark.image");
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setPostprocessor(new FastGreyScalePostprocessor())
.build();
PipelineDraweeController controller = (PipelineDraweeController)
Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(draweeView.getController())
.build();
draweeView.setController(controller);
运行结果如下图所示:
三、图片加载流程
在Fresco加载图片的基础用法draweeView.setImageURI(uri)
中,图片加载过程由DraweeController负责,DraweeController通过ImagePipeline实现图片加载功能。
3.1 DraweeHolder
DraweeView持有DraweeHolder,DraweeHolder持有DraweeController和 DraweeHierarchy:
public class SimpleDraweeView<DH extends DraweeHierarchy> extends ImageView {
private DraweeHolder<DH> mDraweeHolder;
public SimpleDraweeView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mDraweeHolder = DraweeHolder.create(null, context);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mDraweeHolder.onAttach();
}
public void setImageURI(Uri uri, Object callerContext) {
DraweeController controller = mControllerBuilder
.setCallerContext(callerContext).setUri(uri)
.setOldController(getController()).build();
setController(controller);
}
public void setController(DraweeController draweeController) {
mDraweeHolder.setController(draweeController);
super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());
}
}
DraweeHolder在已经attach到window中时,调用setController()设置DraweeController时,会执行DraweeController的onAttach()方法,执行ImagePipeline图片加载流程:
public class DraweeHolder<DH extends DraweeHierarchy> {
private DH mHierarchy;
private DraweeController mController = null;
public void setController(DraweeController draweeController) {
boolean wasAttached = mIsControllerAttached;
// 省略部分代码
mController = draweeController;
mController.setHierarchy(mHierarchy);
if (wasAttached) {
// 如果设置Controller的时候,View已经Attached到window中,
// 则继续执行DraweeController的onAttach()方法
attachController();
}
}
public void onAttach() {
mIsHolderAttached = true;
// 在View Attached到window中时,也会执行DraweeController的onAttach()方法
attachController();
}
private void attachController() {
if (mIsControllerAttached) {
return;
}
mIsControllerAttached = true;
// 执行DraweeController的onAttach()方法,执行图片加载流程
mController.onAttach();
}
}
3.2 DraweeController
在Fresco的初始化中可以看到,DraweeController的默认实现类是PipelineDraweeController。
public class Fresco {
public static void initialize(Context context) {
initialize(context, null, null);
}
public static void initialize(Context context, ImagePipelineConfig imagePipelineConfig,
DraweeConfig draweeConfig, boolean useNativeCode) {
ImagePipelineFactory.initialize(context);
initializeDrawee(context, draweeConfig);
if (FrescoSystrace.isTracing()) {
FrescoSystrace.endSection();
}
}
private static void initializeDrawee(Context context, @Nullable DraweeConfig draweeConfig) {
sDraweeControllerBuilderSupplier =
new PipelineDraweeControllerBuilderSupplier(context, draweeConfig);
SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}
}
下面接着分析DraweeController的onAttach()方法,来分析图片加载流程:
public class PipelineDraweeController{
public void onAttach() {
if (!mIsRequestSubmitted) {
// 提交图片加载请求
submitRequest();
}
}
/**
* 提交图片加载请求
*/
protected void submitRequest() {
// 1.先从内存缓存中获取图片
final T closeableImage = getCachedImage();
if (closeableImage != null) {
// 2.从内存缓存成功拿到结果,则调用onNewResultInternal()处理
onNewResultInternal(mId, mDataSource, closeableImage, 1.0f, true, true, true);
return;
}
// 3.从数据流DataSource中获取图片;该方法内会提交ImageRequest
mDataSource = getDataSource();
// 4.创建数据流DataSource的结果回调
final DataSubscriber<T> dataSubscriber =
new BaseDataSubscriber<T>() {
@Override
public void onNewResultImpl(DataSource<T> dataSource) {
T image = dataSource.getResult();
if (image != null) {
// 6.DataSubscriber的回调中,如果获取成功,则调用onNewResultInternal()处理
onNewResultInternal(id, dataSource, image, progress, isFinished, wasImmediate, hasMultipleResults);
}
}
@Override
public void onFailureImpl(DataSource<T> dataSource) {
}
@Override
public void onProgressUpdate(DataSource<T> dataSource) {
}
};
// 5.设置数据流DataSource的结果回调
mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor);
}
/**
* 从内存缓存中获取图片
*/
protected CloseableReference<CloseableImage> getCachedImage() {
CloseableReference<CloseableImage> closeableImage = mMemoryCache.get(mCacheKey);
if (closeableImage != null && !closeableImage.get().getQualityInfo().isOfFullQuality()) {
closeableImage.close();
return null;
}
return closeableImage;
}
/**
* 处理图片获取结果
*/
private void onNewResultInternal(String id, DataSource<T> dataSource, T image,
float progress, boolean isFinished, boolean wasImmediate, boolean deliverTempResult) {
// 1.根据结果创建Drawable
Drawable drawable = createDrawable(image);
mFetchedImage = image;
mDrawable = drawable;
// 2.将结果设置给DraweeHierarchy,DraweeHierarchy负责后续的图片渲染等流程
if (isFinished) {
mDataSource = null;
mSettableDraweeHierarchy.setImage(drawable, 1f, wasImmediate);
} else if (deliverTempResult) {
mSettableDraweeHierarchy.setImage(drawable, 1f, wasImmediate);
} else {
mSettableDraweeHierarchy.setImage(drawable, progress, wasImmediate);
}
}
protected DataSource<CloseableReference<CloseableImage>> getDataSource() {
DataSource<CloseableReference<CloseableImage>> result = mDataSourceSupplier.get();
return result;
}
}
3.3 Image Pipeline
在PipelineDraweeController调用getDataSource()获取数据源时,会提交一个Image request,Image pipeline返回一个数据源DataSource,可通过数据订阅者(DataSubscriber)获取数据。Image pipeline 加载图片的流程如下:
- 检查内存缓存,如有则返回;
- 检查是否在未解码内存缓存中。如有则解码,变换,返回,然后缓存到内存缓存中。
- 检查是否在磁盘缓存中,如果有则变换,返回。缓存到未解码缓存和内存缓存中。
- 从网络或者本地加载。加载完成后,解码,变换,返回。存到各个缓存中。
Fresco建议Image Pipeline使用单例模式,默认情况下开发者可以通过如下方式获取ImagePipeline,并实现预加载等功能(如果本地持有Image Pipeline返回的CloseableReference,记得调用CloseableReference.close()避免内存泄漏):
Fresco.getImagePipeline()
// 预加载到磁盘缓存
imagePipeline.prefetchToDiskCache(imageRequest, callerContext);
四、图片展示流程
在DraweeController通过ImagePipeline返回图片后,会调用DraweeController.onNewResultInternal()方法,接着调用到了DraweeHierarchy.setImage()方法,将Drawable绘制到ImageView中:
public class GenericDraweeHierarchy implements SettableDraweeHierarchy {
// 展示的图层,实际的图层由于mFadeDrawable提供
private final RootDrawable mTopLevelDrawable;
// FadeDrawable是一个Drawalbe数组,用来储存各个图层,根据状态来获得不同的图层
private final FadeDrawable mFadeDrawable;
private final ForwardingDrawable mActualImageWrapper;
GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) {
mActualImageWrapper = new ForwardingDrawable(mEmptyActualImageDrawable);
int numLayers = OVERLAY_IMAGES_INDEX + numOverlays;
Drawable[] layers = new Drawable[numLayers];
layers[BACKGROUND_IMAGE_INDEX] = buildBranch(builder.getBackground(), null);
layers[PLACEHOLDER_IMAGE_INDEX] =
buildBranch(builder.getPlaceholderImage(), builder.getPlaceholderImageScaleType());
layers[ACTUAL_IMAGE_INDEX] =
buildActualImageBranch(
mActualImageWrapper,
builder.getActualImageScaleType(),
builder.getActualImageFocusPoint(),
builder.getActualImageColorFilter());
layers[PROGRESS_BAR_IMAGE_INDEX] =
buildBranch(builder.getProgressBarImage(), builder.getProgressBarImageScaleType());
layers[RETRY_IMAGE_INDEX] =
buildBranch(builder.getRetryImage(), builder.getRetryImageScaleType());
layers[FAILURE_IMAGE_INDEX] =
buildBranch(builder.getFailureImage(), builder.getFailureImageScaleType());
layers[OVERLAY_IMAGES_INDEX + index] = buildBranch(builder.getPressedStateOverlay(), null);
// fade drawable composed of layers
mFadeDrawable = new FadeDrawable(layers, false, ACTUAL_IMAGE_INDEX);
mFadeDrawable.setTransitionDuration(builder.getFadeDuration());
// rounded corners drawable (optional)
Drawable maybeRoundedDrawable =
WrappingUtils.maybeWrapWithRoundedOverlayColor(mFadeDrawable, mRoundingParams);
// top-level drawable
mTopLevelDrawable = new RootDrawable(maybeRoundedDrawable);
mTopLevelDrawable.mutate();
}
public void setImage(Drawable drawable, float progress, boolean immediate) {
drawable = WrappingUtils.maybeApplyLeafRounding(drawable, mRoundingParams, mResources);
drawable.mutate();
// 更新drawable后,调用ImageView的invalidateDrawable完成绘制
mActualImageWrapper.setDrawable(drawable);
mFadeDrawable.beginBatchMode();
fadeOutBranches();
fadeInLayer(ACTUAL_IMAGE_INDEX);
setProgress(progress);
if (immediate) {
mFadeDrawable.finishTransitionImmediately();
}
mFadeDrawable.endBatchMode();
}
}
The End
欢迎关注我,一起解锁更多技能:BC的掘金主页~💐 BC的CSDN主页~💐💐
Fresco文档:https://frescolib.org/docs/
Fresco github:https://github.com/facebook/fresco