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。

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/app/6251.html

(0)
上一篇 2021年7月17日 00:44
下一篇 2021年7月17日 00:44

相关推荐

发表回复

登录后才能评论