Android图片库Fresco

一、基础用法

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 providercontent://
asset目录下的资源asset://
res目录下的资源res://
Uri中指定图片数据data:mime/type;base64,

二、关键概念

2.1 MVC模式

  Fresco的API类似MVC模式,其中DraweeView负责View展示,DraweeHierarchy负责Drawable数据的维护和组织,DraweeController负责加载图片(图片加载功能由ImagePipeline实现)。

  1. DraweeView:继承于 ImageView,负责图片的显示。 一般情况下,使用 SimpleDraweeView 即可。
  2. DraweeHierarchy:用于组织和维护最终绘制和呈现的 Drawable 对象,相当于MVC中的M。
  3. 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 图片缩放

  1. ScaleType:与ImageView的ScaleType类似,可通过设置SimpleDraweeView的缩放类型ScaleType来控制图片的显示方式,这种方式不会改变Bitmap对象的内存占用大小。
  2. 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);
  1. 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实现图片加载功能。

image.png

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 加载图片的流程如下:

  1. 检查内存缓存,如有则返回;
  2. 检查是否在未解码内存缓存中。如有则解码,变换,返回,然后缓存到内存缓存中。
  3. 检查是否在磁盘缓存中,如果有则变换,返回。缓存到未解码缓存和内存缓存中。
  4. 从网络或者本地加载。加载完成后,解码,变换,返回。存到各个缓存中。
    imagepipeline.png

  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

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值