Android Fresco 源码深度剖析:从入门到精通

301 阅读31分钟

一、引言

在当今的 Android 应用开发中,图片处理是一个至关重要的环节。无论是社交媒体应用中的用户头像、新闻资讯类应用中的配图,还是电商应用里的商品图片,都需要高效、稳定的图片加载和显示方案。Fresco 作为 Facebook 开源的一款强大的图片加载库,凭借其卓越的性能和丰富的功能,在 Android 开发社区中得到了广泛的应用和认可。

Fresco 不仅提供了便捷的 API 来实现图片的加载和显示,更重要的是,它在内存管理、图片处理和缓存机制等方面进行了精心的设计和优化。例如,它采用了独特的三级缓存策略,能够有效减少内存占用,避免因图片加载导致的 OOM(Out of Memory)异常;支持多种图片格式,包括常见的 JPEG、PNG 以及动画格式 GIF 和 WebP;还提供了丰富的图片处理功能,如缩放、裁剪、圆角处理等。

本文将深入剖析 Fresco 的源码,从整体架构到各个核心模块,再到具体的实现细节,逐步揭开 Fresco 的神秘面纱。通过对源码的分析,我们不仅能够更好地理解 Fresco 的工作原理,还能学习到其中蕴含的优秀设计思想和编程技巧,从而在实际开发中更加灵活地运用 Fresco,提升应用的性能和用户体验。

二、Fresco 基础介绍

2.1 什么是 Fresco

Fresco 是 Facebook 为 Android 平台开发的一款开源图片加载库,它旨在解决 Android 应用中图片加载和显示的各种问题,提供高效、流畅的图片处理体验。Fresco 的核心优势在于其强大的内存管理机制和丰富的图片处理功能,能够显著提升应用的性能和用户体验。

2.2 Fresco 的主要特性

2.2.1 内存管理优化

Fresco 使用了三级缓存机制,包括内存缓存、磁盘缓存和网络缓存。其中,内存缓存又分为 Bitmap 缓存和未解码的图片数据缓存。在 Android 系统中,图片的内存占用是一个常见的问题,尤其是在处理高分辨率图片时,容易导致 OOM 异常。Fresco 通过合理的缓存策略和内存管理机制,能够有效减少内存占用,提高应用的稳定性。

2.2.2 图片格式支持

Fresco 支持多种图片格式,包括常见的 JPEG、PNG,以及动画格式 GIF 和 WebP。对于 GIF 和 WebP 动画,Fresco 能够流畅地播放,为应用增添更多的趣味性和交互性。

2.2.3 图片处理功能

Fresco 提供了丰富的图片处理功能,如缩放、裁剪、圆角处理、模糊效果等。开发者可以通过简单的配置实现各种复杂的图片显示效果,满足不同的设计需求。

2.2.4 渐进式加载

对于大尺寸的图片,Fresco 支持渐进式加载。在图片下载过程中,Fresco 会逐步显示低质量的图片,随着下载的进行,图片的质量会逐渐提高。这种方式可以让用户更快地看到图片的大致内容,提升用户体验。

2.3 Fresco 的基本使用

2.3.1 添加依赖

在项目的 build.gradle 文件中添加 Fresco 的依赖:

implementation 'com.facebook.fresco:fresco:2.x.x'

2.3.2 初始化

在 Application 类中进行初始化:

import android.app.Application;
import com.facebook.drawee.backends.pipeline.Fresco;

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Fresco.initialize(this);
    }
}

2.3.3 在布局文件中使用 SimpleDraweeView

<com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/my_image_view"
    android:layout_width="200dp"
    android:layout_height="200dp"
    fresco:placeholderImage="@color/gray" />

2.3.4 在代码中加载图片

import com.facebook.drawee.view.SimpleDraweeView;
import android.net.Uri;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SimpleDraweeView draweeView = findViewById(R.id.my_image_view);
        Uri uri = Uri.parse("https://example.com/image.jpg");
        draweeView.setImageURI(uri);
    }
}

三、Fresco 整体架构

graph LR
    classDef client fill:#FFEBEB,stroke:#FF6B6B,stroke-width:2px
    classDef controller fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef dataSource fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
    classDef cache fill:#D5F5E3,stroke:#58D68D,stroke-width:2px
    classDef decoder fill:#F9E79F,stroke:#F1C40F,stroke-width:2px
    classDef imagePipeline fill:#B2EBF2,stroke:#00BCD4,stroke-width:2px
    classDef drawee fill:#FFCCBC,stroke:#FF5722,stroke-width:2px

    A(客户端代码):::client -->|请求图片| B(ImageController):::controller
    B -->|管理请求| C(ImagePipeline):::imagePipeline
    C -->|检查缓存| D(内存缓存):::cache
    D -->|命中| C
    C -->|未命中| E(磁盘缓存):::cache
    E -->|命中| C
    C -->|未命中| F(网络数据源):::dataSource
    F -->|下载图片| C
    C -->|解码图片| G(解码器):::decoder
    G -->|返回解码后图片| C
    C -->|处理图片| B
    B -->|显示图片| H(DraweeView):::drawee
    H -->|展示界面| I(Android 视图系统):::client

3.1 架构概述

Fresco 的整体架构可以分为几个核心模块,包括图片加载管道(Image Pipeline)、图像显示模块(Drawee)、缓存模块(Cache)和图片处理模块(Image Processor)。这些模块相互协作,共同完成图片的加载、解码、处理和显示任务。

3.2 模块关系

3.2.1 图片加载管道(Image Pipeline)

图片加载管道是 Fresco 的核心模块,负责图片的加载、解码、缓存等操作。它采用了管道式的设计,将图片加载过程拆分成多个阶段,每个阶段由一个或多个处理器完成。图片加载管道会根据图片的来源(如网络、本地文件)和缓存策略,决定是从缓存中获取图片还是发起网络请求下载图片。

3.2.2 图像显示模块(Drawee)

图像显示模块主要负责图片的显示和渲染。SimpleDraweeView 是该模块的核心组件,它封装了图片的显示逻辑,提供了丰富的属性和方法,方便开发者进行图片的显示和控制。图像显示模块会根据图片加载管道返回的图片数据,将图片显示在界面上,并处理图片的缩放、裁剪等显示效果。

3.2.3 缓存模块(Cache)

缓存模块实现了三级缓存机制,包括内存缓存、磁盘缓存和网络缓存。内存缓存用于存储解码后的图片和未解码的图片数据,以提高图片的加载速度;磁盘缓存用于存储下载的图片文件,避免重复的网络请求;网络缓存则依赖于网络请求库的缓存机制。缓存模块会根据缓存策略,对图片进行缓存和更新,以减少内存占用和网络流量。

3.2.4 图片处理模块(Image Processor)

图片处理模块提供了各种图片处理功能,如缩放、裁剪、旋转、圆角处理等。开发者可以根据需要自定义图片处理器,对图片进行个性化的处理。图片处理模块会在图片解码完成后,根据配置的图片处理器对图片进行处理,然后将处理后的图片传递给图像显示模块进行显示。

3.3 架构图

+---------------------+
|     Image Pipeline   |
|  (图片加载管道)      |
+---------------------+
        |
        | 图片数据
        v
+---------------------+
|      Drawee         |
|  (图像显示模块)      |
+---------------------+
        |
        | 缓存操作
        v
+---------------------+
|       Cache         |
|  (缓存模块)          |
+---------------------+
        |
        | 图片处理
        v
+---------------------+
|    Image Processor  |
|  (图片处理模块)      |
+---------------------+

四、图片加载管道(Image Pipeline)源码分析

4.1 初始化过程

在 Fresco.initialize 方法中,会调用 ImagePipelineFactory 类的 initialize 方法来初始化图片加载管道:

public static void initialize(Context context) {
    ImagePipelineFactory.initialize(context);
}

ImagePipelineFactory 类负责创建和管理图片加载管道的各个组件,包括缓存、网络请求器、图片解码器等。以下是 ImagePipelineFactory 的 initialize 方法的简化代码:

public static void initialize(Context context) {
    ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
           .setBitmapMemoryCacheParamsSupplier(new DefaultBitmapMemoryCacheParamsSupplier())
           .setEncodedMemoryCacheParamsSupplier(new DefaultEncodedMemoryCacheParamsSupplier())
           .setDiskCacheConfig(DiskCacheConfig.newBuilder(context).build())
           .setNetworkFetcher(new OkHttpNetworkFetcher(new OkHttpClient()))
           .setImageDecoder(new DefaultImageDecoder())
           .build();
    sInstance = new ImagePipelineFactory(config);
}

在上述代码中,ImagePipelineConfig 用于配置图片加载管道的各种参数。DefaultBitmapMemoryCacheParamsSupplier 和 DefaultEncodedMemoryCacheParamsSupplier 分别为 Bitmap 内存缓存和编码后数据的内存缓存提供参数。DiskCacheConfig 用于配置磁盘缓存的相关参数,如缓存路径、缓存大小等。OkHttpNetworkFetcher 利用 OkHttp 作为网络请求库,DefaultImageDecoder 则是默认的图片解码器。通过 ImagePipelineFactory 的构造函数,将配置信息传递给图片加载管道,完成初始化过程。

4.2 图片加载流程

当调用 SimpleDraweeView.setImageURI 方法时,会触发图片加载流程。下面我们逐步分析这个流程。

4.2.1 创建 DraweeController

public void setImageURI(Uri uri) {
    setImageURI(uri, null);
}

public void setImageURI(Uri uri, @Nullable Object callerContext) {
    DraweeController controller = mDraweeControllerBuilder
           .setCallerContext(callerContext)
           .setUri(uri)
           .setOldController(getController())
           .build();
    setController(controller);
}

DraweeController 是图片加载的控制器,它负责协调图片加载管道和图像显示模块之间的交互。DraweeControllerBuilder 用于构建 DraweeController 实例,通过设置 uricallerContext 等参数,创建一个新的 DraweeControllercallerContext 可以携带一些额外的上下文信息,例如调用该加载操作的 Activity 或 Fragment 等,方便后续进行一些特定的处理。

4.2.2 启动图片加载

在 DraweeController 的 build 方法中,会调用图片加载管道的 fetchDecodedImage 方法来启动图片加载流程:

public DraweeController build() {
    ImageRequest imageRequest = mImageRequestBuilder.build();
    ImagePipeline imagePipeline = ImagePipelineFactory.getInstance().getImagePipeline();
    DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(imageRequest, mCallerContext);
    // 创建 DraweeController 实例
    DraweeController draweeController = new PipelineDraweeController(
            mContext,
            mDraweeHierarchy,
            dataSource,
            imageRequest,
            mCallerContext
    );
    return draweeController;
}

4.2.3 缓存查找阶段

public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(ImageRequest imageRequest, Object callerContext) {
    // 从内存缓存中查找
    DataSource<CloseableReference<CloseableImage>> memoryCacheDataSource = mMemoryCache.fetch(imageRequest);
    if (memoryCacheDataSource.hasResult()) {
        return memoryCacheDataSource;
    }

    // 从磁盘缓存中查找
    DataSource<CloseableReference<CloseableImage>> diskCacheDataSource = mDiskCache.fetch(imageRequest);
    if (diskCacheDataSource.hasResult()) {
        return diskCacheDataSource;
    }

    // 若缓存未命中,发起网络请求
    return fetchFromNetwork(imageRequest, callerContext);
}

在缓存查找阶段,图片加载管道会先从内存缓存中查找图片。内存缓存分为两部分,一部分是 Bitmap 内存缓存,用于存储已经解码的 Bitmap 数据;另一部分是编码后数据的内存缓存,用于存储未解码的图片数据。mMemoryCache.fetch 方法会根据 ImageRequest 中的信息,在内存缓存中查找对应的图片数据。如果内存缓存命中,直接返回缓存中的图片数据;如果内存缓存未命中,再从磁盘缓存中查找。磁盘缓存是将下载的图片文件存储在本地磁盘上,mDiskCache.fetch 方法会根据 ImageRequest 中的 URL 等信息,在磁盘缓存中查找对应的图片文件。如果磁盘缓存命中,将磁盘缓存中的图片数据进行解码后返回;如果磁盘缓存也未命中,就会发起网络请求来下载图片。

4.2.4 网络请求阶段

private DataSource<CloseableReference<CloseableImage>> fetchFromNetwork(ImageRequest imageRequest, Object callerContext) {
    NetworkFetcher<FetchState> networkFetcher = mNetworkFetcher;
    FetchState fetchState = new FetchState(imageRequest, callerContext);
    return networkFetcher.fetch(fetchState, new FetchCallback() {
        @Override
        public void onFetchCompletion(FetchState fetchState, int byteSize) {
            // 处理图片下载完成事件
            processDownloadedImage(fetchState, byteSize);
        }

        @Override
        public void onCancellation(FetchState fetchState) {
            // 处理图片下载取消事件
        }

        @Override
        public void onFailure(FetchState fetchState, Throwable throwable) {
            // 处理图片下载失败事件
        }
    });
}

在网络请求阶段,图片加载管道会使用配置的网络请求器(如 OkHttpNetworkFetcher)来发起网络请求。FetchState 封装了请求的状态信息,包括请求的 ImageRequest 对象、请求的上下文信息等。FetchCallback 用于处理请求的回调事件,包括下载完成、取消和失败等。当网络请求完成后,会调用 onFetchCompletion 方法,在该方法中会调用 processDownloadedImage 方法对下载的图片数据进行处理。

4.2.5 图片解码阶段

当图片下载完成后,会进入图片解码阶段:

private DataSource<CloseableReference<CloseableImage>> decodeImage(EncodedImage encodedImage, ImageRequest imageRequest) {
    ImageDecoder imageDecoder = mImageDecoder;
    return imageDecoder.decode(encodedImage, imageRequest.getImageDecodeOptions());
}

ImageDecoder 负责将下载的编码后的图片数据解码为 CloseableImage 对象。ImageDecodeOptions 包含了解码的相关参数,如图片的缩放比例、颜色模式等。DefaultImageDecoder 是默认的图片解码器,它会根据图片的格式(如 JPEG、PNG 等)选择合适的解码方式进行解码。

4.2.6 缓存更新阶段

解码完成后,会将图片存入内存缓存和磁盘缓存:

private void updateCache(CloseableReference<CloseableImage> imageRef, ImageRequest imageRequest) {
    mMemoryCache.put(imageRequest, imageRef);
    mDiskCache.put(imageRequest, EncodedImage.fromCloseableReference(imageRef));
}

通过 mMemoryCache.put 方法将解码后的图片存入内存缓存,通过 mDiskCache.put 方法将编码后的图片数据存入磁盘缓存。这样,下次请求相同的图片时,就可以直接从缓存中获取,提高加载速度。

4.3 核心类和接口分析

4.3.1 ImagePipeline

ImagePipeline 是图片加载管道的核心类,它负责管理图片的加载、解码、缓存等操作。主要方法包括 fetchDecodedImagefetchEncodedImage 等,用于启动图片加载流程。ImagePipeline 内部维护了各种组件的引用,如缓存、网络请求器、图片解码器等,通过协调这些组件的工作,完成图片的加载任务。

4.3.2 ImageRequest

ImageRequest 封装了图片的请求信息,包括图片的 URL、缓存策略、图片处理参数等。它是图片加载管道的输入参数,不同的请求信息会影响图片的加载和处理方式。例如,通过设置不同的缓存策略,可以决定是否从缓存中获取图片,以及是否将下载的图片存入缓存。

4.3.3 DataSource

DataSource 是一个抽象接口,用于表示数据源。在图片加载过程中,DataSource 可以表示从缓存中获取的图片数据,也可以表示从网络下载的图片数据。它提供了获取数据、监听数据变化等方法。DataSource 的实现类通常会使用 CloseableReference 来管理图片数据的引用,确保数据的安全释放。

4.3.4 NetworkFetcher

NetworkFetcher 是一个接口,用于定义网络请求的逻辑。不同的网络请求器(如 OkHttpNetworkFetcher)实现了该接口,负责发起网络请求并处理响应。NetworkFetcher 的 fetch 方法会根据 FetchState 中的请求信息发起网络请求,并通过 FetchCallback 通知请求的结果。

4.3.5 ImageDecoder

ImageDecoder 是一个接口,用于定义图片解码的逻辑。DefaultImageDecoder 是其默认实现,负责将编码后的图片数据解码为 CloseableImage 对象。ImageDecoder 会根据图片的格式和 ImageDecodeOptions 中的参数进行解码,确保解码后的图片符合要求。

五、图像显示模块(Drawee)源码分析

5.1 SimpleDraweeView 结构与继承关系

SimpleDraweeView 是 Fresco 中用于显示图片的核心视图组件,它继承自 GenericDraweeViewGenericDraweeView 又继承自 View,这意味着 SimpleDraweeView 本质上是一个 Android 视图,具备视图的基本属性和行为。以下是其类继承结构的代码示例:

public class SimpleDraweeView extends GenericDraweeView {
    public SimpleDraweeView(Context context) {
        super(context);
        init();
    }

    public SimpleDraweeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public SimpleDraweeView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        if (getHierarchy() == null) {
            GenericDraweeHierarchyBuilder builder =
                    new GenericDraweeHierarchyBuilder(getResources());
            GenericDraweeHierarchy hierarchy = builder.build();
            setHierarchy(hierarchy);
        }
    }
}

在上述代码中,SimpleDraweeView 的构造函数会调用父类 GenericDraweeView 的构造函数,并在 init 方法中进行初始化操作。如果当前没有设置 DraweeHierarchy,则会创建一个 GenericDraweeHierarchy 并设置给视图。

5.2 DraweeHierarchy 层次结构详解

5.2.1 概述

DraweeHierarchy 是一个非常重要的概念,它定义了图片显示的层次结构。简单来说,它就像一个图层管理器,将不同状态下的图片(如占位图、实际图片、失败图等)按照一定的层次关系进行管理和显示。常见的 DraweeHierarchy 实现是 GenericDraweeHierarchy

5.2.2 GenericDraweeHierarchy 组成

GenericDraweeHierarchy 包含了多个 Drawable 对象,用于显示不同状态下的图片。以下是其主要组成部分的详细分析:

public class GenericDraweeHierarchy implements DraweeHierarchy {
    private final Drawable mTopLevelDrawable;
    private final Drawable mPlaceholderImage;
    private final Drawable mFailureImage;
    private final Drawable mActualImage;
    private final Drawable mProgressBarImage;
    private final Drawable mRetryImage;

    public GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) {
        mPlaceholderImage = builder.getPlaceholderImage();
        mFailureImage = builder.getFailureImage();
        mActualImage = new ScaleTypeDrawable(builder.getActualImageDrawable());
        mProgressBarImage = builder.getProgressBarImage();
        mRetryImage = builder.getRetryImage();

        // 构建层次结构
        mTopLevelDrawable = new DraweeDrawable(mActualImage, mPlaceholderImage, mFailureImage, mProgressBarImage, mRetryImage);
    }

    @Override
    public Drawable getTopLevelDrawable() {
        return mTopLevelDrawable;
    }
}
  • 占位图(mPlaceholderImage :在图片加载过程中显示的临时图片,通常用于提示用户图片正在加载。可以通过 GenericDraweeHierarchyBuilder 的 setPlaceholderImage 方法进行设置。
  • 失败图(mFailureImage :当图片加载失败时显示的图片,用于告知用户图片加载出现问题。可以通过 GenericDraweeHierarchyBuilder 的 setFailureImage 方法进行设置。
  • 实际图片(mActualImage :真正要显示的图片,即从网络或本地加载的图片。这里使用 ScaleTypeDrawable 对其进行包装,以支持不同的缩放类型。
  • 进度条图片(mProgressBarImage :在图片加载过程中显示的进度条图片,用于实时反馈图片加载的进度。可以通过 GenericDraweeHierarchyBuilder 的 setProgressBarImage 方法进行设置。
  • 重试图(mRetryImage :当图片加载失败时,点击该图片可以触发重试操作。可以通过 GenericDraweeHierarchyBuilder 的 setRetryImage 方法进行设置。

5.2.3 层次结构的构建

GenericDraweeHierarchy 通过 DraweeDrawable 来构建层次结构。DraweeDrawable 会根据图片的加载状态,动态地显示不同的 Drawable 对象。例如,在图片加载过程中,会显示占位图和进度条图片;当图片加载成功时,会显示实际图片;当图片加载失败时,会显示失败图和重试图。

5.3 图片显示流程

5.3.1 设置图片 URI

当调用 SimpleDraweeView.setImageURI 方法时,会触发一系列的操作,最终实现图片的显示。以下是 setImageURI 方法的简化代码:

public void setImageURI(Uri uri) {
  setImageURI(uri, null);
}

public void setImageURI(Uri uri, @Nullable Object callerContext) {
  DraweeController controller = mDraweeControllerBuilder
         .setCallerContext(callerContext)
         .setUri(uri)
         .setOldController(getController())
         .build();
  setController(controller);
}

在这个方法中,首先会使用 DraweeControllerBuilder 构建一个 DraweeController,并将图片的 URI 和调用上下文信息传递给它。然后将构建好的 DraweeController 设置给 SimpleDraweeView

5.3.2 DraweeController 的作用

DraweeController 是图片加载和显示的控制器,它负责协调图片加载管道和图像显示模块之间的交互。当 DraweeController 接收到图片加载完成的通知后,会将解码后的图片数据传递给 DraweeHierarchy 进行显示。以下是 DraweeController 处理图片加载完成事件的简化代码:

public class PipelineDraweeController extends AbstractDraweeController {
    @Override
    protected void onNewResultImpl(ImageInfo imageInfo, CloseableReference<CloseableImage> imageRef) {
        super.onNewResultImpl(imageInfo, imageRef);
        if (imageInfo != null && imageRef != null) {
            mDraweeHierarchy.setImage(imageRef, 1f, true);
        }
    }
}

在 onNewResultImpl 方法中,当图片加载完成并获取到图片信息和图片引用后,会调用 DraweeHierarchy 的 setImage 方法将图片显示出来。

5.3.3 DraweeHierarchy 显示图片

DraweeHierarchy 的 setImage 方法会更新图片显示层,具体代码如下:

public void setImage(CloseableReference<CloseableImage> imageRef, float progress, boolean immediate) {
    mActualImageWrapper.setImage(imageRef);
    // 更新其他显示层
    if (mProgressBarImage != null) {
        mProgressBarImage.setVisible(false, immediate);
    }
    if (mPlaceholderImage != null) {
        mPlaceholderImage.setVisible(false, immediate);
    }
    if (mFailureImage != null) {
        mFailureImage.setVisible(false, immediate);
    }
    if (mRetryImage != null) {
        mRetryImage.setVisible(false, immediate);
    }
    mActualImage.setVisible(true, immediate);
    invalidateSelf();
}

在这个方法中,首先会将实际图片的引用设置给 mActualImageWrapper,然后隐藏占位图、进度条图片、失败图和重试图,显示实际图片。最后调用 invalidateSelf 方法触发视图的重绘,将图片显示在界面上。

5.4 缩放和裁剪效果实现

5.4.1 缩放类型

Fresco 支持多种缩放类型,如 CENTERCENTER_CROPCENTER_INSIDEFIT_CENTER 等。可以通过 GenericDraweeHierarchyBuilder 的 setActualImageScaleType 方法进行设置。以下是设置缩放类型的示例代码:

GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(getResources());
builder.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP);
GenericDraweeHierarchy hierarchy = builder.build();
simpleDraweeView.setHierarchy(hierarchy);

5.4.2 裁剪效果

除了缩放类型,Fresco 还支持裁剪效果,如圆角裁剪、圆形裁剪等。可以通过 RoundingParams 来设置裁剪效果。以下是设置圆角裁剪的示例代码:

RoundingParams roundingParams = RoundingParams.fromCornersRadius(10f);
GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(getResources());
builder.setRoundingParams(roundingParams);
GenericDraweeHierarchy hierarchy = builder.build();
simpleDraweeView.setHierarchy(hierarchy);

在上述代码中,通过 RoundingParams.fromCornersRadius 方法创建一个圆角裁剪参数对象,并将其设置给 GenericDraweeHierarchyBuilder。然后构建 GenericDraweeHierarchy 并设置给 SimpleDraweeView,这样就可以实现圆角裁剪效果。

5.5 动画支持

5.5.1 GIF 和 WebP 动画

Fresco 对 GIF 和 WebP 动画有良好的支持。当加载 GIF 或 WebP 动画时,DraweeController 会自动识别并播放动画。以下是加载 GIF 动画的示例代码:

DraweeController controller = Fresco.newDraweeControllerBuilder()
       .setUri(Uri.parse("https://example.com/animation.gif"))
       .setAutoPlayAnimations(true)
       .build();
simpleDraweeView.setController(controller);

在上述代码中,通过 setAutoPlayAnimations(true) 方法设置动画自动播放。

5.5.2 动画控制

除了自动播放,Fresco 还提供了一些方法来控制动画的播放,如暂停、继续播放等。可以通过 Animatable 接口来实现这些控制。以下是暂停和继续播放动画的示例代码:

DraweeController controller = simpleDraweeView.getController();
Animatable animatable = controller.getAnimatable();
if (animatable != null) {
    if (animatable.isRunning()) {
        animatable.stop();
    } else {
        animatable.start();
    }
}

在上述代码中,通过 getAnimatable 方法获取动画对象,然后根据动画的运行状态进行暂停或继续播放操作。

5.6 事件处理

5.6.1 点击事件

可以为 SimpleDraweeView 设置点击事件监听器,实现点击图片的交互效果。以下是设置点击事件监听器的示例代码:

simpleDraweeView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 处理点击事件
        Toast.makeText(MainActivity.this, "图片被点击了", Toast.LENGTH_SHORT).show();
    }
});

5.6.2 图片加载状态监听

可以通过 ControllerListener 来监听图片的加载状态,如加载开始、加载成功、加载失败等。以下是设置 ControllerListener 的示例代码:

ControllerListener<ImageInfo> controllerListener = new BaseControllerListener<ImageInfo>() {
    @Override
    public void onSubmit(String id, Object callerContext) {
        // 图片加载开始
    }

    @Override
    public void onFinalImageSet(String id, @Nullable ImageInfo imageInfo, @Nullable Animatable anim) {
        // 图片加载成功
    }

    @Override
    public void onFailure(String id, Throwable throwable) {
        // 图片加载失败
    }
};

DraweeController controller = Fresco.newDraweeControllerBuilder()
       .setUri(Uri.parse("https://example.com/image.jpg"))
       .setControllerListener(controllerListener)
       .build();
simpleDraweeView.setController(controller);

在上述代码中,通过 setControllerListener 方法设置 ControllerListener,在不同的回调方法中处理图片加载的不同状态。

通过以上对图像显示模块(Drawee)的源码分析,我们深入了解了 SimpleDraweeView 的结构、DraweeHierarchy 的层次结构、图片显示流程、缩放和裁剪效果实现、动画支持以及事件处理等方面的原理和实现细节。这些知识有助于我们在实际开发中更加灵活地使用 Fresco 进行图片显示和交互。

六、缓存模块(Cache)源码分析

6.1 缓存模块概述

缓存模块是 Fresco 中极为重要的一部分,它采用了三级缓存机制,分别是内存缓存、磁盘缓存和网络缓存。这种多层次的缓存策略能够显著提高图片的加载速度,减少网络请求,降低内存占用,从而提升应用的性能和用户体验。

6.2 内存缓存

6.2.1 内存缓存的类型

Fresco 的内存缓存分为两部分:Bitmap 内存缓存和编码后数据的内存缓存。

  • Bitmap 内存缓存:用于存储已经解码的 Bitmap 数据。当图片被解码后,会将解码后的 Bitmap 存储在该缓存中,这样下次需要显示同一张图片时,就可以直接从缓存中获取解码后的 Bitmap,避免了重复的解码操作,提高了图片的显示速度。
  • 编码后数据的内存缓存:用于存储未解码的图片数据。在图片下载完成后,会将原始的编码数据存储在该缓存中。如果后续需要对图片进行不同的解码操作或者需要重新使用该编码数据,就可以直接从该缓存中获取,减少了网络请求和磁盘 I/O 操作。

6.2.2 内存缓存的实现类:LruMemoryCache

Fresco 中内存缓存的核心实现类是 LruMemoryCache,它基于 LRU(Least Recently Used,最近最少使用)算法实现。LRU 算法的核心思想是,当缓存空间不足时,优先移除最近最少使用的元素。以下是 LruMemoryCache 的部分关键代码:

public class LruMemoryCache<K, V> implements MemoryCache<K, V> {
    private final LinkedHashMap<K, Entry<K, V>> mCache;
    private final int mMaxCacheSize;
    private int mCurrentCacheSize;

    public LruMemoryCache(int maxCacheSize) {
        mMaxCacheSize = maxCacheSize;
        mCache = new LinkedHashMap<K, Entry<K, V>>(0, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<K, Entry<K, V>> eldest) {
                return mCurrentCacheSize > mMaxCacheSize;
            }
        };
    }

    @Override
    public synchronized CloseableReference<V> get(K key) {
        Entry<K, V> entry = mCache.get(key);
        if (entry != null) {
            return entry.valueRef.clone();
        }
        return null;
    }

    @Override
    public synchronized CloseableReference<V> cache(K key, CloseableReference<V> valueRef) {
        Entry<K, V> oldEntry = mCache.get(key);
        if (oldEntry != null) {
            oldEntry.valueRef.close();
        }
        Entry<K, V> newEntry = new Entry<>(key, valueRef.clone());
        mCache.put(key, newEntry);
        mCurrentCacheSize += getSizeInBytes(newEntry.valueRef.get());
        trimToSize(mMaxCacheSize);
        return valueRef;
    }

    private void trimToSize(int maxSize) {
        while (mCurrentCacheSize > maxSize) {
            Map.Entry<K, Entry<K, V>> toEvict = mCache.entrySet().iterator().next();
            mCache.remove(toEvict.getKey());
            mCurrentCacheSize -= getSizeInBytes(toEvict.getValue().valueRef.get());
            toEvict.getValue().valueRef.close();
        }
    }

    private int getSizeInBytes(V value) {
        // 计算元素的大小
        return 0;
    }
}
  • 构造函数:初始化 LinkedHashMap 用于存储缓存元素,并设置最大缓存大小。
  • get 方法:根据键从缓存中获取元素。如果存在,则返回元素的引用;否则返回 null
  • cache 方法:将元素存入缓存。如果该键已经存在,则先移除旧的元素。然后将新元素存入缓存,并更新当前缓存大小。如果缓存大小超过最大缓存大小,则调用 trimToSize 方法进行清理。
  • trimToSize 方法:当缓存大小超过最大缓存大小时,不断移除最近最少使用的元素,直到缓存大小小于等于最大缓存大小。

6.2.3 内存缓存的查找和更新流程

在图片加载过程中,首先会从内存缓存中查找图片。以下是内存缓存查找和更新的流程:

  1. 查找:调用 LruMemoryCache 的 get 方法,根据图片的键(通常是图片的 URL)从缓存中查找元素。如果找到,则返回元素的引用;否则返回 null
  2. 更新:当图片解码完成后,会将解码后的 Bitmap 存入 Bitmap 内存缓存,将编码后的数据存入编码后数据的内存缓存。调用 LruMemoryCache 的 cache 方法,将元素存入缓存,并更新缓存大小。

6.3 磁盘缓存

6.3.1 磁盘缓存的作用

磁盘缓存用于存储下载的图片文件,避免重复的网络请求。当图片需要加载时,首先会从内存缓存中查找,如果内存缓存未命中,则会从磁盘缓存中查找。如果磁盘缓存命中,则直接从磁盘中读取图片文件,进行解码后显示;如果磁盘缓存也未命中,则会发起网络请求下载图片。

6.3.2 磁盘缓存的实现类:DiskStorageCache

Fresco 中磁盘缓存的核心实现类是 DiskStorageCache,它使用 DiskStorage 来管理磁盘上的文件存储。以下是 DiskStorageCache 的部分关键代码:

public class DiskStorageCache implements DiskCache {
    private final DiskStorage mDiskStorage;
    private final EntryEvictionComparatorSupplier mEntryEvictionComparatorSupplier;
    private final CacheEventListener mCacheEventListener;

    public DiskStorageCache(DiskStorage diskStorage, EntryEvictionComparatorSupplier entryEvictionComparatorSupplier, CacheEventListener cacheEventListener) {
        mDiskStorage = diskStorage;
        mEntryEvictionComparatorSupplier = entryEvictionComparatorSupplier;
        mCacheEventListener = cacheEventListener;
    }

    @Override
    public DataSource<EncodedImage> get(ImageRequest imageRequest) {
        CacheKey cacheKey = getCacheKey(imageRequest);
        try {
            DiskStorage.ReadableEntry readableEntry = mDiskStorage.get(cacheKey);
            if (readableEntry != null) {
                return new SimpleDataSource<>(new EncodedImage(readableEntry.getInputStream()));
            }
        } catch (IOException e) {
            // 处理异常
        }
        return DataSources.immediateFailedDataSource(new IOException("Disk cache miss"));
    }

    @Override
    public void put(ImageRequest imageRequest, EncodedImage encodedImage) {
        CacheKey cacheKey = getCacheKey(imageRequest);
        try {
            DiskStorage.WriteableEntry writeableEntry = mDiskStorage.create(cacheKey);
            OutputStream outputStream = writeableEntry.getOutputStream();
            encodedImage.copyToStream(outputStream);
            outputStream.close();
            mCacheEventListener.onWriteSuccess(cacheKey);
        } catch (IOException e) {
            // 处理异常
            mCacheEventListener.onWriteFailure(cacheKey, e);
        }
    }

    private CacheKey getCacheKey(ImageRequest imageRequest) {
        // 生成缓存键
        return null;
    }
}
  • 构造函数:初始化 DiskStorageEntryEvictionComparatorSupplier 和 CacheEventListener
  • get 方法:根据图片请求生成缓存键,然后从磁盘存储中查找对应的文件。如果找到,则将文件的输入流封装成 EncodedImage,并返回一个 DataSource;否则返回一个失败的 DataSource
  • put 方法:根据图片请求生成缓存键,创建一个可写的磁盘存储条目,并将编码后的图片数据写入该条目。如果写入成功,调用 CacheEventListener 的 onWriteSuccess 方法;否则调用 onWriteFailure 方法。

6.3.3 磁盘缓存的查找和更新流程

  1. 查找:调用 DiskStorageCache 的 get 方法,根据图片请求生成缓存键,从磁盘存储中查找对应的文件。如果找到,则将文件的输入流封装成 EncodedImage,并返回一个 DataSource;否则返回一个失败的 DataSource
  2. 更新:当图片下载完成后,会将编码后的图片数据存入磁盘缓存。调用 DiskStorageCache 的 put 方法,根据图片请求生成缓存键,创建一个可写的磁盘存储条目,并将编码后的图片数据写入该条目。

6.4 网络缓存

6.4.1 网络缓存的原理

网络缓存主要依赖于网络请求库的缓存机制。Fresco 通常使用 OkHttp 作为网络请求库,OkHttp 本身提供了强大的网络缓存功能。当发起网络请求时,OkHttp 会根据请求的 URL 和缓存策略,判断是否可以使用缓存的响应。如果可以使用缓存,则直接返回缓存的响应,避免了重复的网络请求;如果缓存失效或不存在,则发起新的网络请求,并将响应结果存入缓存。

6.4.2 配置网络缓存

在使用 Fresco 时,可以通过配置 OkHttpClient 来启用网络缓存。以下是配置网络缓存的示例代码:

File cacheDirectory = new File(context.getCacheDir(), "http_cache");
int cacheSize = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(cacheDirectory, cacheSize);

OkHttpClient okHttpClient = new OkHttpClient.Builder()
       .cache(cache)
       .build();

ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
       .setNetworkFetcher(new OkHttpNetworkFetcher(okHttpClient))
       .build();

Fresco.initialize(context, config);

在上述代码中,首先创建一个 Cache 对象,指定缓存目录和缓存大小。然后将 Cache 对象设置给 OkHttpClient。最后,在初始化 ImagePipelineConfig 时,使用配置好的 OkHttpClient 创建 OkHttpNetworkFetcher,并将其设置给 ImagePipelineConfig

6.5 缓存策略

6.5.1 缓存键的生成

缓存键是用于标识缓存条目的唯一标识符。在 Fresco 中,缓存键通常根据图片的 URL、图片处理参数等信息生成。不同的缓存模块使用不同的缓存键生成策略。例如,内存缓存和磁盘缓存会使用相同的缓存键,以确保能够正确地查找和更新缓存。以下是生成缓存键的示例代码:

public class ImageCacheKeyFactory {
    public static CacheKey getCacheKey(ImageRequest imageRequest) {
        String uri = imageRequest.getSourceUri().toString();
        ImageDecodeOptions decodeOptions = imageRequest.getImageDecodeOptions();
        // 根据 URL 和解码选项生成缓存键
        return new SimpleCacheKey(uri + "_" + decodeOptions.toString());
    }
}

6.5.2 缓存清理

为了避免缓存占用过多的存储空间,Fresco 会定期进行缓存清理操作。内存缓存会根据 LRU 算法,在缓存空间不足时自动移除最近最少使用的元素。磁盘缓存会根据缓存的大小和时间等因素进行清理。例如,可以设置磁盘缓存的最大大小,当磁盘缓存超过该大小时,会移除一些旧的缓存条目。

6.6 缓存模块的整体流程

当发起图片加载请求时,缓存模块的整体流程如下:

  1. 内存缓存查找:首先从 Bitmap 内存缓存中查找解码后的 Bitmap。如果找到,则直接返回该 Bitmap 进行显示;如果未找到,则从编码后数据的内存缓存中查找编码后的数据。

  2. 磁盘缓存查找:如果内存缓存未命中,则从磁盘缓存中查找编码后的图片数据。如果找到,则将其解码后显示,并将解码后的 Bitmap 存入 Bitmap 内存缓存;如果未找到,则发起网络请求。

  3. 网络请求:如果磁盘缓存也未命中,则发起网络请求下载图片。下载完成后,将编码后的数据存入编码后数据的内存缓存和磁盘缓存,将解码后的 Bitmap 存入 Bitmap 内存缓存。

通过以上对缓存模块的源码分析,我们深入了解了 Fresco 的三级缓存机制,包括内存缓存、磁盘缓存和网络缓存的实现原理、查找和更新流程以及缓存策略。这些知识有助于我们在实际开发中更好地利用缓存模块,提高图片加载的性能和效率。

七、图片处理模块(Image Processor)源码分析

7.1 图片处理模块概述

图片处理模块是 Fresco 中一个重要的组成部分,它允许开发者对加载的图片进行各种自定义处理,如缩放、裁剪、旋转、圆角处理、模糊效果等。通过使用图片处理模块,开发者可以轻松实现各种复杂的图片显示效果,满足不同的设计需求。该模块的核心在于其提供了灵活的接口和丰富的内置处理器,同时也支持开发者自定义处理器。

7.2 图片处理器接口 ImageProcessor

7.2.1 接口定义

ImageProcessor 是图片处理模块的核心接口,它定义了图片处理的基本方法。以下是 ImageProcessor 接口的定义:

收起

java

public interface ImageProcessor {
    /**
     * 对输入的图片进行处理
     * @param sourceImage 输入的图片引用
     * @return 处理后的图片引用
     */
    CloseableReference<CloseableImage> process(CloseableReference<CloseableImage> sourceImage);

    /**
     * 获取处理器的名称,用于调试和日志记录
     * @return 处理器的名称
     */
    String getName();
}

7.2.2 接口作用

  • process 方法:该方法接收一个 CloseableReference<CloseableImage> 类型的参数,表示输入的图片。在方法内部,开发者可以实现具体的图片处理逻辑,如缩放、裁剪等操作,并返回处理后的图片引用。
  • getName 方法:返回该处理器的名称,主要用于调试和日志记录,方便开发者识别不同的处理器。

7.3 内置图片处理器

7.3.1 ResizeAndRotateProcessor

ResizeAndRotateProcessor 用于对图片进行缩放和旋转处理。以下是其部分源码分析:

public class ResizeAndRotateProcessor implements ImageProcessor {
    private final int mDesiredWidth;
    private final int mDesiredHeight;
    private final int mRotationAngle;

    public ResizeAndRotateProcessor(int desiredWidth, int desiredHeight, int rotationAngle) {
        mDesiredWidth = desiredWidth;
        mDesiredHeight = desiredHeight;
        mRotationAngle = rotationAngle;
    }

    @Override
    public CloseableReference<CloseableImage> process(CloseableReference<CloseableImage> sourceImage) {
        try {
            // 获取输入图片
            CloseableImage closeableImage = sourceImage.get();
            if (closeableImage instanceof CloseableStaticBitmap) {
                CloseableStaticBitmap staticBitmap = (CloseableStaticBitmap) closeableImage;
                Bitmap sourceBitmap = staticBitmap.getUnderlyingBitmap();

                // 进行缩放操作
                Bitmap resizedBitmap = Bitmap.createScaledBitmap(sourceBitmap, mDesiredWidth, mDesiredHeight, true);

                // 进行旋转操作
                Matrix matrix = new Matrix();
                matrix.postRotate(mRotationAngle);
                Bitmap rotatedBitmap = Bitmap.createBitmap(resizedBitmap, 0, 0, resizedBitmap.getWidth(), resizedBitmap.getHeight(), matrix, true);

                // 创建处理后的图片引用
                CloseableStaticBitmap processedBitmap = new CloseableStaticBitmap(rotatedBitmap, null, ImmutableQualityInfo.FULL_QUALITY, 0);
                return CloseableReference.of(processedBitmap);
            }
        } finally {
            CloseableReference.closeSafely(sourceImage);
        }
        return null;
    }

    @Override
    public String getName() {
        return "ResizeAndRotateProcessor";
    }
}
  • 构造函数:接收期望的宽度、高度和旋转角度作为参数,用于初始化处理器。
  • process 方法:首先判断输入的图片是否为 CloseableStaticBitmap 类型,如果是,则获取其底层的 Bitmap 对象。然后进行缩放操作,使用 Bitmap.createScaledBitmap 方法将图片缩放到指定的宽度和高度。接着进行旋转操作,使用 Matrix 类来实现旋转。最后创建处理后的 CloseableStaticBitmap 对象,并返回其引用。
  • getName 方法:返回处理器的名称 ResizeAndRotateProcessor

7.3.2 BlurPostProcessor

BlurPostProcessor 用于对图片进行模糊处理。其实现原理通常是使用高斯模糊算法。以下是简化的源码分析:

public class BlurPostProcessor implements ImageProcessor {
    private final int mRadius;

    public BlurPostProcessor(int radius) {
        mRadius = radius;
    }

    @Override
    public CloseableReference<CloseableImage> process(CloseableReference<CloseableImage> sourceImage) {
        try {
            CloseableImage closeableImage = sourceImage.get();
            if (closeableImage instanceof CloseableStaticBitmap) {
                CloseableStaticBitmap staticBitmap = (CloseableStaticBitmap) closeableImage;
                Bitmap sourceBitmap = staticBitmap.getUnderlyingBitmap();

                // 创建模糊后的 Bitmap
                Bitmap blurredBitmap = blurBitmap(sourceBitmap, mRadius);

                // 创建处理后的图片引用
                CloseableStaticBitmap processedBitmap = new CloseableStaticBitmap(blurredBitmap, null, ImmutableQualityInfo.FULL_QUALITY, 0);
                return CloseableReference.of(processedBitmap);
            }
        } finally {
            CloseableReference.closeSafely(sourceImage);
        }
        return null;
    }

    private Bitmap blurBitmap(Bitmap sourceBitmap, int radius) {
        // 使用 RenderScript 或其他方法实现高斯模糊
        RenderScript rs = RenderScript.create(context);
        Allocation input = Allocation.createFromBitmap(rs, sourceBitmap);
        Allocation output = Allocation.createTyped(rs, input.getType());
        ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
        script.setRadius(radius);
        script.setInput(input);
        script.forEach(output);
        output.copyTo(sourceBitmap);
        rs.destroy();
        return sourceBitmap;
    }

    @Override
    public String getName() {
        return "BlurPostProcessor";
    }
}
  • 构造函数:接收模糊半径作为参数,用于初始化处理器。
  • process 方法:判断输入的图片类型,获取底层的 Bitmap 对象。调用 blurBitmap 方法对图片进行模糊处理,然后创建处理后的 CloseableStaticBitmap 对象并返回其引用。
  • blurBitmap 方法:使用 RenderScript 实现高斯模糊算法。首先创建 RenderScript 实例,然后创建输入和输出的 Allocation 对象,接着创建 ScriptIntrinsicBlur 脚本并设置模糊半径,最后将处理后的结果复制到输出 Allocation 并返回处理后的 Bitmap
  • getName 方法:返回处理器的名称 BlurPostProcessor

7.4 自定义图片处理器

开发者可以通过实现 ImageProcessor 接口来创建自定义的图片处理器。以下是一个简单的自定义图片处理器示例,用于将图片转换为灰度图:

收起

java

public class GrayScaleProcessor implements ImageProcessor {
    @Override
    public CloseableReference<CloseableImage> process(CloseableReference<CloseableImage> sourceImage) {
        try {
            CloseableImage closeableImage = sourceImage.get();
            if (closeableImage instanceof CloseableStaticBitmap) {
                CloseableStaticBitmap staticBitmap = (CloseableStaticBitmap) closeableImage;
                Bitmap sourceBitmap = staticBitmap.getUnderlyingBitmap();

                // 创建灰度图
                Bitmap grayBitmap = convertToGrayScale(sourceBitmap);

                // 创建处理后的图片引用
                CloseableStaticBitmap processedBitmap = new CloseableStaticBitmap(grayBitmap, null, ImmutableQualityInfo.FULL_QUALITY, 0);
                return CloseableReference.of(processedBitmap);
            }
        } finally {
            CloseableReference.closeSafely(sourceImage);
        }
        return null;
    }

    private Bitmap convertToGrayScale(Bitmap sourceBitmap) {
        int width = sourceBitmap.getWidth();
        int height = sourceBitmap.getHeight();
        Bitmap grayBitmap = Bitmap.createBitmap(width, height, sourceBitmap.getConfig());
        Canvas canvas = new Canvas(grayBitmap);
        ColorMatrix colorMatrix = new ColorMatrix();
        colorMatrix.setSaturation(0);
        ColorMatrixColorFilter filter = new ColorMatrixColorFilter(colorMatrix);
        Paint paint = new Paint();
        paint.setColorFilter(filter);
        canvas.drawBitmap(sourceBitmap, 0, 0, paint);
        return grayBitmap;
    }

    @Override
    public String getName() {
        return "GrayScaleProcessor";
    }
}
  • process 方法:判断输入图片类型,获取底层 Bitmap 对象,调用 convertToGrayScale 方法将图片转换为灰度图,然后创建处理后的 CloseableStaticBitmap 对象并返回其引用。
  • convertToGrayScale 方法:创建一个新的 Bitmap 对象,使用 ColorMatrix 和 ColorMatrixColorFilter 将图片的饱和度设置为 0,从而实现灰度化处理。
  • getName 方法:返回处理器的名称 GrayScaleProcessor

7.5 图片处理流程

在图片解码完成后,会根据图片请求的配置应用相应的图片处理器。以下是图片处理的整体流程:

  1. 获取图片请求的处理器:在 ImageRequest 对象中,可以通过 getImageProcessor 方法获取配置的图片处理器。

ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(uri)
       .setImageProcessor(new ResizeAndRotateProcessor(200, 200, 90))
       .build();
  1. 应用处理器进行处理:在图片解码完成后,调用处理器的 process 方法对图片进行处理。

private DataSource<CloseableReference<CloseableImage>> applyImageProcessor(CloseableReference<CloseableImage> imageRef, ImageRequest imageRequest) {
    ImageProcessor imageProcessor = imageRequest.getImageProcessor();
    if (imageProcessor != null) {
        CloseableReference<CloseableImage> processedImageRef = imageProcessor.process(imageRef);
        return new SimpleDataSource<>(processedImageRef);
    }
    return new SimpleDataSource<>(imageRef);
}

在上述代码中,首先从 ImageRequest 中获取图片处理器,如果处理器不为空,则调用其 process 方法对图片进行处理,并将处理后的图片引用封装成 DataSource 返回;如果处理器为空,则直接返回原始的图片引用。

7.6 处理器链

Fresco 还支持处理器链的概念,即可以将多个图片处理器依次应用于同一张图片。开发者可以通过创建一个实现了 ImageProcessor 接口的复合处理器来实现处理器链。以下是一个简单的处理器链示例:

public class CompositeImageProcessor implements ImageProcessor {
    private final List<ImageProcessor> mProcessors;

    public CompositeImageProcessor(List<ImageProcessor> processors) {
        mProcessors = processors;
    }

    @Override
    public CloseableReference<CloseableImage> process(CloseableReference<CloseableImage> sourceImage) {
        CloseableReference<CloseableImage> currentImage = sourceImage;
        try {
            for (ImageProcessor processor : mProcessors) {
                CloseableReference<CloseableImage> processedImage = processor.process(currentImage);
                CloseableReference.closeSafely(currentImage);
                currentImage = processedImage;
            }
            return currentImage;
        } catch (Exception e) {
            CloseableReference.closeSafely(currentImage);
            return null;
        }
    }

    @Override
    public String getName() {
        StringBuilder name = new StringBuilder();
        for (ImageProcessor processor : mProcessors) {
            name.append(processor.getName()).append(" -> ");
        }
        if (name.length() > 0) {
            name.delete(name.length() - 4, name.length());
        }
        return name.toString();
    }
}
  • 构造函数:接收一个 ImageProcessor 列表作为参数,用于初始化处理器链。

  • process 方法:依次调用列表中每个处理器的 process 方法,将前一个处理器处理后的结果作为下一个处理器的输入,最终返回处理后的图片引用。

  • getName 方法:将所有处理器的名称连接起来,形成一个处理器链的名称。

通过以上对图片处理模块的源码分析,我们深入了解了 ImageProcessor 接口、内置图片处理器、自定义图片处理器的实现方法,以及图片处理的流程和处理器链的概念。这些知识有助于我们在实际开发中灵活运用图片处理模块,实现各种复杂的图片显示效果。