Glide原理(三):图片解析处理、ImageView保证大小

Glide 怎么判断解析图片的

Glide 怎么保证ImageView宽高?

Glide 怎么判断图片旋转角度

以上三个问题是我自己在做一个图片池遇到的问题,趁机好好学习Glide

Glide解码的类在 Downsampler中,它的注释上写着:

Downsamples, decodes, and rotates images according to their exif orientation.

根据图像文件采样、解码、旋转图像

有几个重要的类:
private final BitmapPool bitmapPool;
Bitmap池,是一个LruCache,内部保留着使用过的Bitmap,重复使用

private final DisplayMetrics displayMetrics;
屏幕信息

private final ArrayPool byteArrayPool;
同样是一个LruCache,最大是4MB,充当一个byte的buffer

private final List parsers;
图像的头部信息解析器,例如图像类型、旋转角度等信息;

ImageHeaderParser有两个实现类:DefaultImageHeaderParser、 ExifInterfaceImageHeaderParser

DefaultImageHeaderParser是Glide自定义的默认的头部解析器

ExifInterfaceImageHeaderParser是支持SDK 27的基于 exifInterface的解析器

private final HardwareConfigState hardwareConfigState = HardwareConfigState.getInstance();
目测跟硬件加速有关,具体还不清楚

计算图像大小、缩放、计算角度、解码、旋转
public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
      Options options, DecodeCallbacks callbacks) throws IOException {

    // 以下具体是做一系列的BitmapFactory.Options的配置
    byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
    BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions(); // 获取Option
    bitmapFactoryOptions.inTempStorage = bytesForOptions; // 设定一个可重复使用的临时存储空间

    DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
    DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
    boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS); 
    boolean isHardwareConfigAllowed =
      options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG); // 是否允许硬件加速

    try {
      // 进行解码流程
      Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
          downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
          requestedHeight, fixBitmapToRequestedDimensions, callbacks);
      return BitmapResource.obtain(result, bitmapPool);
    } finally {
      releaseOptions(bitmapFactoryOptions);
      byteArrayPool.put(bytesForOptions);
    }
  }
  
  // 真正解码的流程
  private Bitmap decodeFromWrappedStreams(InputStream is,
      BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
      DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,
      int requestedHeight, boolean fixBitmapToRequestedDimensions,
      DecodeCallbacks callbacks) throws IOException {
    
    //1. 计算图像大小
    int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
    int sourceWidth = sourceDimensions[0];
    int sourceHeight = sourceDimensions[1];
    String sourceMimeType = options.outMimeType;

    if (sourceWidth == -1 || sourceHeight == -1) {
      isHardwareConfigAllowed = false;
    }
    
    //2. 计算图像角度
    int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);
    int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
    boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);

    // 设定图像需要的目标大小
    int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? sourceWidth : requestedWidth;
    int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? sourceHeight : requestedHeight;

    ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);

    // 计算调整Option关于inSamplerSize、inScale、inDensity等参数
    calculateScaling(
        imageType,
        is,
        callbacks,
        bitmapPool,
        downsampleStrategy,
        degreesToRotate,
        sourceWidth,
        sourceHeight,
        targetWidth,
        targetHeight,
        options);
        
    calculateConfig(
        is,
        decodeFormat,
        isHardwareConfigAllowed,
        isExifOrientationRequired,
        options,
        targetWidth,
        targetHeight);
   
    // 重新设定图像最后需要的大小
    boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
    if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
      int expectedWidth;
      int expectedHeight;
      if (sourceWidth >= 0 && sourceHeight >= 0
          && fixBitmapToRequestedDimensions && isKitKatOrGreater) {
        expectedWidth = targetWidth;
        expectedHeight = targetHeight;
      } else {
        float densityMultiplier = isScaling(options)
            ? (float) options.inTargetDensity / options.inDensity : 1f;
        int sampleSize = options.inSampleSize;
        int downsampledWidth = (int) Math.ceil(sourceWidth / (float) sampleSize);
        int downsampledHeight = (int) Math.ceil(sourceHeight / (float) sampleSize);
        expectedWidth = Math.round(downsampledWidth * densityMultiplier);
        expectedHeight = Math.round(downsampledHeight * densityMultiplier);

      }
      // 如果不是image,或者BitmapFactory无法解析,expectedWidth/expectedHeight都会为-1
      if (expectedWidth > 0 && expectedHeight > 0) {
        // 设置InBitmap可复用的bitmap
        setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
      }
    }
    // 3. 解码
    Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
    callbacks.onDecodeComplete(bitmapPool, downsampled);

    Bitmap rotated = null;
    if (downsampled != null) {
      // 如果缩放过,bitmap的密度inTargetDensity, 等于设定bitmap密度为屏幕的密度
      downsampled.setDensity(displayMetrics.densityDpi);
      // 4.旋转图像
      rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation);
      if (!downsampled.equals(rotated)) {
      // 加入缓存池
        bitmapPool.put(downsampled);
      }
    }

    return rotated;
  }

这里说下Bitmap Options的几个参数:

inSamplerSize:采样率,2的幂次方
inDesity: 位图使用的像素密度
inTargetDesity: 设备的屏幕密度
inScale: 是否需要放缩位图
inScale设置为true时,且inDenstiy和inTargetDensity也不为0时,位图将在加载时(解码)时放缩去匹配inTargetDensity,在绘制到canvas时不会依赖图像系统放缩。

看完上面的流程,Glide可以说把复用机制用到了极致了,不仅用缓存池,还有上了inBitmap等参数。

这里我只关心几个问题:

1. 计算图像大小
  private static int[] getDimensions(InputStream is, BitmapFactory.Options options,
      DecodeCallbacks decodeCallbacks, BitmapPool bitmapPool) throws IOException {
    options.inJustDecodeBounds = true;
    decodeStream(is, options, decodeCallbacks, bitmapPool);
    options.inJustDecodeBounds = false;
    return new int[] { options.outWidth, options.outHeight };
  }

跟日常我们使用的差不多,也是用inJustDecodeBounds参数,不多说

2. 计算图像角度
    int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);
    int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);

以上说了parsers有两种类型: DefaultImageHeaderParser、 ExifInterfaceImageHeaderParser

DefaultImageHeaderParser是Glide自定义的默认的头部解析器,自己解析头部信息

ExifInterfaceImageHeaderParser 是利用ExifInterface接口,比较有借鉴意义:

// ExifInterfaceImageHeaderParser
  public int getOrientation(@NonNull InputStream is, @NonNull ArrayPool byteArrayPool)
      throws IOException {
    ExifInterface exifInterface = new ExifInterface(is);
    int result = exifInterface.getAttributeInt(
            ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
    if (result == ExifInterface.ORIENTATION_UNDEFINED) {
      return ImageHeaderParser.UNKNOWN_ORIENTATION;
    }
    return result;
  }
  
// TransformationUtils
  public static int getExifOrientationDegrees(int exifOrientation) {
    final int degreesToRotate;
    switch (exifOrientation) {
      case ExifInterface.ORIENTATION_TRANSPOSE:
      case ExifInterface.ORIENTATION_ROTATE_90:
        degreesToRotate = 90;
        break;
      case ExifInterface.ORIENTATION_ROTATE_180:
      case ExifInterface.ORIENTATION_FLIP_VERTICAL:
        degreesToRotate = 180;
        break;
      case ExifInterface.ORIENTATION_TRANSVERSE:
      case ExifInterface.ORIENTATION_ROTATE_270:
        degreesToRotate = 270;
        break;
      default:
        degreesToRotate = 0;
        break;
    }
    return degreesToRotate;
  }

3. 解码
private static Bitmap decodeStream(InputStream is, BitmapFactory.Options options,
      DecodeCallbacks callbacks, BitmapPool bitmapPool) throws IOException {
    if (options.inJustDecodeBounds) {
      is.mark(MARK_POSITION);
    } else {
      callbacks.onObtainBounds();
    }
    String outMimeType = options.outMimeType;
    final Bitmap result;
    TransformationUtils.getBitmapDrawableLock().lock();
    try {
      result = BitmapFactory.decodeStream(is, null, options);
    } catch (IllegalArgumentException e) {
        ...
    }
    if (options.inJustDecodeBounds) {
      is.reset();

    }
    return result;
  }

Glide的解码也是利用了BitmapFactory.decodeStream()。

这里可以学到的是:通过mark和reset可以复用InputStream,这里的InputStream是Glide自己实现的 RecyclableBufferedInputStream

但是FileInputStream是不支持mark和reset的,不过你可以使用BufferedInputStream,它提供“缓冲功能”以及支持“mark()标记”和“reset()重置方法”。本质上通过一个内部缓冲区数组实现的,将输入流的数据分批的填入到缓冲区中,当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区,如此反复,直到读完。

4.旋转图像
public static Bitmap rotateImageExif(@NonNull BitmapPool pool, @NonNull Bitmap inBitmap,
      int exifOrientation) {
    // 判断是否需要旋转
    if (!isExifOrientationRequired(exifOrientation)) {
      return inBitmap;
    }

    final Matrix matrix = new Matrix();
    initializeMatrixForRotation(exifOrientation, matrix);

    // From Bitmap.createBitmap.
    final RectF newRect = new RectF(0, 0, inBitmap.getWidth(), inBitmap.getHeight());
    matrix.mapRect(newRect);

    final int newWidth = Math.round(newRect.width());
    final int newHeight = Math.round(newRect.height());

    Bitmap.Config config = getNonNullConfig(inBitmap);
    // 从缓存池获取一个bitmap
    Bitmap result = pool.get(newWidth, newHeight, config);

    matrix.postTranslate(-newRect.left, -newRect.top);
    // 绘制一个新的bitmap
    applyMatrix(inBitmap, result, matrix);
    return result;
  }
  
  // 判断是否需要旋转
  public static boolean isExifOrientationRequired(int exifOrientation) {
    switch (exifOrientation) {
      case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
      case ExifInterface.ORIENTATION_ROTATE_180:
      case ExifInterface.ORIENTATION_FLIP_VERTICAL:
      case ExifInterface.ORIENTATION_TRANSPOSE:
      case ExifInterface.ORIENTATION_ROTATE_90:
      case ExifInterface.ORIENTATION_TRANSVERSE:
      case ExifInterface.ORIENTATION_ROTATE_270:
        return true;
      default:
        return false;
    }
  }
  
  // 利用 matrix 绘制一个新的bitmap
  private static void applyMatrix(@NonNull Bitmap inBitmap, @NonNull Bitmap targetBitmap,
      Matrix matrix) {
    BITMAP_DRAWABLE_LOCK.lock();
    try {
      Canvas canvas = new Canvas(targetBitmap);
      canvas.drawBitmap(inBitmap, matrix, DEFAULT_PAINT);
      clear(canvas);
    } finally {
      BITMAP_DRAWABLE_LOCK.unlock();
    }
  }

以上注释蛮清晰简单的,不做过多解释

Glide 怎么保证ImageView宽高?
在《Glide原理解析(一):加载流程分析》 的SingleRequest的begin()方法里,如果知道了Target的Size就会调用onSizeReady(),如果不知道就会调用Target.getSize()

    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      // 已经拿到了size,可以开始异步请求
      onSizeReady(overrideWidth, overrideHeight);
    } else {
      // 从Target中获取目标的size,最后会走到onSizeReady()
      target.getSize(this);
    }

我们看一下 ViewTarget.getSize()方法

  // ViewTarget
  public void getSize(@NonNull SizeReadyCallback cb) {
    sizeDeterminer.getSize(cb);
  }

sizeDeterminer 是负责获取target的size

// SizeDeterminer
  void getSize(@NonNull SizeReadyCallback cb) {
    int currentWidth = getTargetWidth();
    int currentHeight = getTargetHeight();
    if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
      // 已经拿到了size,回调出去
      cb.onSizeReady(currentWidth, currentHeight);
      return;
    }
    // 添加一个回调
    if (!cbs.contains(cb)) {
      cbs.add(cb);
    }
    
    if (layoutListener == null) {
      ViewTreeObserver observer = view.getViewTreeObserver();
      layoutListener = new SizeDeterminerLayoutListener(this);
      // 利用了ViewTreeObserver
      observer.addOnPreDrawListener(layoutListener);
    }
  }

到这里不需要继续往下看了。

其实就是Glide利用了ViewTreeObserver.OnPreDrawListener,添加到View中,当view绘制之前就会通知Glide。
 

猜你喜欢

转载自blog.csdn.net/suyimin2010/article/details/91353452
今日推荐