Bitmaps加载之内部存储器管理

日期:2019-09-07编辑作者:编程应用

除了前面说的Bitmap缓存之外,还有一些事情我们可以做来使用好GC和Bitmap的重用. 对于不同的Android版本要做不同的处理,这样才能达到高效使用Bitmap的效果,这是推荐的策略.

以下内容整理自互联网,仅用于个人学习

这里先介绍一些关于Android中Bitmap内存管理的基础知识铺垫一下:


  • 在Android2.2及以前,当GC开始回收时,app的所有线程都将停止, 这就导致了延迟的产生,进而影响体验. Android2.3及之后就不会用这个问题了,因此增加了GC的并发处理,也就意味着Bitmap被清理之后app的可用空间会很快回收回来.
  • Android2.3.3及以前,Bitmap的图片数据是保存在native memory上, 而Bitmap对象是保存在Dalvik的heap上,这样这两个就分离开了,就会导致内存释放不及时从而带来潜在的OOM. 在Android 3.0之后这个问题就解决了,因为这两个都被放在Dalvik的heap上.

1. Bitmap的存储位置

在Android2.3.3(API 10)及之前的版本中,Bitmap对象与其像素数据是分开存储的,Bitmap对象存储在Dalvik heap中,而Bitmap对象的像素数据则存储在Native Memory(本地内存)中或者说Derict Memory(直接内存)中,这使得存储在Native Memory中的像素数据的释放是不可预知的,我们可以调用recycle()方法来对Native Memory中的像素数据进行释放,前提是你可以清楚的确定Bitmap已不再使用了,如果你调用了Bitmap对象recycle()之后再将Bitmap绘制出来,就会出现Canvas: trying to use a recycled bitmap错误,而在Android3.0(API 11)之后,Bitmap的像素数据和Bitmap对象一起存储在Dalvik heap中,所以我们不用手动调用recycle()来释放Bitmap对象,内存的释放都交给垃圾回收器来做。

下面介绍如何根据不同的Android版本来管理Bitmap的内存.

2. Bitmap导致的OOM

假如系统启动了垃圾回收线程去收集垃圾,而此时我们一下子产生大量的Bitmap对象,此时是有可能会产生OutOfMemoryError,因为垃圾回收器首先要判断某个对象是否还存活(JAVA语言判断对象是否存活使用的是根搜索算法GC Root Tracing),然后利用垃圾回收算法来对垃圾进行回收,不同的垃圾回收器具有不同的回收算法,这些都是需要时间的,发生OutOfMemoryError的时候,我们要明确到底是因为内存泄露(Memory Leak)引发的还是内存溢出(Memory overflow)引发的:

  • 如果是内存泄露我们需要利用工具(比如MAT)查明内存泄露的代码并进行改正。
  • 如果不存在泄露,换句话来说就是内存中的对象确实还必须活着,那我们可以看看是否可以通过某种途径,减少对象对内存的消耗,比如我们在使用Bitmap的时候,应该根据View的大小利用BitmapFactory.Options计算合适的inSimpleSize来对Bitmap进行相对应的裁剪,以减少Bitmap对内存的使用。

如果上面都做好了还是存在OutOfMemoryError(一般这种情况很少发生)的话,那我们只能调大Dalvik heap的大小了,在Android 3.1以及更高的版本中,我们可以在AndroidManifest.xml的application标签中增加一个值等于true的android:largeHeap属性来通知Dalvik虚拟机应用程序需要使用较大的Java Heap,但是我们也不鼓励这么做。

Android2.3.3及以下

在这个版本范围内,推荐使用recycle()方法, 该方法会尽快把Bitmap的内存回收回来.

  • 注意: 你只有在确定这个Bitmap不再使用的情况下才去调用recycle()方法. 不然如果你调用recycle()之后又要想去使用之前的Bitmap,会抛出一个异常:"Canvas: trying to use a recycled bitmap"

下面的代码是Demo中RecyclingBitmapDrawable的一部分,其中mDisplayRefCount和mCacheRefCount这两个变量用来记录该Bitmap显示和缓存情况,具体回收条件如下:

  1. mDisplayRefCount和mCacheRefCount的值都为0.
  2. Bitmap不为空.

完整代码请参考官方demo,下面Reference带下载地址.

private int mCacheRefCount = 0;private int mDisplayRefCount = 0;...// Notify the drawable that the displayed state has changed.// Keep a count to determine when the drawable is no longer displayed.public void setIsDisplayed(boolean isDisplayed) { synchronized  { if (isDisplayed) { mDisplayRefCount++; mHasBeenDisplayed = true; } else { mDisplayRefCount--; } } // Check to see if recycle() can be called. checkState();}// Notify the drawable that the cache state has changed.// Keep a count to determine when the drawable is no longer being cached.public void setIsCached(boolean isCached) { synchronized  { if  { mCacheRefCount++; } else { mCacheRefCount--; } } // Check to see if recycle() can be called. checkState();}private synchronized void checkState() { // If the drawable cache and display ref counts = 0, and this drawable // has been displayed, then recycle. if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed && hasValidBitmap { getBitmap().recycle(); }}private synchronized boolean hasValidBitmap() { Bitmap bitmap = getBitmap(); return bitmap != null && !bitmap.isRecycled();}

3. Bitmap的四种属性

图片压缩质量参数 枚举变量

  • public static final Bitmap.Config ALPHA_8
  • public static final Bitmap.Config ARGB_4444
  • public static final Bitmap.Config ARGB_8888
  • public static final Bitmap.Config RGB_565

ARGB指的是一种色彩模式,里面A代表Alpha,R表示red,G表示green,B表示blue, 其实所有的可见色都是红绿蓝组成的,所以红绿蓝又称为三原色,每个原色都存储着所表示颜色的信息值

  • ALPHA_8就是Alpha由8位组成
  • ARGB_4444就是由4个4位组成即16位,
  • ARGB_8888就是由4个8位组成即32位,
  • RGB_565就是R为5位,G为6位,B为5位共16位

由此可见

  • ALPHA_8 代表8位Alpha位图
  • ARGB_4444 代表16位ARGB位图
  • ARGB_8888 代表32位ARGB位图
  • RGB_565 代表8位RGB位图

Android 3.0及以上

Android 3.0引入了 BitmapFactory.Options.inBitmap属性.如果设置了该属性, BitmapFactory带有Options参数的decode相关方法会尝试去重用已存在的Bitmap, 这就意味这Bitmap的内存空间得到了重用, 就可以改善性能,减少内存分配和回收.但是使用inBitmap这个属性有一些限制, 有一点比较特别的是在Android4.4以前,只有相同大小的Bitmap才可以重用,具体可以看inBitmap文档.

下面看具体实例:

1. 保存Bitmap

下面是用一个HashSet来保存从LruCache中移除的Bitmap的软引用.

Set<SoftReference<Bitmap>> mReusableBitmaps;private LruCache<String, BitmapDrawable> mMemoryCache;// If you're running on Honeycomb or newer, create a// synchronized HashSet of references to reusable bitmaps.if (Utils.hasHoneycomb { mReusableBitmaps = Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>;}mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) { // Notify the removed entry that is no longer being cached. @Override protected void entryRemoved(boolean evicted, String key, BitmapDrawable oldValue, BitmapDrawable newValue) { if (RecyclingBitmapDrawable.class.isInstance) { // The removed entry is a recycling drawable, so notify it // that it has been removed from the memory cache. ((RecyclingBitmapDrawable) oldValue).setIsCached; } else { // The removed entry is a standard BitmapDrawable. if (Utils.hasHoneycomb { // We're running on Honeycomb or later, so add the bitmap // to a SoftReference set for possible use with inBitmap later. mReusableBitmaps.add (new SoftReference<Bitmap>(oldValue.getBitmap; } } }....}

2. 重用Bitmap

查找是否有可重用的Bitmap

public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth, int reqHeight, ImageCache cache) { final BitmapFactory.Options options = new BitmapFactory.Options(); ... BitmapFactory.decodeFile(filename, options); ... // If we're running on Honeycomb or newer, try to use inBitmap. if (Utils.hasHoneycomb { addInBitmapOptions(options, cache); } ... return BitmapFactory.decodeFile(filename, options);}

如果找到可用的就设置inBitmap

private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) { // inBitmap only works with mutable bitmaps, so force the decoder to // return mutable bitmaps. options.inMutable = true; if (cache != null) { // Try to find a bitmap to use for inBitmap. Bitmap inBitmap = cache.getBitmapFromReusableSet; if (inBitmap != null) { // If a suitable bitmap has been found, set it as the value of // inBitmap. options.inBitmap = inBitmap; } }}// This method iterates through the reusable bitmaps, looking for one // to use for inBitmap:protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) { Bitmap bitmap = null; if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty { synchronized (mReusableBitmaps) { final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator(); Bitmap item; while (iterator.hasNext { item = iterator.next; if (null != item && item.isMutable { // Check to see it the item can be used for inBitmap. if (canUseForInBitmap(item, options)) { bitmap = item; // Remove from reusable set so it can't be used again. iterator.remove(); break; } } else { // Remove from the set if the reference has been cleared. iterator.remove(); } } } } return bitmap;}

查找时具体的匹配条件如下

static boolean canUseForInBitmap( Bitmap candidate, BitmapFactory.Options targetOptions) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // From Android 4.4  onward we can re-use if the byte size of // the new bitmap is smaller than the reusable bitmap candidate // allocation byte count. int width = targetOptions.outWidth / targetOptions.inSampleSize; int height = targetOptions.outHeight / targetOptions.inSampleSize; int byteCount = width * height * getBytesPerPixel(candidate.getConfig; return byteCount <= candidate.getAllocationByteCount(); } // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1 return candidate.getWidth() == targetOptions.outWidth && candidate.getHeight() == targetOptions.outHeight && targetOptions.inSampleSize == 1;}/** * A helper function to return the byte usage per pixel of a bitmap based on its configuration. */static int getBytesPerPixel(Config config) { if (config == Config.ARGB_8888) { return 4; } else if (config == Config.RGB_565) { return 2; } else if (config == Config.ARGB_4444) { return 2; } else if (config == Config.ALPHA_8) { return 1; } return 1;}

4. Bitmap的使用

创建Bitmap的时候,Java不提供new Bitmap()的形式去创建,而是通过BitmapFactory中的静态方法去创建,如:BitmapFactory.decodeStream(is),通过InputStream去解析生成Bitmap。

在BitmapFactory中,我们可以很方便的通过BitmapFactory.Options中的options.inSampleSize去设置Bitmap的压缩比。

如果我们需要一次性加载大量的图片,我们不希望多次解析相同的Bitmap,也不希望可能不会用到的Bitmap一直存在于内存中,所以,这个场景下,Bitmap的重用变得异常的重要,Android提供BitmapFactory.Options.inBitmap的重用方式。

Reference

  1. Managing Bitmap Memory
  2. 官方DisplayingBitmaps Demo

本文由今晚最快开奖现场直播发布于编程应用,转载请注明出处:Bitmaps加载之内部存储器管理

关键词: