Picasso源码解析 一

前面写的5篇博客让我们对Picasso的用法有了很详细的了解,Android的框架层出不穷,每年都会开源出大量的优秀的框架,如果我们只是站在一个使用者的角度去不断的接触框架,而不去学习框架底层的实现原理,那样顶多也只能一直当新手,而不是老司机了,下面我们就从最基本的入手,从源码的角度去剖析Picasso的工作原理(该分析都是基于Picasso 2.5.2版本).Let’s Go.

官方给出的一个最简单的用法,一个链式调用就能实现一张图片的请求及展示:

 Picasso.with(this).load("http://i.imgur.com/DvpvklR.png").into(target);

只需这一行代码,Picasso就能从我们提供的URL地址中去请求图片,然后将图片展示到targetImageView中,我们就从最简单的开始,一层层的还原Picasso最底层的本质.

Picasso.with(context)

public static Picasso with(Context context) {
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          singleton = new Builder(context).build();
        }
      }
    }
    return singleton;
  }

这段代码就是为了初始化Picasso,获取一个Picasso的实例,该类代码具有很大的共性,很多框架在获取实例时基本都是采用了 单例模式+建造者模式

  • 单例模式特点:
    • 采用后加载机制,保证实例只有在被使用的时候才被创建
    • 采用双重检验锁模式,保证了在多个线程同时请求的过程中,只创建唯一的单例对象
    • 同步对象为类本身,保证了该对象的同步范围在整个java虚拟机中,也就是全局的
  • 利用建造者模式创建一个默认的Picasso对象,一般来说使用建造者模式,是因为构建该对象本身时需要创建其他的类对象,也是保证了多个类的初始化,看代码new Builder(context).build()
 /** Create the {@link Picasso} instance. */
    public Picasso build() {
      Context context = this.context;

      if (downloader == null) {
        downloader = Utils.createDefaultDownloader(context);
      }
      if (cache == null) {
        cache = new LruCache(context);
      }
      if (service == null) {
        service = new PicassoExecutorService();
      }
      if (transformer == null) {
        transformer = RequestTransformer.IDENTITY;
      }

      Stats stats = new Stats(cache);

      picasso.Dispatcher dispatcher = new picasso.Dispatcher(context, service, HANDLER, downloader, cache, stats);

      return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
          defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
    }
  }

在该方法中主要对初始化了以下参数:

  • Downloader
    • 下载工具类,该Utils.createDefaultDownloader(context);如果OKHttp可用的话,会返回一个OkHttpDownloader,否则的话会返回一个UrlConnectionDownloader(内部使用HttpURLConnection实现)
  • Cache

    • 该类的默认实现为LruCache,也就是利用最近最少使用算法进行缓存,内部是一个LinkedHashMap,这里要注意的是,一般我们在初始化LinkedHashMap时,会限制initialCapacity容量值,但此处是在初始化LruCache时通过调用Utils.calculateMemoryCacheSize(context)来计算出最大的堆内存值然后划分出7分之一,也就是15%作为容量大小,所以在每次调用set()方法进行缓存时,都会调用trimToSize()不停的计算当前可使用的空间大小,超出范围就删除之前保存的数据;

      • Utils.calculateMemoryCacheSize(context)的代码块
      static int calculateMemoryCacheSize(Context context) {
      ActivityManager am = getService(context, ACTIVITY_SERVICE);
      //计算出最大的堆内存值,也就是可用大小值
      boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
      int memoryClass = am.getMemoryClass();
      if (largeHeap && SDK_INT >= HONEYCOMB) {
      memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);
      }
      //分配各15%的大小
      // Target ~15% of the available heap.
      return 1024 * 1024 * memoryClass / 7;
      }
  • ExecutorService

    • 默认实现类PicassoExecutorService,也就是ThreadPoolExecutor子类而已,默认线程数是3,但是PicassoExecutorService会在不同的网络状态下调整线程数,wifi下为4个线程,4G状态为3个线程,3G状态下为2个线程,2G状态下为1个线程.
  • RequestTransformer
  • Stats
    • 主要记录一些信息,比如下载的总大小值,缓存大小值,在之前的博客中有用到过,详见Picasso最全使用教程 四,用到了StatsSnapshot
  • Dispatcher

    • 负责分发和处理 Action,包括提交、暂停、继续、取消、网络状态变化、重试等等。这个类是我们着重要分析的类,我们先来看一下默认的操作都有哪些

      • 在构造方法中,将之前的ExecutorService,Downloader,Cache,Stats进行了赋值

      • 在构造方法第一行就创建了DispatcherThread类,启动了一个线程

      • 在构造方法中创建一个DispatcherHandler,并将DispatcherThread.getLooper()作为参数传入该handler的构造函数中,意味着该Handler处理的数据都是从DispatcherThread发出的

      • 初始化并添加了一个对网络监听的广播

上面就是关于.with(context)所做的分析,我们继续向下分析.

.load(String url)方法源码解析

我们现在看.load(String url)方法都做了什么操作,其实.load(url)最后调用的依然是.load(Uri uri)

public RequestCreator load(String path) {
   //对path进行判空,省略部分代码
    ....

    return load(Uri.parse(path));
  }

//其实调用的是这个方法
 public RequestCreator load(Uri uri) {
    return new RequestCreator(this, uri, 0);
  }

所以加载图片的本质就是我们通过Picasso调用了load(url)返回了一个new出来的RequestCreator示例,同时将Picasso ,uri地址, 还有resourceId也就是0,作为构造参数传入,下面我们就看看该构造函数都做了哪些操作;

 RequestCreator(Picasso picasso, Uri uri, int resourceId) {
    if (picasso.shutdown) {
      throw new IllegalStateException(
          "Picasso instance already shut down. Cannot submit new requests.");
    }
    this.picasso = picasso;
    this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
  }
  1. 先判断Picasso是否已经启动,如果没有启动,就直接抛出异常
  2. 对picasso进行赋值
  3. 这里又用到了建造者模式使用Request.Builder()将创建的Builder赋值给data,我们来看该部分的代码

    • 代码如下:

        this.uri = uri;
        this.resourceId = resourceId;
        this.config = bitmapConfig;
      }
      • 我们看到,这里只是做了赋值操作,并没有做其他事,Builder还有其他几个构造方法,也都只进行了赋值操作,并未做其他处理,等我们分析完之后,就会明白此处代码的用意. 贴出来两个,另一个Builder (Request request)就不在贴出,部分代码如下

        public Builder(Uri uri) { setUri(uri); }

        public Builder(int resourceId) { setResourceId(resourceId); }

最重要的方法.into(target)方法解析

.into(target)方法是ReqeustCreator中的方法,所以我们去一窥究竟,其实.into(target)还是在内部调用的.into(target,callback),看代码

 public void into(ImageView target) {
    into(target, null);
  }

下面我们就着重看一下.into(target,callback)都做了哪些操作,代码较多,我们逐一进行分析

public void into(ImageView target, Callback callback) {
    long started = System.nanoTime();
    //1 检测是否在主线程中执行,如果不是就抛异常
    checkMain();

    if (target == null) {
      throw new IllegalArgumentException("Target must not be null.");
    }
    //2 该判断是只有在uri为null,并且resourceId为0的情况下才会执行相应逻辑,取消请求显示默认图片,然后结束
    if (!data.hasImage()) {
      picasso.cancelRequest(target);
      if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
      }
      return;
    }
    //3 是否延时加载,默认为false,只有当调用了 .fit() 才会执行该逻辑
    if (deferred) {
      //调用 .fit() 之前,不能调用 .resize(w,h) ,否则抛出异常
      if (data.hasSize()) {
        throw new IllegalStateException("Fit cannot be used with resize.");
      }
      int width = target.getWidth();
      int height = target.getHeight();
      //target是否已经完全绘制出来,如果没有,则执行相关操作
      if (width == 0 || height == 0) {
        //是否设置了默认图片,在进行延时请求前先展示默认图片
        if (setPlaceholder) {
          setPlaceholder(target, getPlaceholderDrawable());
        }
        // 加入延时队列,同时创建一个DeferredRequestCreator去测量target的宽高,结束任务
        picasso.defer(target, new DeferredRequestCreator(this, target, callback));
        return;
      }
      //target已经绘制完成,调用resize方法,并继续向下执行
      data.resize(width, height);
    }
    //4 创建一个request,并根据request生产一个requestKey
    Request request = createRequest(started);
    String requestKey = createKey(request);
    //5 判断可否从内存中读取资源,默认为可以
    if (shouldReadFromMemoryCache(memoryPolicy)) {
      //从内存中获取,如果能获取到就取消请求操作,并执行callback.onSuccess()的回调,结束任务
      Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
      if (bitmap != null) {
        picasso.cancelRequest(target);
        setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
        if (picasso.loggingEnabled) {
          log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
        }
        if (callback != null) {
          callback.onSuccess();
        }
        return;
      }
    }
    //6 判断是否设置了默认图片,在进行请求前先展示默认图片
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
    //7 这时是的确需要联网请求了,生成对应的 ImageViewAction,并将 action 添加到 Picasso的请求队列中
    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

    picasso.enqueueAndSubmit(action);
  }

部分功能说明已在代码中标出,但还是用一张示意图来进行解析比较简单易懂

图画的比较丑,但是看着还算比较清晰,我们在按照代码,图示,再用文字描述一遍,让更有深的认识;

  1. 检测是否在主线程中执行,如果不是就抛异常,可能是要更新UI显示,所以要在主线程中执行代码逻辑
  2. 该判定方法data.hasImage()容易让人误解为是否已经有图片获取好了,但其实是为了判断在Builder创建的时候,是否设置了uri或者resourceId,如果没有设置uri并且resourceId为0,说明该请求是一个错误请求,就可以加载默认图片并取消请求操作;
  3. 是否延时加载,默认为false,只有当调用了 .fit() 才会执行该逻辑,如果有同学不太理解.fit()的作用,可翻看我之前写的博客,其实就是为了重新计算target的宽高值,而且不能在调用了.resize()之后再次调用.fit()
  4. 创建一个request,并根据request生产一个requestKey
  5. 判断可否从内存中读取资源,默认为true,如果有同学不知道怎么设置跳过内存读取这一步骤,可翻看我之前写的博客
  6. 判断是否设置了默认图片,在进行请求前先展示默认图片
  7. 这时是的确需要联网请求了,生成对应的 ImageViewAction,并将 action 添加到 Picasso的请求队列中

OK,到现在我们已经大致了解了Picasso.with(this).load("http://i.imgur.com/DvpvklR.png").into(target);都做了哪些操作,至于第7步之后又做了哪些操作,我们将继续分析,还原她的本质,愿大家都有美好的一天…

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值