Bitmap版本演變&Glide對(duì)Bitmap的處理

Bitmap版本演變

Bitmap的處理是Android開發(fā)過程中無法避開的一項(xiàng),也是內(nèi)存占用的大戶,常見的內(nèi)存占用優(yōu)化都涉及到Bitmap。

Android SDK各版本也一直在對(duì)Bitmap進(jìn)行優(yōu)化:

    Android3.0以前Bitmap像素存在Native區(qū),生命周期不可控,需手動(dòng)回收bitmap.recycle()

    Android3.0-8.0 Bitmap像素存在Java堆,虛擬機(jī)自動(dòng)回收,但容易OOM

    Android8.0及以后Bitmap像素存在Native區(qū)域,配合NativeAllocationRegistry機(jī)制+系統(tǒng)支持,虛擬機(jī)自動(dòng)回收

為什么會(huì)有這種變化?首先Bitmap對(duì)象占用內(nèi)存很小,主要是像素信息占用內(nèi)存很大。

Bitmap在3.0以前Native區(qū)的回收是依賴Java虛擬機(jī)的GC來回收的。Bitmap在Java堆上的內(nèi)存占用很小,假如應(yīng)用增加了很多圖片,在這個(gè)過程中Java堆上的內(nèi)存上漲是不明顯的;在沒有內(nèi)存增長(zhǎng)觸發(fā)GC時(shí),虛擬機(jī)自身的回收周期很漫長(zhǎng),這時(shí)就會(huì)出現(xiàn)系統(tǒng)內(nèi)存已經(jīng)不夠用了,而Java堆依然無法觸發(fā)GC,直到系統(tǒng)內(nèi)存OOM。以前的手機(jī)大家可能遇到過應(yīng)用無提示閃退、桌面崩潰、手機(jī)自動(dòng)關(guān)機(jī)的情況,這大都是系統(tǒng)內(nèi)存OOM造成的,系統(tǒng)內(nèi)存OOM時(shí),會(huì)殺后臺(tái)進(jìn)程,殺服務(wù)(部分廠商定制有問題導(dǎo)致手機(jī)關(guān)機(jī)),殺前臺(tái)進(jìn)程直到內(nèi)存釋放出來。所以3.0以前在不使用Bitmap時(shí),需要我們手動(dòng)調(diào)用recycle()釋放Native內(nèi)存。

但手動(dòng)釋放內(nèi)存不符合JAVA一直宣揚(yáng)的把程序員的雙手從內(nèi)存管理中解脫出來的氣質(zhì)啊。所以3.0版本把像素信息放到Java堆上了,這樣就可以觸發(fā)Java虛擬機(jī)的GC機(jī)制,不需要程序員手動(dòng)調(diào)用recycle()釋放,但是也更容易造成應(yīng)用OOM了。我們知道虛擬機(jī)的內(nèi)存使用上限都不大,3.0-8.0版本期間的手機(jī)虛擬機(jī)上限一般都是192M、256M。假如應(yīng)用加載幾張過大的圖片或者圖片引用有問題,直接就會(huì)導(dǎo)致OOM。

面對(duì)手機(jī)越來越大的內(nèi)存配置,如果還用虛擬機(jī)內(nèi)存限制應(yīng)用開發(fā),勢(shì)必是爭(zhēng)奪不過iOS的。所以8.0版本開始Google像iOS一樣,把內(nèi)存占用較大的像素信息重新放到Native區(qū),配合NativeAllocationRegistry機(jī)制+系統(tǒng)支持做到自動(dòng)回收。NativeAllocationRegistry機(jī)制在7.0版本已經(jīng)引入了,但是系統(tǒng)不支持,所以8.0版本才把像素信息放到Native區(qū)。

還有一點(diǎn)是recycle()調(diào)用還有沒有必要?如果圖片確認(rèn)不使用的話,還是有必要調(diào)用recycle()的,不管哪個(gè)系統(tǒng)版本,recycle()調(diào)用都會(huì)直接回收Native內(nèi)存。

    Android3.0以前版本不用說必須調(diào)用

    Android3.0-8.0版本雖然像素信息放到Java堆中,但是Bitmap創(chuàng)建是在native層解碼的,還是有少量?jī)?nèi)存占用在Native層

    Android8.0及以后版本系統(tǒng)對(duì)Bitmap的內(nèi)存占用做了優(yōu)化監(jiān)聽,但還是依賴GC來回收的,而GC是非實(shí)時(shí)回收的

另外一個(gè)變化比較大的是Bitmap的內(nèi)存復(fù)用

    Android4.4之前版本內(nèi)存復(fù)用要求width=width&&height=height&&inSampleSize = 1

    Android4.4及之后版本只要被復(fù)用bitmap內(nèi)存大于所需內(nèi)存即可

這里內(nèi)存復(fù)用有個(gè)需要注意的地方,內(nèi)存復(fù)用的是bitmap整塊內(nèi)存,而不是所需要的內(nèi)存。

比如當(dāng)前圖片大小是1M,復(fù)用的Bitmap內(nèi)存大小是4M,復(fù)用這塊內(nèi)存之后,當(dāng)前圖片所占內(nèi)存為4M,只要當(dāng)前圖片沒被釋放,所占用的4M內(nèi)存是無法被釋放的。所以復(fù)用內(nèi)存時(shí)要找大小相當(dāng)?shù)膬?nèi)存使用。

Glide對(duì)Bitmap的處理

Glide非常強(qiáng)大,基本上我們能做的圖片優(yōu)化,Glide默認(rèn)都幫我們做了。

關(guān)于Glide對(duì)圖片緩存的處理網(wǎng)上有很多文章。這里只說一下內(nèi)存優(yōu)化相關(guān)的三塊:Bitmap自動(dòng)適配Imageview、內(nèi)存復(fù)用和Bitmap解碼的處理。以Glide:4.9.0版本作為源碼分析,因?yàn)镚lide的框架很龐大,調(diào)用鏈很長(zhǎng)(看過Glide源碼的應(yīng)該知道),就不貼調(diào)用鏈代碼了,只把關(guān)鍵代碼貼出來。

Glide中Bitmap的自動(dòng)適配Imageview。

// 計(jì)算縮小比例是否大于2或者2的倍數(shù),如果大于2或者2的倍數(shù)先設(shè)置inSampleSize屬性
 
int outWidth = round(exactScaleFactor * sourceWidth);
int outHeight = round(exactScaleFactor * sourceHeight);
 
int widthScaleFactor = sourceWidth / outWidth;
int heightScaleFactor = sourceHeight / outHeight;
 
int scaleFactor = rounding == SampleSizeRounding.MEMORY
    ? Math.max(widthScaleFactor, heightScaleFactor)
    : Math.min(widthScaleFactor, heightScaleFactor);
 
int powerOfTwoSampleSize;
// BitmapFactory does not support downsampling wbmp files on platforms <= M. See b/27305903.
if (Build.VERSION.SDK_INT <= 23
    && NO_DOWNSAMPLE_PRE_N_MIME_TYPES.contains(options.outMimeType)) {
  powerOfTwoSampleSize = 1;
} else {
  powerOfTwoSampleSize = Math.max(1, Integer.highestOneBit(scaleFactor));
  if (rounding == SampleSizeRounding.MEMORY
      && powerOfTwoSampleSize < (1.f / exactScaleFactor)) {
    powerOfTwoSampleSize = powerOfTwoSampleSize << 1;
  }
}
options.inSampleSize = powerOfTwoSampleSize;
// 如果縮小2的倍數(shù)后仍需要縮小,利用inDensity和inTargetDensity繼續(xù)縮小
 
int powerOfTwoWidth;
int powerOfTwoHeight;
if (imageType == ImageType.JPEG) {
  // libjpegturbo can downsample up to a sample size of 8. libjpegturbo uses ceiling to round.
  // After libjpegturbo's native rounding, skia does a secondary scale using floor
  // (integer division). Here we replicate that logic.
  int nativeScaling = Math.min(powerOfTwoSampleSize, 8);
  powerOfTwoWidth = (int) Math.ceil(sourceWidth / (float) nativeScaling);
  powerOfTwoHeight = (int) Math.ceil(sourceHeight / (float) nativeScaling);
  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) {
  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) {
  // If we're not confident the image is in one of our types, fall back to checking the
  // dimensions again. inJustDecodeBounds decodes do obey inSampleSize.
  int[] dimensions = getDimensions(is, options, decodeCallbacks, bitmapPool);
  // Power of two downsampling in BitmapFactory uses a variety of random factors to determine
  // rounding that we can't reliably replicate for all image formats. Use ceiling here to make
  // sure that we at least provide a Bitmap that's large enough to fit the content we're going
  // to load.
  powerOfTwoWidth = dimensions[0];
  powerOfTwoHeight = dimensions[1];
} else {
  powerOfTwoWidth = sourceWidth / powerOfTwoSampleSize;
  powerOfTwoHeight = sourceHeight / powerOfTwoSampleSize;
}
 
double adjustedScaleFactor = downsampleStrategy.getScaleFactor(
    powerOfTwoWidth, powerOfTwoHeight, targetWidth, targetHeight);
 
// Density scaling is only supported if inBitmap is null prior to KitKat. Avoid setting
// densities here so we calculate the final Bitmap size correctly.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  options.inTargetDensity = adjustTargetDensityForError(adjustedScaleFactor);
  options.inDensity = getDensityMultiplier(adjustedScaleFactor);
}
if (isScaling(options)) {
  options.inScaled = true;
} else {
  options.inDensity = options.inTargetDensity = 0;
}

計(jì)算bitmap的縮放在com.bumptech.glide.load.resource.bitmap.Downsampler#calculateScaling方法中。Bitmap縮小計(jì)算主要分兩步,首先計(jì)算縮小比例是否大于2或者2的倍數(shù),如果大于2或者2的倍數(shù),先利用inSampleSize屬性進(jìn)行縮小,如果縮小之后的尺寸仍然大于imageview尺寸,再利用inDensity和inTargetDensity繼續(xù)縮小。通過這種方式可以嚴(yán)格縮小到我們所需要的尺寸。比如一張圖片大小是1000 * 1000,我們展示的imageView大小是200 * 200,那么首先計(jì)算出inSampleSize = 4縮小到250 * 250,仍然大于我們需要的200 * 200,再利用inDensity和inTargetDensity計(jì)算出一個(gè)0.8的系數(shù)縮小到200 * 200。

Glide中Bitmap內(nèi)存復(fù)用

Glide默認(rèn)支持了Bitmap的內(nèi)存復(fù)用。Glide內(nèi)存復(fù)用的處理是這樣的,在解碼一張圖片的尺寸后,先去bitmapPool中查找有沒有可以復(fù)用的內(nèi)存,如果有直接拿來復(fù)用,如果沒有,會(huì)先申請(qǐng)一塊沒有像素信息的內(nèi)存,然后把這塊沒有像素信息的bitmap內(nèi)存復(fù)用給我們需要解碼的圖片。

public Bitmap getDirty(int width, int height, Bitmap.Config config) {
  Bitmap result = getDirtyOrNull(width, height, config);
  if (result == null) {
    result = createBitmap(width, height, config);
  }
  return result;
}

可以看到查找和創(chuàng)建時(shí)都傳入了三個(gè)參數(shù)width、height和config,通過width、height和config中的outConfig計(jì)算來所需內(nèi)存大小。后面有個(gè)Glide中Bitmap解碼的坑會(huì)用到這塊。

Glide中Bitmap解碼

我們通過inPreferredConfig來配置Bitmap的解碼方式,系統(tǒng)默認(rèn)使用的是ARGB-8888解碼方式,這個(gè)配置選項(xiàng)只是建議解碼方式,系統(tǒng)真正解碼不一定會(huì)按照我們配置的參數(shù)來解碼,比如說如果圖片有alpha通道,就會(huì)強(qiáng)制使用ARGB-8888解碼;還有8.0版本之后的硬拉位圖更為特殊,會(huì)使很多配置無法生效。

Glide4.0之前版本默認(rèn)使用RGB-565解碼,之后默認(rèn)使用ARGB-8888解碼。但是Glide提供了配置解碼方式的api,如果我們想利用系統(tǒng)解碼的特點(diǎn)通過配置RGB-565的方式來減少bitmap的內(nèi)存占用,會(huì)發(fā)現(xiàn)一直無法“生效”。

下面來看一下Glide配置Bitmap解碼方式的邏輯。

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;
  }
 
  // Changing configs can cause skewing on 4.1, see issue #128.
  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) {
    if (Log.isLoggable(TAG, Log.DEBUG)) {
      Log.d(TAG, "Cannot determine whether the image has alpha or not from header"
          + ", format " + format, e);
    }
  }
 
  optionsWithScaling.inPreferredConfig =
      hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
  if (optionsWithScaling.inPreferredConfig == Config.RGB_565) {
    optionsWithScaling.inDither = true;
  }
}

可以看到,首先判斷是否有硬拉位圖的配置,如果有直接返回。然后是否為ARGB-8888或者有alpha通道,如果沒有alpha通道配置為RGB-565解碼。我們配置解碼方式為RGB-565并且使用一張沒有alpha通道的圖片,最終解碼方式為RGB-565,看一下為什么不“生效”。

下面是Glide解碼的大部分配置處理,包括尺寸縮小,第12行就是上面的配置解碼方式。

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);
 
  ....
 
 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);
 
      ....
 
    if (expectedWidth > 0 && expectedHeight > 0) {
      setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
    }
  }
  Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
  callbacks.onDecodeComplete(bitmapPool, downsampled);
 
  ....
 
  return rotated;
}

如果服務(wù)端返回的圖片大小和前端所需展示的相差不大,那么這里inSampleSize就是1。然后會(huì)進(jìn)入第41行,這個(gè)方法就是內(nèi)存復(fù)用了??匆幌聝?nèi)存復(fù)用的邏輯。

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;
    }
    // On API 26 outConfig may be null for some images even if the image is valid, can be decoded
    // and outWidth/outHeight/outColorSpace are populated (see b/71513049).
    expectedConfig = options.outConfig;
  }
 
  if (expectedConfig == null) {
    // We're going to guess that BitmapFactory will return us the config we're requesting. This
    // isn't always the case, even though our guesses tend to be conservative and prefer configs
    // of larger sizes so that the Bitmap will fit our image anyway. If we're wrong here and the
    // config we choose is too small, our initial decode will fail, but we will retry with no
    // inBitmap which will succeed so if we're wrong here, we're less efficient but still correct.
    expectedConfig = options.inPreferredConfig;
  }
  // BitmapFactory will clear out the Bitmap before writing to it, so getDirty is safe.
  options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
}

如果是8.0及以上系統(tǒng)并且是硬拉位圖直接返回,否則使用options.outConfig。這里就要出問題了,options.outConfig這個(gè)值是非空的,而且是ARGB-8888,這個(gè)參數(shù)是什么時(shí)候賦值的呢?

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 };
}

這個(gè)方法應(yīng)該還記得吧,在上面解碼方法的第一行就是這個(gè)方法用來獲取圖片的尺寸。而獲取尺寸時(shí)傳入的options是沒有inPreferredConfig值的,后面會(huì)說到為什么。還記得上面說的如果沒有配置解碼方式,系統(tǒng)默認(rèn)使用ARGB-8888解碼嗎,所以這里獲取到尺寸后options的outConfig就被賦值了ARGB-8888。

回到上面來,8.0及以后版本,這個(gè)值肯定不為空,導(dǎo)致無法被賦值我們配置的inPreferredConfig。然后調(diào)用bitmapPool.getDirty(width, height, expectedConfig)去獲取要復(fù)用內(nèi)存的bitmap。上面說內(nèi)存復(fù)用時(shí)提到過內(nèi)存復(fù)用通過width、height和outConfig來計(jì)算需要的內(nèi)存大小,這里計(jì)算的大小就是ARGB-8888解碼4個(gè)字節(jié)的大小啦。還記得系統(tǒng)內(nèi)存復(fù)用的機(jī)制嗎,內(nèi)存復(fù)用是復(fù)用整個(gè)bitmap的內(nèi)存,所以這里的內(nèi)存大小就是ARGB-8888解碼格式的大小了。

下面來看一下我們配置的inPreferredConfig為什么在我們解碼尺寸的時(shí)候沒有被使用到呢?

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.Options bitmapFactoryOptions = getDefaultOptions();
  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 static synchronized BitmapFactory.Options getDefaultOptions() {
  BitmapFactory.Options decodeBitmapOptions;
  synchronized (OPTIONS_QUEUE) {
    decodeBitmapOptions = OPTIONS_QUEUE.poll();
  }
  if (decodeBitmapOptions == null) {
    decodeBitmapOptions = new BitmapFactory.Options();
    resetOptions(decodeBitmapOptions);
  }
 
  return decodeBitmapOptions;
}
 
 
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方法中在調(diào)用decodeFromWrappedStreams之前通過getDefaultOptions()來獲取BitmapFactory.Options,然后在getDefaultOptions()方法里實(shí)例化了一個(gè)新的BitmapFactory.Options,并通過resetOptions方法進(jìn)行初始化,初始化時(shí)inPreferredConfig值為空,為了使用內(nèi)存復(fù)用機(jī)制設(shè)置了inMutable = true。之后再調(diào)用decodeFromWrappedStreams方法時(shí)傳入這個(gè)options,這就是為什么解碼尺寸時(shí),沒有inPreferredConfig的原因了。

總結(jié):整體看下來為什么會(huì)出現(xiàn)這個(gè)問題呢,Glide使用了Glide.Options和BitmapFactory.Options兩套來維護(hù)解碼方式(當(dāng)然Glide.Options還做了很多其他事情),一直到decode方法時(shí)才實(shí)例化BitmapFactory.Options。在decodeFromWrappedStreams方式解碼時(shí),先調(diào)用了解碼圖片尺寸,這時(shí)給BitmapFactory.Options賦值了ARGB-8888解碼,然后才做calculateConfig解碼方式的配置,導(dǎo)致在獲取復(fù)用內(nèi)存時(shí)獲取到了ARGB-8888解碼的內(nèi)存。

我們配置了inPreferredConfig = RGB-565解碼,復(fù)用了ARGB-8888解碼的內(nèi)存,最后我們的圖片是什么格式呢?

是RGB-565格式的,實(shí)際我們圖片內(nèi)存只有真實(shí)占用內(nèi)存的一半,但整塊內(nèi)存是無法被釋放的,不知道這算不算Glide一個(gè)bug?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容