Glide4.8原始碼拆解(四)Bitmap解析之"下采樣"淺析

HitenDev發表於2019-01-13

前言

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底層的邏輯尤為重要,值得學習;

相關文章