Android开源框架源码鉴赏:Fresco

本文详细介绍了Android开源框架Fresco的图片加载流程、DraweeController与DraweeHierarchy、Producer与Consumer以及缓存机制。Fresco以其完善的内存管理和自定义加载过程受到开发者喜爱。文章通过实例分析了Fresco图片加载的四个关键步骤,并深入探讨了内存缓存和磁盘缓存的实现,包括缓存键设计、缓存插入、获取和删除的逻辑。Fresco的缓存机制包括两级内存缓存和一级磁盘缓存,通过复杂的缓存策略确保高效运行。
摘要由CSDN通过智能技术生成

关于作者

郭孝星,程序员,吉他手,主要从事Android平台基础架构方面的工作,欢迎交流技术方面的问题,可以去我的Github提issue或者发邮件至guoxiaoxingse@163.com与我交流。

文章目录

  • 一 图片加载流程
    • 1.1 初始化Fresco
    • 1.2 获取DataSource
    • 1.3 绑定DraweeController与DraweeHierarchy
    • 1.4 从内存缓存/磁盘缓存/网络获取图片,并设置到对应的Drawable层
  • 二 DraweeController与DraweeHierarchy
    • 2.1 图层的层级构造
    • 2.2 图层的构建流程
  • 三 Producer与Consumer
  • 四 缓存机制
    • 3.1 内存缓存
    • 3.2 磁盘缓存

更多Android开源框架源码分析文章请参见Android open framework analysis

这个系列的文章原来叫做《Android开源框架源码分析》,后来这些优秀开源库的代码看的多了,感觉大佬们代码写的真真美如画👍,所以就更名为《Android开源框架源码鉴赏》了。闲话 不多说,我们进入正题,今天分析的开源库是Fresco。

Fresco是一个功能完善的图片加载框架,在Android开发中有着广泛的应用,那么它作为一个图片加载框架,有哪些特色让它备受推崇呢?

  • 完善的内存管理功能,减少图片对内存的占用,即便在低端机器上也有着不错的表现。
  • 自定义图片加载的过程,可以先显示低清晰度图片或者缩略图,加载完成后再显示高清图,可以在加载的时候缩放和旋转图片。
  • 自定义图片绘制的过程,可以自定义谷中焦点、圆角图、占位图、overlay、进图条。
  • 渐进式显示图片。
  • 支持Gif。
  • 支持Webp。

好,又吹了一波Fresco(人家好像也不给广告费T_T),但是光知道人家好并没有用,我们还需要为什么这么好,怎么实现的,日后在做我们的框架的时候偷师一手,岂不美哉。 Fresco的源码还是比较多的,看起来会比较费劲,但是不怕,Android的系统源码都被我们啃下来了,还怕一个小小的Fresco吗😎。要更好的去理解Fresco的实现,还是要从 整体入手,了解它的模块和层次划分,层层推进,逐个理解,才能达到融会贯通的效果。

由于Fresco比较大,我们先来看一下它的整体结构,有个整体的把握,Fresco的整体架构如下图所示:

👉 点击图片查看大图

  • DraweeView:继承于ImageView,只是简单的读取xml文件的一些属性值和做一些初始化的工作,图层管理交由Hierarchy负责,图层数据获取交由负责。
  • DraweeHierarchy:由多层Drawable组成,每层Drawable提供某种功能(例如:缩放、圆角)。
  • DraweeController:控制数据的获取与图片加载,向pipeline发出请求,并接收相应事件,并根据不同事件控制Hierarchy,从DraweeView接收用户的事件,然后执行取消网络请求、回收资源等操作。
  • DraweeHolder:统筹管理Hierarchy与DraweeHolder。
  • ImagePipeline:Fresco的核心模块,用来以各种方式(内存、磁盘、网络等)获取图像。
  • Producer/Consumer:Producer也有很多种,它用来完成网络数据获取,缓存数据获取、图片解码等多种工作,它产生的结果由Consumer进行消费。
  • IO/Data:这一层便是数据层了,负责实现内存缓存、磁盘缓存、网络缓存和其他IO相关的功能。

纵观整个Fresco的架构,DraweeView是门面,和用户进行交互,DraweeHierarchy是视图层级,管理图层,DraweeController是控制器,管理数据。它们构成了整个Fresco框架的三驾马车。当然还有我们 幕后英雄Producer,所有的脏活累活都是它干的,最佳劳模👍

理解了Fresco整体的架构,我们还有了解在这套矿建里发挥重要作用的几个关键角色,如下所示:

  • Supplier:提供一种特定类型的对象,Fresco里有很多以Supplier结尾的类都实现了这个接口。
  • SimpleDraweeView:这个我们就很熟悉了,它接收一个URL,然后调用Controller去加载图片。该类继承于GenericDraweeView,GenericDraweeView又继承于DraweeView,DraweeView是Fresco的顶层View类。
  • PipelineDraweeController:负责图片数据的获取与加载,它继承于AbstractDraweeController,由PipelineDraweeControllerBuilder构建而来。AbstractDraweeController实现了DraweeController接口,DraweeController 是Fresco的数据大管家,所以的图片数据的处理都是由它来完成的。
  • GenericDraweeHierarchy:负责SimpleDraweeView上的图层管理,由多层Drawable组成,每层Drawable提供某种功能(例如:缩放、圆角),该类由GenericDraweeHierarchyBuilder进行构建,该构建器 将placeholderImage、retryImage、failureImage、progressBarImage、background、overlays与pressedStateOverlay等 xml文件或者Java代码里设置的属性信息都传入GenericDraweeHierarchy中,由GenericDraweeHierarchy进行处理。
  • DraweeHolder:该类是一个Holder类,和SimpleDraweeView关联在一起,DraweeView是通过DraweeHolder来统一管理的。而DraweeHolder又是用来统一管理相关的Hierarchy与Controller
  • DataSource:类似于Java里的Futures,代表数据的来源,和Futures不同,它可以有多个result。
  • DataSubscriber:接收DataSource返回的结果。
  • ImagePipeline:用来调取获取图片的接口。
  • Producer:加载与处理图片,它有多种实现,例如:NetworkFetcherProducer,LocalAssetFetcherProducer,LocalFileFetchProducer。从这些类的名字我们就可以知道它们是干什么的。 Producer由ProducerFactory这个工厂类构建的,而且所有的Producer都是像Java的IO流那样,可以一层嵌套一层,最终只得到一个结果,这是一个很精巧的设计👍
  • Consumer:用来接收Producer产生的结果,它与Producer组成了生产者与消费者模式。

注:Fresco源码里的类的名字都比较长,但是都是按照一定的命令规律来的,例如:以Supplier结尾的类都实现了Supplier接口,它可以提供某一个类型的对象(factory, generator, builder, closure等)。 以Builder结尾的当然就是以构造者模式创建对象的类。

通过上面的描述,想必大家都Fresco有了一个整体的认识,那面对这样庞大的一个库,我们在去分析它的时候需要重点关注哪些点呢?🤔

  1. 图片加载流程
  2. DraweeController与DraweeHierarchy
  3. Producer与Consumer
  4. 缓存机制

👉 注:Fresco里还大量运用各种设计模式,例如:Builder、Factory、Wrapper、Producer/Consumer、Adapter等,在阅读源码的时候,大家也要留心这些设计模式的应用与实践。

接下来我们就带着这4个问题去源码中一探究竟。

一 图片加载流程

至于分析的手段,还是老套路,先从一个简单的例子入手,展示Fresco是如何加载图片的,然后去分析它的图片加载流程,让大家有个整体的理解,然后再逐个去分析Fresco每个 子模块的功能实现。

好,我们先来写一个小例子。

👉 举例

初始化

Fresco.initialize(this); 

加载图片

String url = "https://github.com/guoxiaoxing/android-open-framwork-analysis/raw/master/art/fresco/scenery.jpg";
SimpleDraweeView simpleDraweeView = findViewById(R.id.drawee_view);
simpleDraweeView.setImageURI(Uri.parse(url)); 

我们来看一下它的调用流程,序列图如下所示:

👉 点击图片查看大图

嗯,图看起来有点大,但是不要紧,我们按照颜色将整个流程分为了四大步:

  1. 初始化Fresco。
  2. 获取DataSource。
  3. 绑定Controller与Hierarchy。
  4. 从内存缓存/磁盘缓存/网络获取图片,并设置到对应的Drawable层。

👉 注:Fresco里的类虽多,类名虽长,但都是基于接口和Abstract类的设计,每个模块自成一套继承体系,所以只要掌握了它们的继承关系以及不同模块之间的联系,整个 流程还是比较简单的。

由于序列图设计具体细节,为了辅助理解,我们再提供一张总结新的流程图,如下所示:

👉 点击图片查看大图

接下来,我们就针对这两张图结合具体细节来一一分析。

1.1 初始化Fresco

👉 序列图 1.1 -> 1.11

public class Fresco {
    public static void initialize(
        Context context,
        @Nullable ImagePipelineConfig imagePipelineConfig,
        @Nullable DraweeConfig draweeConfig) {
      //... 重复初始化检验
      try {
        //1. 加载so库,这个主要是一些第三方的native库,例如:giflib,libjpeg,libpng,
        //主要用来做图片解码。
        SoLoader.init(context, 0);
      } catch (IOException e) {
        throw new RuntimeException("Could not initialize SoLoader", e);
      }
      //2. 设置传入的配置参数magePipelineConfig。
      context = context.getApplicationContext();
      if (imagePipelineConfig == null) {
        ImagePipelineFactory.initialize(context);
      } else {
        ImagePipelineFactory.initialize(imagePipelineConfig);
      }
      //3. 初始化SimpleDraweeView。
      initializeDrawee(context, draweeConfig);
    }
  
    private static void initializeDrawee(
        Context context,
        @Nullable DraweeConfig draweeConfig) {
      //构建PipelineDraweeControllerBuilderSupplier对象,并传给SimpleDraweeView。
      sDraweeControllerBuilderSupplier =
          new PipelineDraweeControllerBuilderSupplier(context, draweeConfig);
      SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
    }  
} 

可以发现,Fresco在初始化的过程中,主要做了三件事情:

  1. 加载so库,这个主要是一些第三方的native库,例如:giflib,libjpeg,libpng,主要用来做图片解码。
  2. 设置传入的配置参数magePipelineConfig。
  3. 初始化SimpleDraweeView。

这里面我们需要重点关注三个对象:

  • ImagePipelineConfig:ImagePipeline参数配置。
  • DraweeControllerBuilderSupplier:提供DraweeControllerBuilder用来构建DraweeController。

我们先来看ImagePipelineConfig,ImagePipelineConfig通过建造者模式来构建传递给ImagePipeline的参数,如下所示:

  • Bitmap.Config mBitmapConfig; 图片质量。
  • Supplier mBitmapMemoryCacheParamsSupplier; 内存缓存的配置参数提供者。
  • CountingMemoryCache.CacheTrimStrategy mBitmapMemoryCacheTrimStrategy; 内存缓存的削减策略。
  • CacheKeyFactory mCacheKeyFactory; CacheKey的创建工厂。
  • Context mContext; 上下文环境。
  • boolean mDownsampleEnabled; 是否开启图片向下采样。
  • FileCacheFactory mFileCacheFactory; 磁盘缓存创建工厂。
  • Supplier mEncodedMemoryCacheParamsSupplier; 未解码图片缓存配置参数提供者。
  • ExecutorSupplier mExecutorSupplier; 线程池提供者。
  • ImageCacheStatsTracker mImageCacheStatsTracker; 图片缓存状态追踪器。
  • ImageDecoder mImageDecoder; 图片解码器。
  • Supplier mIsPrefetchEnabledSupplier; 是否开启预加载。
  • DiskCacheConfig mMainDiskCacheConfig; 磁盘缓存配置。
  • MemoryTrimmableRegistry mMemoryTrimmableRegistry; 内存变化监听注册表,那些需要监听系统内存变化的对象需要添加到这个表中类。
  • NetworkFetcher mNetworkFetcher; 下载网络图片,默认使用内置的HttpUrlConnectionNetworkFetcher,也可以自定义。
  • PlatformBitmapFactory mPlatformBitmapFactory; 根据不同的Android版本生成不同的Bitmap的工厂,主要的区别在Bitmap在内存中的位置,Android 5.0以下存储在Ashmem中,Android 5.0以上存在Java Heap中。
  • PoolFactory mPoolFactory; Bitmap池等各种池的构建工厂。
  • ProgressiveJpegConfig mProgressiveJpegConfig; 渐进式JPEG配置。
  • Set mRequestListeners; 请求监听器集合,监听请求过程中的各种事件。
  • boolean mResizeAndRotateEnabledForNetwork; 是否开启网络图片的压缩和旋转。
  • DiskCacheConfig mSmallImageDiskCacheConfig; 磁盘缓存配置
  • ImageDecoderConfig mImageDecoderConfig; 图片解码配置
  • ImagePipelineExperiments mImagePipelineExperiments; Fresco提供的关于Image Pipe的实验性功能。

上述参数基本不需要我们手动配置,除非项目上有定制性的需求。

我们可以发现,在初始化方法的最后调用initializeDrawee()给SimpleDraweeView传入了一个PipelineDraweeControllerBuilderSupplier,这是一个很重要的对象,我们 来看看它都初始化了哪些东西。

public class PipelineDraweeControllerBuilderSupplier implements
    Supplier<PipelineDraweeControllerBuilder> {
    
      public PipelineDraweeControllerBuilderSupplier(
          Context context,
          ImagePipelineFactory imagePipelineFactory,
          Set<ControllerListener> boundControllerListeners,
          @Nullable DraweeConfig draweeConfig) {
        mContext = context;
        //1. 获取ImagePipeline
        mImagePipeline = imagePipelineFactory.getImagePipeline();
    
        if (draweeConfig != null && draweeConfig.getPipelineDraweeControllerFactory() != null) {
          mPipelineDraweeControllerFactory = draweeConfig.getPipelineDraweeControllerFactory();
        } else {
          mPipelineDraweeControllerFactory = new PipelineDraweeControllerFactory();
        }
        //2. 获取PipelineDraweeControllerFactory,并初始化。
        mPipelineDraweeControllerFactory.init(
            context.getResources(),
            DeferredReleaser.getInstance(),
            imagePipelineFactory.getAnimatedDrawableFactory(context),
            UiThreadImmediateExecutorService.getInstance(),
            mImagePipeline.getBitmapMemoryCache(),
            draweeConfig != null
                ? draweeConfig.getCustomDrawableFactories()
                : null,
            draweeConfig != null
                ? draweeConfig.getDebugOverlayEnabledSupplier()
                : null);
        mBoundControllerListeners = boundControllerListeners;
      }
} 

可以发现在这个方法里初始化了两个重要的对象:

  1. 获取ImagePipeline。
  2. 获取PipelineDraweeControllerFactory,并初始化。

这个PipelineDraweeControllerFactory就是用来构建PipelineDraweeController,我们前面说过PipelineDraweeController继承于AbstractDraweeController,用来控制图片 数据的获取和加载,这个PipelineDraweeControllerFactory()的init()方法也是将参数里的遍历传入PipelineDraweeControllerFactory中,用来准备构建PipelineDraweeController。 我们来看一下它都传入哪些东西进去。

  • context.getResources():Android的Resources对象。
  • DeferredReleaser.getInstance():延迟释放资源,等主线程处理完消息后再进行回收。
  • mImagePipeline.getBitmapMemoryCache():已解码的图片缓存。

👉 注:所谓拔出萝卜带出泥,在分析图片加载流程的时候难免会带进来各种各样的类,如果一时理不清它们的关系也没关系,第一步只是要掌握整体的加载流程即可,后面 我们会对这些类逐一分析。

该方法执行完成后调用SimpleDraweeView的initizlize()方法将PipelineDraweeControllerBuilderSupplier对象设置进SimpleDraweeView的静态对象sDraweeControllerBuilderSupplier中 整个初始化流程便完成了。

1.2 获取DataSource

👉 序列图 2.1 -> 2.12

在分析如何生成DataSource之前,我们得先了解什么DataSource。

DataSource是一个接口其实现类是AbstractDataSource,它可以提交数据请求,并能获取progress、fail result与success result等信息,类似于Java里的Future。

DataSource接口如下所示:

public interface DataSource<T> {
  //数据源是否关闭
  boolean isClosed();
  //异步请求的结果
  @Nullable T getResult();
  //是否有结果返回
  boolean hasResult();
  //请求是否结束
  boolean isFinished();
  //请求是否发生错误
  boolean hasFailed();
  //发生错误的原因
  @Nullable Throwable getFailureCause();
  //请求的进度[0, 1]
  float getProgress();
  //结束请求,释放资源。 
  boolean close();
  //发送并订阅请求,等待请求结果。
  void subscribe(DataSubscriber<T> dataSubscriber, Executor executor);
} 

AbstractDataSource实现了DataSource接口,它是一个基础类,其他DataSource类都扩展自该类。AbstractDataSource实现了上述接口里的方法,维护这DataSource的success、progress和fail的状态。 除此之外还有以下DataSource类:

  • AbstractProducerToDataSourceAdapter:继承自AbstractDataSource,包装了Producer取数据的过程,也就是创建了一个Consumer,详细的过程我们后面还会说。
  • CloseableProducerToDataSourceAdapter:继承自AbstractProducerToDataSourceAdapter,实现了closeResult()方法,绘制自己销毁时同时销毁Result,这个是最主要使用的DataSource。
  • ProducerToDataSourceAdapter:没有实现额外的方法,仅仅用于预加载图片。
  • IncreasingQualityDataSource:内部维护一个CloseableProducerToDataSourceAdapter列表,按数据的清晰度从后往前递增,它为列表里的每个DataSour测绑定一个DataSubscriber,该类负责保证 每次获取清晰度更高的数据,获取数据的同时销毁清晰度更低的数据。
  • FirstAvailableDataSource:内部维护一个CloseableProducerToDataSourceAdapter列表,它会返回列表里最先获取数据的DataSource,它为列表里的每个DataSour测绑定一个DataSubscriber,如果 数据加载成功,则将当前成功的DataSource指定为目标DataSource,否则跳转到下一个DataSource继续尝试。
  • SettableDataSource:继承自AbstractDataSource,并将重写settResult()、setFailure()、setProgress()在内部调用父类的相应函数,但是修饰符变成了public(原来是protected)。即使 用SettableDataSource时可以在外部调用这三个函数设置DataSource状态。一般用于在获取DataSource失败时直接产生一个设置为Failure的DataSource。

了解了DataSource,我们再来看看它是如何生成的。

我们知道,在使用Fresco展示图片的时候,只需要调用setImageURI()设置图片URL即可,我们就以这个方法为入口开始分析,如下所示:

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

可以发现,SimpleDraweeView将外面传递的URL数据封装进了DraweeController,并调用mSimpleDraweeControllerBuilder构造了一个DraweeController对象,这个 DraweeController对象实际上就是PipelineDraweeController。

我们来看看它是如何构建的,mSimpleDraweeControllerBuilder由sDraweeControllerBuilderSupplier调用ge

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值