Glide4.8源码拆解(四)Bitmap解析之"下采样"浅析

栏目: Android · 发布时间: 5年前

内容简介:Glide归根结底是一个图片加载框架,它一定会涉及到本文主要分析这两个类:假设我们有一张宽高100x200的网络图片,需要加载到300x300像素的ImageView上(scaleType属性为CenterCrop),用Glide加载,不做任何处理会得到多大的Bitmap?

Glide归根结底是一个图片加载框架,它一定会涉及到 BitmapFactory 相关API把 Bitmap 读取到内存;可能大家已经很熟悉如何高效的加载Bitmap(比如使用inSample等),这一章还是要看一看Glide是如何玩转的;

本文主要分析这两个类:

DownsampleStrategy
Downsampler

从"Glide会对原图进行放大"案例开始

假设我们有一张宽高100x200的网络图片,需要加载到300x300像素的ImageView上(scaleType属性为CenterCrop),用Glide加载,不做任何处理会得到多大的Bitmap?

Glide.with(MainActivity.this).load(URL).listener(new RequestListener<Drawable>() {
        @Override
        public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
          return false;
        }

        @Override
        public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
          if (resource instanceof BitmapDrawable){
            Bitmap bitmap = ((BitmapDrawable) resource).getBitmap();
            Log.d("onResourceReady",bitmap.toString());
          }
          return false;
        }
      }).into(vh.imageView);
复制代码

不出意外会得到300*300的Bitmap,Bitmap比例显然被放大了;

我怀疑是进行了 Transformation 操作,所以我们要禁用 Transformation ;

假设我们对RequestOptions加上noTransformation的属性,比如这个样子:

RequestOptions requestOptions = RequestOptions.noTransformation();
 Glide.with(MainActivity.this).load(URL).apply(requestOptions).listener(xxx).into(vh.imageView);
复制代码

不出意外会得到300*600的Bitmap,Bitmap比例依然在被放大,甚至更大了;

如果我们不想让Bitmap被放大,可以用加载原图尺寸的方式,比如设置 override(Target.SIZE_ORIGINAL,Target.SIZE_ORIGINAL) 或者用 SimpleTarget 这样的 Target ,但是加载原图终究不是解决方案,为什么?内存的原因,大部分情况下我们还是希望Glide来对图片进行缩小,加载原图的操作等于是直接把APP往OOM送近了一步;

Bitmap被放大不是Glide发明的,是Android官方带的头,比如同一张图片,放在res中不同drawable文件夹下,得到的尺寸不一样;既然如此,Bitmap被放大肯定是有积极意义的,也难怪Glide很少提及这事;

但是,总会有强迫症患者不想Bitmap被放大,Glide肯定也想到了这一点,具体怎么限制Bitmap不会放大,答案的使用 RequestOptions.downsample()方法 ;

RequestOptions.downsample() 方法接受参数类型为: DownsampleStrategy ,那么我们从 DownsampleStrategy 开始分析;

DownsampleStrategy

DownsampleStrategy 顾名思义就是下采样策略,下采样是图像进行缩小的一种方式;

DownsampleStrategy.java

public abstract class DownsampleStrategy {
//获取缩放比例
public abstract float getScaleFactor(int sourceWidth, int sourceHeight, int requestedWidth,
      int requestedHeight);
      
//获取SampleSize策略
public abstract SampleSizeRounding getSampleSizeRounding(int sourceWidth, int sourceHeight,
      int requestedWidth, int requestedHeight);
}

public enum SampleSizeRounding {
    //内存优先 
    MEMORY,
    //图片质量优先
    QUALITY,
  }
复制代码

DownsampleStrategy 是抽象类,提供两个抽象方法, getScaleFactor() 顾名思义是获取缩放比例的, getSampleSizeRounding() 可能是获取SampleSize的, DownsampleStrategy 真是的实现类在其内部,几个嵌套内部类 FitCenter , CenterOutside , AtLeast , AtMost , None , CenterInside ,除此之外, DownsampleStrategy 还定义对应类的静态变量;

DownsampleStrategy.java

public static final DownsampleStrategy FIT_CENTER = new FitCenter();

 public static final DownsampleStrategy CENTER_OUTSIDE = new CenterOutside();

 public static final DownsampleStrategy AT_LEAST = new AtLeast();
 
 public static final DownsampleStrategy AT_MOST = new AtMost();
 
 public static final DownsampleStrategy CENTER_INSIDE = new CenterInside();
 
 public static final DownsampleStrategy NONE = new None();
 
 public static final DownsampleStrategy DEFAULT = CENTER_OUTSIDE;
复制代码

其中 DEFAULT 应该是Glide默认的策略,它指向的是 CENTER_OUTSIDE ,我们简单看一下 CenterOutside 的逻辑;

CenterOutside.java

private static class CenterOutside extends DownsampleStrategy {

    @Synthetic
    CenterOutside() { }

    @Override
    public float getScaleFactor(int sourceWidth, int sourceHeight, int requestedWidth,
        int requestedHeight) {
        //宽度比例(控件/图片)
      float widthPercentage = requestedWidth / (float) sourceWidth;
      //高度比例(控件/图片)
      float heightPercentage = requestedHeight / (float) sourceHeight;
      //谁大取谁
      return Math.max(widthPercentage, heightPercentage);
    }

    @Override
    public SampleSizeRounding getSampleSizeRounding(int sourceWidth, int sourceHeight,
        int requestedWidth, int requestedHeight) {
      return SampleSizeRounding.QUALITY;
    }
  }
复制代码

主要关注是 CenterOutsidegetScaleFactor() 逻辑,该逻辑非常简单,主要是拿 控件宽高/图片宽高 ,得到尺寸取最大值,我们回到文章开头的那个例子,假设此时控件宽高是300x300,图片宽高100x200:

widthPercentage = 300/100 = 3.0f;

heightPercentage = 300/200 = 1.5f;

Math.max(widthPercentage, heightPercentage) = 3.0f;

从这个推理来看,文章开头那个图片确实会被放大3倍;

文章开头试图找控制缩放比例不超过1的,就是只缩小不放大的,有没有这样的策略,其实是有的,比如 AtLeast , AtMost

AtLeast.java

private static class AtLeast extends DownsampleStrategy {

    @Synthetic
    AtLeast() { }

    @Override
    public float getScaleFactor(int sourceWidth, int sourceHeight, int requestedWidth,
        int requestedHeight) {
      int minIntegerFactor = Math.min(sourceHeight / requestedHeight, sourceWidth / requestedWidth);
      return minIntegerFactor == 0 ? 1f : 1f / Integer.highestOneBit(minIntegerFactor);
    }

    @Override
    public SampleSizeRounding getSampleSizeRounding(int sourceWidth, int sourceHeight,
        int requestedWidth, int requestedHeight) {
      return SampleSizeRounding.QUALITY;
    }
  }
复制代码

AtLeastgetScaleFactor 的逻辑简要分析:

获取图片宽高/布局宽高的比例的最小值,转成int值;

如果这个值等于0,直接返回1,等于0其实就意味着图片宽高/布局宽高至少有一个是小于1的,也就屏蔽了需要放大的情况;

如果 minIntegerFactor 不等一0,肯定是需要缩小的,最后返回 1f / Integer.highestOneBit(minIntegerFactor) ,其中 Integer.highestOneBit(minIntegerFactor) 是取 minIntegerFactor 的二进制形式最左边的最高一位且高位后面全部补零,最后返回int型的结果;

其余的 DownsampleStrategy 实现类就不讲解了, DownsampleStrategy 是负责计算缩放比例和SampleSize策略,那么真正去进行Bitmap计算的是 Downsampler ;

重头戏Downsampler

Downsampler.java

Downsampler 这个类在我们之前分析Decode流程时已经遇到过它,该类的主要职责是从输入流中解析出 Bitmap ,核心功能的入口方法在 decode() ;

public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
      Options options, DecodeCallbacks callbacks) throws IOException {
    Preconditions.checkArgument(is.markSupported(), "You must provide an InputStream that supports"
        + " mark()");

    byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
    //获取默认的BitmapFactory
    BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
    bitmapFactoryOptions.inTempStorage = bytesForOptions;
    //decodeFormat主要是RGB_8888||RGB_565
    DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
    //获取DownsampleStrategy
    DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
    //是否fixedBitmapSize
    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
      Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
          downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
          requestedHeight, fixBitmapToRequestedDimensions, callbacks);
     //BitmapResource包装Bitmap
      return BitmapResource.obtain(result, bitmapPool);
    } finally {
      releaseOptions(bitmapFactoryOptions);
      byteArrayPool.put(bytesForOptions);
    }
  }
复制代码

首先调用 getDefaultOptions() 获取默认的 BitmapFactory ;

默认的BitmapFactory

默认的BitmapFactory使用一个队列缓存,初始化设置在 resetOptions() 方法;

private static void resetOptions(BitmapFactory.Options decodeBitmapOptions) {
    decodeBitmapOptions.inTempStorage = null;
    decodeBitmapOptions.inDither = false;
    decodeBitmapOptions.inScaled = false;
    decodeBitmapOptions.inSampleSize = 1;
    decodeBitmapOptions.inPreferredConfig = null;
    decodeBitmapOptions.inJustDecodeBounds = false;
    decodeBitmapOptions.inDensity = 0;
    decodeBitmapOptions.inTargetDensity = 0;
    decodeBitmapOptions.outWidth = 0;
    decodeBitmapOptions.outHeight = 0;
    decodeBitmapOptions.outMimeType = null;
    decodeBitmapOptions.inBitmap = null;
    decodeBitmapOptions.inMutable = true;
  }
复制代码

继续 decode() 方法分析,首先从 Option 中获取 decodeFormatfixBitmapToRequestedDimensionsisHardwareConfigAllowed 等,这个 Option 是从 RequestOptions 传递过来,代表用户的配置;

  • decodeFormat 表示解析格式,RGB_565或ARGB_8888;
  • fixBitmapToRequestedDimensions 表示是否填充尺寸,不进行缩放
  • isHardwareConfigAllowed 是否允许硬件位图,详细了解可以看Glide官方文档: muyangmin.github.io/glide-docs-…
  • BitmapResource 包装 Bitmap 返回; 真正的加载Bitmap逻辑在 decodeFromWrappedStreams() 方法:

decodeFromWrappedStreams

decodeFromWrappedStreams()

private Bitmap decodeFromWrappedStreams(InputStream is,
      BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
      DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,
      int requestedHeight, boolean fixBitmapToRequestedDimensions,
      DecodeCallbacks callbacks) throws IOException {
    long startTime = LogTime.getLogTime();
    //解析出输入流图片尺寸
    int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
    int sourceWidth = sourceDimensions[0];
    int sourceHeight = sourceDimensions[1];
    //得到mimeType
    String sourceMimeType = options.outMimeType;
    if (sourceWidth == -1 || sourceHeight == -1) {
      isHardwareConfigAllowed = false;
    }
    //获取图片旋转方向
    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;
    //获取image类型
    ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);
    //计算缩放
    calculateScaling();
    //计算其他config
    calculateConfig();

    boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
    //处理inBitmap
    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);
      }
      //设置inBitmap
      if (expectedWidth > 0 && expectedHeight > 0) {
        setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
      }
    }
    //BitmapFactory.Option配置完毕,调用decodeStream解析
    Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
    callbacks.onDecodeComplete(bitmapPool, downsampled);
    
    Bitmap rotated = null;
    if (downsampled != null) {
      //重新设置density
      downsampled.setDensity(displayMetrics.densityDpi);
      //处理旋转信息
      rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation);
      if (!downsampled.equals(rotated)) {
      //添加进BitmapPool
        bitmapPool.put(downsampled);
      }
    }

    return rotated;
  }
复制代码
  • decodeFromWrappedStreams() 方法首先调用 getDimensions() 获取输入流图片尺寸和 mimeType
  • 获取图片的方向和旋转角度;
  • 确认目标控件的宽高,获取图片的ImageType;
  • 调用 calculateScaling() 计算缩放信息;
  • 调用 calculateConfig() 计算其他配置信息;
  • 根据配置,调用 setInBitmap() 设置inBitmap;
  • 上诉流程完成 BitmapFactory.Option 配置,调用 decodeStream() 解析输入流;
  • 得到Bitmap,对Bitmap做最后的操作;

getDimensions()

private static int[] getDimensions(InputStream is, BitmapFactory.Options options,
      DecodeCallbacks decodeCallbacks, BitmapPool bitmapPool) throws IOException {
    //只解析Bounds
    options.inJustDecodeBounds = true;
    decodeStream(is, options, decodeCallbacks, bitmapPool);
     //重置为false;
    options.inJustDecodeBounds = false;
    return new int[] { options.outWidth, options.outHeight };
  }
复制代码

getDimensions() 使用 options.inJustDecodeBounds = true 读取输入流图片信息;

计算缩放

calculateScaling()

private static void calculateScaling(
      ImageType imageType,
      InputStream is,
      DecodeCallbacks decodeCallbacks,
      BitmapPool bitmapPool,
      DownsampleStrategy downsampleStrategy,
      int degreesToRotate,
      int sourceWidth,
      int sourceHeight,
      int targetWidth,
      int targetHeight,
      BitmapFactory.Options options) throws IOException {
    //尺寸不能为0
    if (sourceWidth <= 0 || sourceHeight <= 0) {
      return;
    }

    final float exactScaleFactor;
    //根据方向和角度获取exactScaleFactor
    if (degreesToRotate == 90 || degreesToRotate == 270) {
      exactScaleFactor = downsampleStrategy.getScaleFactor(sourceHeight, sourceWidth,
          targetWidth, targetHeight);
    } else {
      exactScaleFactor =
          downsampleStrategy.getScaleFactor(sourceWidth, sourceHeight, targetWidth, targetHeight);
    }
    //exactScaleFactor不能小于等于0
    if (exactScaleFactor <= 0f) {
      throw new IllegalArgumentException("");
    }
    //获取SampleSizeRounding
    SampleSizeRounding rounding = downsampleStrategy.getSampleSizeRounding(sourceWidth,
        sourceHeight, targetWidth, targetHeight);
    if (rounding == null) {
      throw new IllegalArgumentException("Cannot round with null rounding");
    }
    //获取Bitmap输出宽高
    int outWidth = round(exactScaleFactor * sourceWidth);
    int outHeight = round(exactScaleFactor * sourceHeight);
    //转成int类型的factor
    int widthScaleFactor = sourceWidth / outWidth;
    int heightScaleFactor = sourceHeight / outHeight;
    //根据SampleSizeRounding得到scaleFactor
    int scaleFactor = rounding == SampleSizeRounding.MEMORY
        ? Math.max(widthScaleFactor, heightScaleFactor)
        : Math.min(widthScaleFactor, heightScaleFactor);

    int powerOfTwoSampleSize;
    //不支持下采样
    if (Build.VERSION.SDK_INT <= 23
        && NO_DOWNSAMPLE_PRE_N_MIME_TYPES.contains(options.outMimeType)) {
      powerOfTwoSampleSize = 1;
    } else {
    //在scaleFactor再进行处理,保证是2的指数幂
      powerOfTwoSampleSize = Math.max(1, Integer.highestOneBit(scaleFactor));
      if (rounding == SampleSizeRounding.MEMORY
          && powerOfTwoSampleSize < (1.f / exactScaleFactor)) {
        powerOfTwoSampleSize = powerOfTwoSampleSize << 1;
      }
    }
    //设置到inSampleSize
    options.inSampleSize = powerOfTwoSampleSize;
    int powerOfTwoWidth;
    int powerOfTwoHeight;
    //针对不同图片格式,重新计算采样后的宽高
    if (imageType == ImageType.JPEG) {
    //libjpeg引擎最大采样size=8,skia会进行二次采样
      int nativeScaling = Math.min(powerOfTwoSampleSize, 8);
      powerOfTwoWidth = (int) Math.ceil(sourceWidth / (float) nativeScaling);
      powerOfTwoHeight = (int) Math.ceil(sourceHeight / (float) nativeScaling);
      //如大大于8,skia会对剩下的进行二次采样计算逻辑
      int secondaryScaling = powerOfTwoSampleSize / 8;
      if (secondaryScaling > 0) {
        powerOfTwoWidth = powerOfTwoWidth / secondaryScaling;
        powerOfTwoHeight = powerOfTwoHeight / secondaryScaling;
      }
    } else if (imageType == ImageType.PNG || imageType == ImageType.PNG_A) {
      powerOfTwoWidth = (int) Math.floor(sourceWidth / (float) powerOfTwoSampleSize);
      powerOfTwoHeight = (int) Math.floor(sourceHeight / (float) powerOfTwoSampleSize);
    } else if (imageType == ImageType.WEBP || imageType == ImageType.WEBP_A) {
    //不同版本采样不同的计算方式round或者floor
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        powerOfTwoWidth = Math.round(sourceWidth / (float) powerOfTwoSampleSize);
        powerOfTwoHeight = Math.round(sourceHeight / (float) powerOfTwoSampleSize);
      } else {
        powerOfTwoWidth = (int) Math.floor(sourceWidth / (float) powerOfTwoSampleSize);
        powerOfTwoHeight = (int) Math.floor(sourceHeight / (float) powerOfTwoSampleSize);
      }
    } else if (
        sourceWidth % powerOfTwoSampleSize != 0 || sourceHeight % powerOfTwoSampleSize != 0) {
      int[] dimensions = getDimensions(is, options, decodeCallbacks, bitmapPool);
      powerOfTwoWidth = dimensions[0];
      powerOfTwoHeight = dimensions[1];
    } else {
      powerOfTwoWidth = sourceWidth / powerOfTwoSampleSize;
      powerOfTwoHeight = sourceHeight / powerOfTwoSampleSize;
    }
    //计算采样后进行缩放的比例
    double adjustedScaleFactor = downsampleStrategy.getScaleFactor(
        powerOfTwoWidth, powerOfTwoHeight, targetWidth, targetHeight);
    //计算inTargetDensity和inDensity进行缩放
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    //这一块计算方法没有弄明白
      options.inTargetDensity = adjustTargetDensityForError(adjustedScaleFactor);
      options.inDensity = getDensityMultiplier(adjustedScaleFactor);
    }
    //设置inScaled
    if (isScaling(options)){
      options.inScaled = true;
    } else {
      options.inDensity = options.inTargetDensity = 0;
    }
  }
复制代码

calculateScaling代码看似复杂,但是仔细分析流程还是很清晰的,该方法主要计算 BitmapFactory.FactoryinSampleSizeinTargetDensityinDensityinSampleSize 主要是进行向下采样,采样后自然会对图片进行缩小,但是可能不满足目标缩放比例,所以再配合 inTargetDensityinDensity 进行二次缩放;主要针对两个缩放变量 powerOfTwoSampleSizeadjustedScaleFactor ;具体流程分析:

流程一

  1. 计算 powerOfTwoSampleSize 过程: powerOfTwoSampleSize 是最终赋值给 options.inSampleSize 的;
  • 根据图片角度不同,分别调用 downSampleStategy.getScaleFactor() 方法,得到 exactScaleFactor ;
  • 通过 exactScaleFactor 计算出 outWidthoutHeight ,再重新计算 widthScaleFactorheightScaleFactor (ps:widthScaleFactor和heightScaleFactor是int类型,而exactScaleFactor是浮点型,结果是不一样的);
  • 通过策略得到int类型的比例 scaleFactor
  • 判断是否支持下采样,计算出最终的缩放比例 powerOfTwoSampleSize
  • 赋值给options.inSampleSize

流程二

  1. 计算 adjustedScaleFactor 过程: adjustedScaleFactor 是最终计算 options.inTargetDensityoptions.inDensity 的;
  • 不同格式和版本的下采样逻辑不同,Glide分别实现采样逻辑的计算,然后生成采样后的宽高尺寸 powerOfTwoWidthpowerOfTwoHeight ;
  • 再此调用 downsampleStrategy.getScaleFactor() 得到缩放比 adjustedScaleFactor ;
  • 调用 adjustTargetDensityForError()getDensityMultiplier() 得到 inTargetDensityinDensity 并赋值;
  • 上面 inDensity 依赖 options.inScaled = true ,最终还得判断是否能够进行scale;

计算硬件位图/Bitmap.Config

calculateConfig()

private void calculateConfig(
      InputStream is,
      DecodeFormat format,
      boolean isHardwareConfigAllowed,
      boolean isExifOrientationRequired,
      BitmapFactory.Options optionsWithScaling,
      int targetWidth,
      int targetHeight) {
      //如果支持硬件位图
    if (hardwareConfigState.setHardwareConfigIfAllowed(
        targetWidth,
        targetHeight,
        optionsWithScaling,
        format,
        isHardwareConfigAllowed,
        isExifOrientationRequired)) {
      return;
    }
    //这种情况,直接配置ARGB_8888
    if (format == DecodeFormat.PREFER_ARGB_8888
        || Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
      optionsWithScaling.inPreferredConfig = Bitmap.Config.ARGB_8888;
      return;
    }
    //是否有透明的通道
    boolean hasAlpha = false;
    try {
      hasAlpha = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool).hasAlpha();
    } catch (IOException e) {
    
    }
    //有透明的通道,需要配置成ARGB_8888
    optionsWithScaling.inPreferredConfig =
        hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
    if (optionsWithScaling.inPreferredConfig == Config.RGB_565) {
      optionsWithScaling.inDither = true;//565支持抖动界面
    }
  }
复制代码

calculateConfig() 主要是对 BitmapFactory.Option 的硬件位图和 inPreferredConfig 进行计算;如果用户配置8888直接配置,如果用户配置565,还需要判断是否有透明度通道,如果有透明度通道,依然采用8888解码器;

硬件位图

HardwareConfigState.java

boolean setHardwareConfigIfAllowed(
      int targetWidth,
      int targetHeight,
      BitmapFactory.Options optionsWithScaling,
      DecodeFormat decodeFormat,
      boolean isHardwareConfigAllowed,
      boolean isExifOrientationRequired) {
    if (!isHardwareConfigAllowed
        || Build.VERSION.SDK_INT < Build.VERSION_CODES.O
        || isExifOrientationRequired) {
        Android O以上支持
      return false;
    }
    //对尺寸有要求;
    boolean result =
        targetWidth >= MIN_HARDWARE_DIMENSION
            && targetHeight >= MIN_HARDWARE_DIMENSION
            && isFdSizeBelowHardwareLimit();

    if (result) {
      optionsWithScaling.inPreferredConfig = Bitmap.Config.HARDWARE;
      optionsWithScaling.inMutable = false;
    }
    return result;
  }
复制代码

硬件位图Bitmap.Config.HARDWARE 是一种 Android O 添加的新的位图格式。硬件位图仅在显存 (graphic memory) 里存储像素数据,并对图片仅在屏幕上绘制的场景做了优化。

###设置InBitmap

setInBitmap()

private static void setInBitmap(
      BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
    @Nullable Bitmap.Config expectedConfig = null;
    // Avoid short circuiting, it appears to break on some devices.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    //硬件位图不设置
      if (options.inPreferredConfig == Config.HARDWARE) {
        return;
      }
      //inJustDecodeBoudes时候有可能可以获取到
      expectedConfig = options.outConfig;
    }

    if (expectedConfig == null) {
      expectedConfig = options.inPreferredConfig;
    }
   
    options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
  }
复制代码

设置成硬件位图的不能进行 inBitmap 操作,最终会设置 options.inBitmap ,其中缓存的位图从bitmapPool中获取;

解析输出流

decodeStream()

万事具体只差东风,设置完BitmapFactory.Options之后,正在解析输入流的代码就在 decodeStream() :

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();
    }
    int sourceWidth = options.outWidth;
    int sourceHeight = options.outHeight;
    String outMimeType = options.outMimeType;
    final Bitmap result;
    TransformationUtils.getBitmapDrawableLock().lock();
    try {
    //真正的解析调用
      result = BitmapFactory.decodeStream(is, null, options);
    } catch (IllegalArgumentException e) {
      if (options.inBitmap != null) {
        try {
        //发生异常要做inBitmap回收
          is.reset();
          bitmapPool.put(options.inBitmap);
          options.inBitmap = null;//不适用inBitmap
          return decodeStream(is, options, callbacks, bitmapPool);
        } catch (IOException resetException) {
          throw bitmapAssertionException;
        }
      }
      throw bitmapAssertionException;
    } finally {
      TransformationUtils.getBitmapDrawableLock().unlock();
    }
    if (options.inJustDecodeBounds) {
      is.reset();
    }
    return result;
  }
复制代码

decodeStream() 核心的方法是调用 BitmapFactory.decodeStream(is, null, options) 做解析工作,剩下的代码都是对异常解析,如果发现异常时 inBitmap 不为空,设置 inBitmapnull 并重试一次;

总结

本文主要是对Glide下采样做了简单介绍,从代码流程上分析,可以分为对BitmapFactory.Options的配置、调用BitmapFactory解析输入流以及对Bitmap最后的处理三个步骤,其中Options配置阶段在计算缩放上最为重要,其中在缩放因子计算阶段,涉及 native 以及 libjpeg 底层的逻辑尤为重要,值得学习;


以上所述就是小编给大家介绍的《Glide4.8源码拆解(四)Bitmap解析之"下采样"浅析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

"笨办法"学Python

"笨办法"学Python

肖 (Zed A.Shaw) / 王巍巍 / 人民邮电出版社 / 2014-11-1 / CNY 49.00

本书是一本Python入门书籍,适合对计算机了解不多,没有学过编程,但对编程感兴趣的读者学习使用。这本书以习题的方式引导读者一步一步学习编程,从简单的打印一直讲到完整项目的实现,让初学者从基础的编程技术入手,最终体验到软件开发的基本过程。 本书结构非常简单,共包括52个习题,其中26个覆盖了输入/输出、变量和函数三个主题,另外26个覆盖了一些比较高级的话题,如条件判断、循环、类和对象、代码测......一起来看看 《"笨办法"学Python》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

html转js在线工具
html转js在线工具

html转js在线工具