Picasso源码完全解析——学习其优秀设计思想

前言

图片加载框架Picasso相信大家都已经用过很多次了,对它们的使用方法也早就熟稔于心了,那么本文就Picasso的源码进行剖析,学习设计者的优秀的代码设计理念和方法。

几个重要的类

在源码解析开始之前,笔者认为有必要对Picasso的几个重要的类进行简单梳理,以便于后面遇到这些组件的时候可以马上知道它的作用是什么。
1、OkHttp3Downloader
Picasso借助该类来下载图片,并把图片缓存在磁盘空间上。实际上,它用的是OkHttp3这个网络通信库来完成下载任务。我们看看它的构造方法:

  public OkHttp3Downloader(final Context context) {
   
    this(Utils.createDefaultCacheDir(context));
  }

  public OkHttp3Downloader(final File cacheDir) {
   
    this(cacheDir, Utils.calculateDiskCacheSize(cacheDir));
  }

  public OkHttp3Downloader(final File cacheDir, final long maxSize) {
   
    this(new OkHttpClient.Builder().cache(new Cache(cacheDir, maxSize)).build());
    sharedClient = false;
  }

  public OkHttp3Downloader(OkHttpClient client) {
   
    this.client = client;
    this.cache = client.cache();
  }

通过Utils.createDefaultCacheDir(context)方法来创建缓存文件夹,通过Utils.calculateDiskCacheSize(cacheDir)来确定磁盘缓存空间的大小。由此我们可以知道,Picasso利用了OkHttp3的下载机制来缓存图片,并且磁盘缓存的大小也是可以配置的,默认实现是可用空间的2%且不少于5MB.

2、LruCache
如果说OkHttp3Downloader实现了磁盘缓存,那么LruCache则是实现了内存缓存。内存缓存的意义在于避免图片过多地堆积在内存中而导致OOM。这里使用的是Lru算法(Least recently used,最近最少使用算法),该算法可以使得经常使用的图片驻留于内存中,避免了反复从磁盘加载图片而导致内存抖动的问题。

3、PicassoExecutorService
这个实际上是一个线程池,它的主要作用就在于把下载任务分配到各个子线程中去执行。

class PicassoExecutorService extends ThreadPoolExecutor {
   
  private static final int DEFAULT_THREAD_COUNT = 3;

  PicassoExecutorService() {
   
    super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
        new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());
  }
}

从构造方法可以看出,该线程池的默认实现是3个核心线程且最大线程数不超过3条。也就是说,默认情况下Picasso在下载图片的时候,最大的同时下载数量是3。但实际上,核心线程和最大线程数是会随着设备的网络状态而改变的,比如WIFI状态下是4条核心线程,而4G状态下是3条核心线程,以此类推。

4、Dispatcher
顾名思义,该类是一个调度器,负责分发、调度和处理Picasso产生的各种事件。在这个调度器内,需要关注的分别是dispatcherThreadhandler这两个成员变量。可以先看一下Dispatcher的构造方法:

Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
      Downloader downloader, Cache cache, Stats stats) {
   
    this.dispatcherThread = new DispatcherThread();
    this.dispatcherThread.start();
    this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
    //省略...
  }

其中,DispatcherThread继承自Thread,是一条子线程;而DispatcherHandler则继承自Handler,熟悉Handler的同学,肯定知道这是用于处理线程间通信的常见方法。由此可知,Dispatcher这个调度器的主要工作都是在DispatcherThread这条线程内完成,而线程切换的任务则是DispatcherHandler来完成。

5、Request
Request封装了有关一次图片请求的所有信息,比如图片的url、图片的变换策略等,这些都是不可更改的信息。举个例子来说,Picasso.get().load(url).centerCrop().rotate(15).into(imageview);上面的调用链,Request会封装centerCrop、rotate等信息。与Request相关的是RequestCreator,它可以看作是一个建造器,配置了图片请求的信息。

6、RequestHandler
上面说到Picasso将图片请求封装成了一个Request,而处理Request的组件则是RequestHandler,因为图片的请求是多种多样的,有的是提供了一个URL从网络获取图片;有的则是提供了一个resourceId,从本地加载图片,不同的请求会有不同的加载方式。因此Picasso提供了多个RequestHandler来应对不同的情况,用户也可以自定义RequestHandler来实现自己的需求,只需要重写canHandleRequest方法和load方法,如下所示:

public abstract class RequestHandler {
   

   public abstract boolean canHandleRequest(Request data);

   @Nullable public abstract Result load(Request request, int networkPolicy) throws IOException;
}

由此看出,Picasso在处理不同的图片请求的时候,将不同请求的实现方式放在了所对应的handler去实现,这样便实现了图片请求和处理请求的解耦合,这样用户自行拓展以适应不同场景下的图片加载需求。

加载图片流程的详细分析

1、Picasso.get()
该方法的调用是一切流程的起点,通过该方法我们可以获取一个Picasso的实例。在Picasso以前的版本,我们是通过Picasso.with(context)的方式来获取实例的,这限制了我们只能在有上下文context的环境下使用Picasso。我们来看看这个方法的实现以及探究下为什么新版本的Picasso不用context这个参数了。

  //代码清单:Picasso#get()
  public static Picasso get() {
   
    if (singleton == null) {
     //第一次判空
      synchronized (Picasso.class) {
   
        if (singleton == null) {
     //上锁后的第二次判空
          if (PicassoProvider.context == null) {
     //确保有context
            throw new IllegalStateException("context == null");
          }
          singleton = new Builder(PicassoProvider.context).build();
        }
      }
    }
    return singleton;
  }

从上面的代码,我们可以看出Picasso使用了DCL(double check lock)形式的单例模式,确保全局只有一个Picasso对象。同时我们注意到context对象是由PicassoProvider.context来提供的,显然PicassoProvider是一个ContentProvider,是Android的四大组件之一,通过它也是可以获取到我们应用的上下文环境的。Picasso通过这样形式的改动,使得Picasso可以适应更多不同的环境,比如在没有context的条件下仅仅利用Picasso进行图片的预下载。

1-1、Picasso实例的构造
Picasso实例的构造是通过构造器模式来进行创建的,Picasso.get()方法获取的是默认配置的Picasso实例,我们也可以通过Picasso.Builder来灵活配置适合我们需求的Picasso实例。我们来看看Picasso.Builder.build()方法,看它是怎样创建一个实例的:

//代码清单1-1:Picasso.Builder#build()
public Picasso build() {
   
      Context context = this.context;

      if (downloader == null) {
   
        downloader = new OkHttp3Downloader(context);
      }
      if (cache == null) {
   
        cache = new LruCache(context);
      }
      if (service == null) {
   
        service = new PicassoExecutorService();
      }
      if (transformer == null
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值