Fresco正传(2):DraweeView分析

本文详细解析了Fresco图片加载框架的核心组件DraweeView及其相关类的作用和工作原理,包括SimpleDraweeView、GenericDraweeView和DraweeView等类的初始化流程及关键方法的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

正文

既然要分析DraweeView,那么就先看一下DraweeView的主要集成体系,然后在详细分析一下都做了些什么事情。

在看潘永强博客的时候,其中他提到:源码的分析分为广度和深度,先广度再深度,但是都要适当,避免陷入无止境的广度和深度的细节中去。

从这么多篇博文来看,源码的分析从上至下、先父类后子类、先构造再细节的分析思路是非常棒的。但是,每个人分析习惯不一样,就我来说更喜欢先从更易接触的类再深入父类、先看类注释再看详细的方法。后续的博客也会基本按照此种思路。

SimpleDraweeView是官方文档中,我们最先接触类,也是最易使用的类。那么就从这个类入手,一步一步分析。先上一下继承体系图,虽不是从上至下的分析,但是也需要充分了解体系结构。

SimpleDraweeViwe体系图.png

好的开发框架,在命名方面往往做的非常好,起到见名知意作用。那么,我们就从自己的开发经验推测一下每个类都做了什么。

SimpleDraweeView是简单控件的意思,既然是简单,就会提供出简单明了、便易使用的方法,我想setImageURI(Uri uri)就是这样一个方法。

GenericDraweeView是通用控件的意思,通过上一篇文章知道,DraweeView体系中,持有了DraweeHierarchyDraweeController的引用,那么默认的东西,会不会是在这个类中提供的?。

DraweeView是什么意思呢?,只从命名来看推测不出来什么。

SimpleDrawee

要分析一个类,就先看他的类注释。

/** 
* This view takes a uri as input and internally builds and sets a controller. 
* This class must be statically initialized in order to be used.
* If you are using the Fresco * image pipeline, use Fresco#initialize to do this. 
*/
  1. 这个控件接收URI作为输入,并在内部建立和设置一个控制器。
  2. 这个类必须被静态初始化才能够被使用,如果你想使用Fresco默认的图片管道来加载图片,请使用Fresco的initlialize去做这件事。

相关注释一定会体现为代码表示,我们很轻松就能够找到做第一件事情的代码:

/**
  * Displays an image given by the uri.
  */
 public void setImageURI(Uri uri, @Nullable Object callerContext) {
     DraweeController controller = mSimpleDraweeControllerBuilder
             .setCallerContext(callerContext)
             .setUri(uri)
             .setOldController(getController())
             .build();
     setController(controller);
 }

通过uri展示一张图片。在其内部,通过mSimpleDraweeControllerBuilder建造了一个DraweeController对象,并通过setController()方法,将控制器设置给了顶层的DraweeView类,当前从目前来看是这个样子的。而一般以Builder结尾的都是使用了建造者模式的类。

这是一个学习设计模式的博客

mSimpleDraweeControllerBuilder是一个成员变量,搜索一下它在哪里被赋值的。

来到了init()方法,此方法被各个构造函数所调用。

private void init() {
    if (isInEditMode()) {
        return;
    }
    Preconditions.checkNotNull(sDraweeControllerBuilderSupplier, "SimpleDraweeView was not initialized!");
    mSimpleDraweeControllerBuilder = sDraweeControllerBuilderSupplier.get();
}

虽然,代码不多,还是值得解释一下。isInEditMode()是判断当前是否是预览模式或者是编辑模式,通常在AndrdioStudio中预览布局时,会碰到自定义控件显示不出来的情况,加上这句话就可以了。

Preconditions.checkNotNull()是Fresco的工具类,从命名来看,就是检查相关引用是否不为空,为空则直接报错.

下面一句则是,从Supplier(提供者、供应商)中获取一个SimpleDraweeContoller的建造器。特别说明的是,Supplier虽然不是某种设计模式之一,但是在Fresco被广泛使用,它可是提供单个对象类型的类,在语义上它可能是一个工厂、建造、或其他任意的东西。

sDraweeControllerBuilderSupplier是一个静态成员变量,它的初始化就联系到了本类所做的第二件事,使用Fresco.initialize(Context)来初始化本类。

既然需要适当在广度上扩展,就接着看一下Fresco类。根据类的注释,可以知道:

  1. 这个类是Fresco的切入点。
  2. 在使用这个类之前,你必须初始化此类。而一种简单初始化的方式就是调用Fresco.initialize(Context)

既然有简单的,那么就有非简单的,一共有两种方式:

/** Initializes Fresco with the default config. */
 public static void initialize(Context context) {
   ImagePipelineFactory.initialize(context);
   initializeDrawee(context);
 }

 /** Initializes Fresco with the specified config. */
 public static void initialize(Context context, ImagePipelineConfig imagePipelineConfig) {
   ImagePipelineFactory.initialize(imagePipelineConfig);
   initializeDrawee(context);
 }

其中提到了默认的配置和指定的配置,这里不做过多的深入,对于指定的配置(也就两个参数的方法),你只需要知道在ImagePipelineConfig中有着大量的参数配置,以保证Fresco框架的可配置化,由于内部参数过多使用了建造者模式来解耦代码。

见名知意,ImagePipelineFactory.initialize(context)就是对配置初始化,这里就先略过了。

这样就引申到了initializeDrawee(Context context)方法:

private static void initializeDrawee(Context context) {
  sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context);
  SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}

这段代码非常重要,它决定了Fresco使用的默认控制器是名为:PipelineDraweeController的类,先简略看一下类注释,该Controller是图片加载器与Model的桥梁,也就是说,如何使用ImagePipeline加载图片,并交给Model都是在这个类中控制的。当然,这也符合MVC模式的设计。

Drawee controller that bridges the image pipeline with {@link SettableDraweeHierarchy}.

接着就调用到了SimpleDraweeView静态初始化的方法。这样,从Fresco初始化到SimpleDraweeView的初始化至调用setImageUri()显示一张图片的流程就基本完整了。

给出一个示意图,这个图既非UML图、也非流程图,仅仅表达一些代码过程,个人觉得使用图形更形象一些:

GenericDraweeView

从类注释入手可以了解,本类的功能主要是通过解析XML的属性来构建出一个通用的Hierarchy并进行设置。此外,还提供了一个设置控件宽高比的方法。

本类比较简单,核心的方法在于inflateHierarchy(Context context, @Nullable AttributeSet attrs)

方法只做的三件事:
1. 解析XML属性
2. 使用建造者模式构建GenericDraweeHierarchy
3. 将Hierarchy设置给DraweeView体系

  private void inflateHierarchy(Context context, @Nullable AttributeSet attrs) {
  Resources resources = context.getResources();

  // 解析XML属性
  if (attrs != null) {
  }

  // 使用建造者模式,构建Hierarchy
  GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(resources);
  // ... ... 

  // 为DraweeView体系设置hierarchy
  setHierarchy(builder.build());
}

到此,值得注意的是。与DraweeView有关的DraweeHierarchyDraweeController均已出现,DraweeHierarchy对应的默认实现类是:GenericDraweeHierarchyDraweeController对应的默认实现类是:PipelineDraweeController

此外,本类做的第二件事,请参考博文:控制宽高比

DraweeView

先上一张图,更简洁的表达目的和想做的事。

还是先从类的注释入手吧。从注释中可以了解到如下几件事:

  1. 该控件用于展示从DraweeHierarchy获取的图像。
  2. 使用此控件之前,应该先为其设置DraweeHierarchy。由于创建一个DraweeHierarchy是一个昂贵的操作,推荐在创建的时候只做一次。
  3. 为了展示一张图片,DraweeController必须被设置。
  4. Note:虽然这个控件是ImageView的直接子类,但是不支持ImageView的众多方法。

在这个类中,即将接触到一个其背后的男人DraweeHolder类,可以说DraweeView类中所做的所有操作,都是直接或者间接和DraweeHolder有关系的。DraweeView从其体系中获取到的DraweeHierarchyDraweeController的信息都被其转交到了DraweeHolder类中。其实,这个就是一个解耦操作。假设,你想要自定一个控件,只想使用DraweeHierarchyDraweeController的功能,难道还自己持有这两个类的引用? Just So So。DraweeHolder帮你省去了很多麻烦。

先看一下DraweeHolder是如何被创建的。

private void init(Context context) {
  ...
  mDraweeHolder = DraweeHolder.create(null, context);
  ...
}

看样子平平无偿,浏览该类的其他方法你会发现,大部分的操作都是将信息转交给DraweeHolder的操作。但是,请不要忽略以下两个方法:

public void setController(@Nullable DraweeController draweeController) {
  mDraweeHolder.setController(draweeController);
  // 重点
  super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());
}
public void setHierarchy(DH hierarchy) {
  mDraweeHolder.setHierarchy(hierarchy);
  // 重点
  super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());
}

注意到了两个重点没?mDraweeHolder.getTopLevelDrawable()获取到的就是DraweeHierarchy.getTopLevelDraweeable()

从之前的分析可以得知,setHierarchy()是先被调用而后才是setController()
通过super.setImageDrawable(mDraweeHolder.getTopLevelDrawable())这句话,就将Fresco的图片显示与ImageView结合了起来,这也是图片能够显示的原因。

如果你自定义控件了,千万不要忘记模仿这两个方法加上同样的语句哦。

既然,DraweeView已经将职责都转交给了DraweeHolder类,那么就来看看DraweeHolder类做了些什么。

还是先从类注释入手:

  1. 这个类持有了DraweeControllerDraweeHierarchy
  2. 这个类便于自定义控件使用

在浏览类的所有方法时,onAttach()onDetach()方法引起了我的注意。

public void onAttach() {
    mEventTracker.recordEvent(Event.ON_HOLDER_ATTACH);
    mIsHolderAttached = true;
    attachOrDetachController();
}

public void onDetach() {
    mEventTracker.recordEvent(Event.ON_HOLDER_DETACH);
    mIsHolderAttached = false;
    attachOrDetachController();
}

通过注释可以知道onAttach()的核心作用是获取DraweeController以显示图像;onDetach()的核心作用是释放用于显示图像的资源。他们都共同调用了attachOrDetachController()方法。

 private void attachOrDetachController() {
     if (mIsHolderAttached && mIsVisible && mIsActivityStarted) {
         attachController();
     } else {
         detachController();
     }
 }

你会发现,当同时满足onAttach()被调用、控件可见时、Activity启动时才会调用attachController()方法,既然需要同时满足这么多条件,想来这一定是个非常重要的方法。

可以说,Fresco核心加载的逻辑都是由这段代码引出的。

private void attachController() {
    if (mIsControllerAttached) {
        return;
    }
    mEventTracker.recordEvent(Event.ON_ATTACH_CONTROLLER);
    mIsControllerAttached = true;
    if (mController != null && mController.getHierarchy() != null) {
        mController.onAttach();
    }
}

private void detachController() {
    if (!mIsControllerAttached) {
        return;
    }
    mEventTracker.recordEvent(Event.ON_DETACH_CONTROLLER);
    mIsControllerAttached = false;
    if (mController != null) {
        mController.onDetach();
    }
}

其中mIsControllerAttached是保证核心逻辑在一个控件中只被执行一次。在attachController()方法中,如果同时满足了DraweeController不为空和DraweeHierarchy不为空,才会调用DraweeControlleronAttach()方法。

到了这里,你是不是非常好奇在DraweeControlleronAttach()的方法中,到底做了怎么样重要的逻辑? (^__^) 嘻嘻……

最后

DraweeView的体系分析就基本完成了,如果觉得对您有帮助,多多留言哦。

github:https://github.com/biezhihua

总览:http://blog.youkuaiyun.com/biezhihua/article/details/49783817

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值