Picasso-源码解析(三)

作者: javalong | 来源:发表于2018-12-09 12:32 被阅读4次

    前言

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

    public方法

    image.png

    太多了,但是这些方法其实实现都非常的简单,举一个例子。

     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这个对象,然后来看里面的一些配置参数,以及参数的作用。

    image.png

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

    1. buildUpon
     public Builder buildUpon() {
        return new Builder(this);
      }
    

    根据request重新构建出一个新的Request.Builder对象,可以在原来的基础之上再调用链式方法创建新的一个Request.Builder对象。

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

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

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

    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的时候必须设置targetWidthtargetHeight

     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是在图片加载后进行处理。

     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!!!

    相关文章

      网友评论

        本文标题:Picasso-源码解析(三)

        本文链接:https://www.haomeiwen.com/subject/dkothqtx.html