Picasso-源码解析(三)

前言

前面介绍了比较重要的Picasso对象的使用,以及他的一些重要方法,其实还有一个对象也很重要 ---- RequestCreator,在第一篇文章中其实我已经介绍了,RequestCreator的最终是为了生成一个Request对象。java

public方法

image.png

太多了,可是这些方法其实实现都很是的简单,举一个例子。android

public RequestCreator centerCrop() {
    data.centerCrop(Gravity.CENTER);
    return this;
  }

//--------Request.java--------
 public Builder centerCrop(int alignGravity) {
      if (centerInside) {
        throw new IllegalStateException("Center crop can not be used after calling centerInside");
      }
      centerCrop = true;
      centerCropGravity = alignGravity;
      return this;
    }
复制代码

其实RequestCreator的方法每一个都很简单就是用来给Request.Builder对象设置参数的。Request.Builder是用来生成Request的,因此仍是Request这个对象,而后来看里面的一些配置参数,以及参数的做用。canvas

image.png

一个个来介绍下,public方法和属性服务器

  1. buildUpon
public Builder buildUpon() {
    return new Builder(this);
  }
复制代码

根据request从新构建出一个新的Request.Builder对象,能够在原来的基础之上再调用链式方法建立新的一个Request.Builder对象。app

  1. hasSize
public boolean hasSize() {
    return targetWidth != 0 || targetHeight != 0;
  }
复制代码

很简单,就是看你有没有resize 宽高框架

上面是2个方法,下面说属性。ide

  1. centerCrop
public Request build() {
      if (centerInside && centerCrop) {
        throw new IllegalStateException("Center crop and center inside can not be used together.");
      }
      if (centerCrop && (targetWidth == 0 && targetHeight == 0)) {
        throw new IllegalStateException(
            "Center crop requires calling resize with positive width and height.");
      }
      if (centerInside && (targetWidth == 0 && targetHeight == 0)) {
        throw new IllegalStateException(
            "Center inside requires calling resize with positive width and height.");
      }
      if (priority == null) {
        priority = Priority.NORMAL;
      }
...
复制代码

在添加centerCrop的时候必须设置targetWidthtargetHeightpost

Picasso.get().load("http://i.imgur.com/DvpvklR.png")
                .centerCrop()
                .into(ivTest)
复制代码

这样会报错, 应该这样优化

Picasso.get().load("http://i.imgur.com/DvpvklR.png")
                .centerCrop()
                .resize(100,100)
                .into(ivTest)
复制代码

真正实现centerCrop是在图片加载后进行处理。ui

if (data.centerCrop) {
        //获取图片要缩放的宽和图片真正的宽的比例
        float widthRatio =
            targetWidth != 0 ? targetWidth / (float) inWidth : targetHeight / (float) inHeight;
        //获取图片要缩放的高和图片真正的高的比例
        float heightRatio =
            targetHeight != 0 ? targetHeight / (float) inHeight : targetWidth / (float) inWidth;
        float scaleX, scaleY;
        //若是宽的比例大于高的比例,就说明图片在y方向上须要被裁减
        if (widthRatio > heightRatio) {
          int newSize = (int) Math.ceil(inHeight * (heightRatio / widthRatio));
          //裁剪后有个对齐的方向,是上对齐,仍是下对齐,默认是中对齐
          if ((data.centerCropGravity & Gravity.TOP) == Gravity.TOP) {
            drawY = 0;
          } else if ((data.centerCropGravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
            drawY = inHeight - newSize;
          } else {
            drawY = (inHeight - newSize) / 2;
          }
          drawHeight = newSize;
          scaleX = widthRatio;
          scaleY = targetHeight / (float) drawHeight;
        } else if (widthRatio < heightRatio) {
          //这里跟前面是同样的逻辑,只不过是x轴方向上须要被裁减,以及左对齐,仍是右对齐
          int newSize = (int) Math.ceil(inWidth * (widthRatio / heightRatio));
          if ((data.centerCropGravity & Gravity.LEFT) == Gravity.LEFT) {
            drawX = 0;
          } else if ((data.centerCropGravity & Gravity.RIGHT) == Gravity.RIGHT) {
            drawX = inWidth - newSize;
          } else {
            drawX = (inWidth - newSize) / 2;
          }
          drawWidth = newSize;
          scaleX = targetWidth / (float) drawWidth;
          scaleY = heightRatio;
        } else {
          drawX = 0;
          drawWidth = inWidth;
          scaleX = scaleY = heightRatio;
        }
        if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) {
          matrix.preScale(scaleX, scaleY);
        }
      }
复制代码

其实这里的代码不少,可是咱们能够从效果上去看,就是对图片进行了 裁剪和缩放,而后以及对齐。这样就很清楚了,上面代码只是为了获取到。

  • scaleX 图片宽的缩放
  • scaleY 图片高的缩放
  • drawX 根据对齐操做,判断从图片的哪一个点开始绘制
  • drawY 根据对齐操做,判断从图片的哪一个点开始绘制
  1. centerCropGravity 上面已经介绍了,就是centerCrop的对齐的属性

  2. centerInside 其实跟centerCrop差很少,就是对图片的一个适配。

else if (data.centerInside) {
        // Keep aspect ratio if one dimension is set to 0
        float widthRatio =
            targetWidth != 0 ? targetWidth / (float) inWidth : targetHeight / (float) inHeight;
        float heightRatio =
            targetHeight != 0 ? targetHeight / (float) inHeight : targetWidth / (float) inWidth;
        float scale = widthRatio < heightRatio ? widthRatio : heightRatio;
        if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) {
          matrix.preScale(scale, scale);
        }
      }
复制代码

代码其实就很是的简单,分别获取到宽高的缩放比例,而后取最小的,这样保证图片在imageview里面都看获得,而且不变形。

  1. config 配置图片的格式,能够是ARGB_8888,RGB_565等
//---------RequestCreator.java---------
//调用链式方法,可配置,最终实际上是传到`Request`对象中
 public RequestCreator config(@NonNull Bitmap.Config config) {
    data.config(config);
    return this;
  }

//-------RequestHandler.java--------
  static BitmapFactory.Options createBitmapOptions(Request data) {
    final boolean justBounds = data.hasSize();
    final boolean hasConfig = data.config != null;
    BitmapFactory.Options options = null;
    if (justBounds || hasConfig || data.purgeable) {
      options = new BitmapFactory.Options();
      options.inJustDecodeBounds = justBounds;
      options.inInputShareable = data.purgeable;
      options.inPurgeable = data.purgeable;
      if (hasConfig) {
        //最终实际上是在建立 BitmapFactory.Options的时候看成参数传入,这样子生成的图片就会按照配置,若是不对图片质量有很高要求的话能够选择RGB_565,节省了一半的内存
        options.inPreferredConfig = data.config;
      }
    }
    return options;
  }

//---------BitmapFactory.java----------
       /** * If this is non-null, the decoder will try to decode into this * internal configuration. If it is null, or the request cannot be met, * the decoder will try to pick the best matching config based on the * system's screen depth, and characteristics of the original image such * as if it has per-pixel alpha (requiring a config that also does). * * Image are loaded with the {@link Bitmap.Config#ARGB_8888} config by * default. */
        public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;
复制代码

其实呢这里已经说的很清楚了,若是配置config,那么就按你配置的来,若是没有配置,默认是Bitmap.Config.ARGB_8888

  1. hasRotationPivot,rotationDegrees,rotationPivotX,rotationPivotY
//----------Request.java------------
 public Builder rotate(float degrees, float pivotX, float pivotY) {
      rotationDegrees = degrees;
      rotationPivotX = pivotX;
      rotationPivotY = pivotY;
      hasRotationPivot = true;
      return this;
    }
复制代码

其实这里就能够看出4个参数的做用 hasRotationPivot:是否设置了旋转参数 rotationDegrees:旋转角度 rotationPivotX,rotationPivotY:旋转中心点x,y

6.onlyScaleDown

if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) {
          matrix.preScale(scaleX, scaleY);
        }

....
 private static boolean shouldResize(boolean onlyScaleDown, int inWidth, int inHeight, int targetWidth, int targetHeight) {
    return !onlyScaleDown || (targetWidth != 0 && inWidth > targetWidth)
            || (targetHeight != 0 && inHeight > targetHeight);
  }
复制代码

onlyScaleDown,顾名思义,只容许缩小,因此每次在缩放的时候,先判断下。

  1. priority 这个其实就是优先级的意思
//-------Picasso.java--------
 public enum Priority {
    LOW,
    NORMAL,
    HIGH
  }
复制代码

从定义上看是3种优先级,从高到低。在构建Request对象的时候会给默认值

//--------Request.java--------
public Request build() {
     ...
      if (priority == null) {
        priority = Priority.NORMAL;
      }
     ...
复制代码

那么设置这个优先级到底有什么用呢,其实还要看PicassoExecutorService这个关键类

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

这里很是有意思,在构建一个线程池的时候,建立了一个PriorityBlockingQueue有优先级的一个阻塞队列。

@Override
  public Future<?> submit(Runnable task) {
    //每次执行的是一个PicassoFutureTask任务
    PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
    execute(ftask);
    return ftask;
  }

  private static final class PicassoFutureTask extends FutureTask<BitmapHunter> implements Comparable<PicassoFutureTask> {
    private final BitmapHunter hunter;

    PicassoFutureTask(BitmapHunter hunter) {
      super(hunter, null);
      this.hunter = hunter;
    }
    
     //任务提交以后,线程池会一个个进行处理,会对加入的对象作下对比,看哪一个优先级高,就先执行哪一个
    @Override
    public int compareTo(PicassoFutureTask other) {
      Picasso.Priority p1 = hunter.getPriority();
      Picasso.Priority p2 = other.hunter.getPriority();
      return (p1 == p2 ? hunter.sequence - other.hunter.sequence : p2.ordinal() - p1.ordinal());
    }
  }
}
复制代码
  1. purgeable
//----------Request.java---------
public Builder purgeable() {
      purgeable = true;
      return this;
    }
复制代码

其实在第5点的config里面是有涉及到purgeable这个的,其实也是BitmapFactory.Options的一个参数。 这里我把另一个参数合起来一块儿说,就是inInputShareable,这里我把源码的注释贴出,而后解释下。

/** * @deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this is * ignored. * * In {@link android.os.Build.VERSION_CODES#KITKAT} and below, if this * is set to true, then the resulting bitmap will allocate its * pixels such that they can be purged if the system needs to reclaim * memory. In that instance, when the pixels need to be accessed again * (e.g. the bitmap is drawn, getPixels() is called), they will be * automatically re-decoded. * * <p>For the re-decode to happen, the bitmap must have access to the * encoded data, either by sharing a reference to the input * or by making a copy of it. This distinction is controlled by * inInputShareable. If this is true, then the bitmap may keep a shallow * reference to the input. If this is false, then the bitmap will * explicitly make a copy of the input data, and keep that. Even if * sharing is allowed, the implementation may still decide to make a * deep copy of the input data.</p> * * <p>While inPurgeable can help avoid big Dalvik heap allocations (from * API level 11 onward), it sacrifices performance predictability since any * image that the view system tries to draw may incur a decode delay which * can lead to dropped frames. Therefore, most apps should avoid using * inPurgeable to allow for a fast and fluid UI. To minimize Dalvik heap * allocations use the {@link #inBitmap} flag instead.</p> * * <p class="note"><strong>Note:</strong> This flag is ignored when used * with {@link #decodeResource(Resources, int, * android.graphics.BitmapFactory.Options)} or {@link #decodeFile(String, * android.graphics.BitmapFactory.Options)}.</p> */
 @Deprecated
        public boolean inPurgeable;
复制代码

首先这是弃用的一个属性,注释里也说的很清楚,LOLLIPOP及以上版本就无效了,在KITKAT及如下版本,有效。 翻译下: 若是设置为true,那么生成bitmap而去申请的一块内存,会在系统须要内存的时候,被回收。若是说这个bitmap又被调用拿去使用了,那么就跟inInputShareable这个属性有关,若是这个属性设置了false,那么,就会对inputdata作一个深拷贝,若是是true的话,一开始就会先使用input的一个引用,可是最后真正要使用到bitmap的时候,仍是会对inputdata作一次深拷贝。

固然,后面还有最后一句比较关键的话Therefore, most apps should avoid using inPurgeable to allow for a fast and fluid UI. 意思就是说,若是你想你的app比较流畅,比较快,那么你就不要去使用,由于从新去解码一张图片是要时间的,这样极可能就会形成你加载图片的时候白了几秒。

固然,在内存紧张的时候是可使用的。

static BitmapFactory.Options createBitmapOptions(Request data) {
   ...
      options.inInputShareable = data.purgeable;
      options.inPurgeable = data.purgeable;
     ...
    return options;
  }
复制代码

很显然PicassoinInputShareable,inPurgeable绑在了一块儿,要么都true,要么都false

  1. resourceId 若是要直接加载R.mipmap.**,R.drawable.**这种资源的话,这个参数就!=0

  2. stableKey

//key-value存入LruCache中,这里就是key的生成规则
 static String createKey(Request data, StringBuilder builder) {
    //先看有没有设置stableKey,有设置的话,就直接生成跟stableKey有关的一段字符串,后面能够直接使用stableKey,获取到须要的value。
    if (data.stableKey != null) {
      builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
      builder.append(data.stableKey);
    } else if (data.uri != null) {
      //若是有说没有上面的stableKey,可是有uri,那么就直接根据uri生成对应的key
      String path = data.uri.toString();
      builder.ensureCapacity(path.length() + KEY_PADDING);
      builder.append(path);
    } else {
      builder.ensureCapacity(KEY_PADDING);
      builder.append(data.resourceId);
    }
    builder.append(KEY_SEPARATOR);

复制代码

正常状况下,其实你的图片的uri不变的话,直接就uri就够用了,可是若是你图片的uri可能会发生改变,而后自己实际上是一张图片的话,实际上是可使用stableKey。

好比说,你图片存放了三方服务器,而后可使用http://****/x100/y100这种裁减的方式的话,那么就可使用stableKey,由于你自己原图是一张,而后呢正常状况下你可能会下载了不少不通尺寸的图片,而后根据uri保存在LruCache中。

可是这里有一个优化的方式了,设置一个stableKey,只保存一份图片,而后再对图片进行缩放或者裁减,这样就防止存放了不少份不一样尺寸的图片。

  1. targetHeight,targetWidth 当调用了resize方法,从新定义宽高的话,targetHeight,targetWidth!=0 如:
Picasso.get().load("http://i.imgur.com/DvpvklR.png")
                .resize(100,100)
                .into(ivTest)
复制代码
  1. transformations 用来进行图片的一个变换的列表。 好比我要切成一个圆图
Picasso.get().load("http://i.imgur.com/DvpvklR.png")
                .transform(object : Transformation {
                    override fun transform(source: Bitmap?): Bitmap {
                        //从新建立一个新的bitmap
                        val bitmap = Bitmap.createBitmap(source?.width!!, source?.height!!, Bitmap.Config.ARGB_8888)
                        val canvas = Canvas(bitmap)
                        val p = Paint(Paint.ANTI_ALIAS_FLAG)
                        canvas.drawCircle(100f, 100f, 100f, p)
                        p.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
                        canvas.drawBitmap(source, 0f, 0f, p)
                        //这里必需要对原来的bitmap作recycle,否则会报错,后面
                        source?.recycle()
                        return bitmap
                    }
                    override fun key() = "test111"
                })
                .resize(200, 200)
                .into(ivTest)

//-------BitmapHunter.java---------
static Bitmap applyCustomTransformations(List<Transformation> transformations, Bitmap result) {
    for (int i = 0, count = transformations.size(); i < count; i++) {
      final Transformation transformation = transformations.get(i);
      Bitmap newResult;
      try {
        newResult = transformation.transform(result);
      } catch (final RuntimeException e) {
        Picasso.HANDLER.post(new Runnable() {
          @Override public void run() {
            throw new RuntimeException(
                "Transformation " + transformation.key() + " crashed with exception.", e);
          }
        });
        return null;
      }

      if (newResult == null) {
        final StringBuilder builder = new StringBuilder() //
            .append("Transformation ")
            .append(transformation.key())
            .append(" returned null after ")
            .append(i)
            .append(" previous transformation(s).\n\nTransformation list:\n");
        for (Transformation t : transformations) {
          builder.append(t.key()).append('\n');
        }
        Picasso.HANDLER.post(new Runnable() {
          @Override public void run() {
            throw new NullPointerException(builder.toString());
          }
        });
        return null;
      }
      //若是有转换,必需要对原来的bitmap作recycle
      if (newResult == result && result.isRecycled()) {
        Picasso.HANDLER.post(new Runnable() {
          @Override public void run() {
            throw new IllegalStateException("Transformation "
                + transformation.key()
                + " returned input Bitmap but recycled it.");
          }
        });
        return null;
      }
...
复制代码
  1. uri 加载图片的uri,没有什么好介绍的。

总结

相对来讲图片框架中,Picasso是比较简单,比较容易看得懂的,因此,若是想看图片框架源码的话,建议能够从Picasso源码入手,先看到一些图片框架的基本的一些功能,而后能够尝试GlideFresco,那必定会受益不浅的。最终能够本身上手写一份,我就是朝着这个目标前进的。fighting!!!

相关文章
相关标签/搜索