android bitmap的内存分配和优化

    xiaoxiao2025-10-04  3

    首先Bitmap在Android虚拟机中的内存分配,在Google的网站上给出了下面的一段话 

    大致的意思也就是说,在Android3.0之前,Bitmap的内存分配分为两部分,一部分是分配在Dalvik的VM堆中,而像素数据的内存是分配在Native堆中,而到了Android3.0之后,Bitmap的内存则已经全部分配在VM堆上,这两种分配方式的区别在于,Native堆的内存不受Dalvik虚拟机的管理,我们想要释放Bitmap的内存,必须手动调用Recycle方法,而到了Android 3.0之后的平台,我们就可以将Bitmap的内存完全放心的交给虚拟机管理了,我们只需要保证Bitmap对象遵守虚拟机的GC Root Tracing的回收规则即可。OK,基础知识科普到此。接下来分几个要点来谈谈如何优化Bitmap内存问题。

    针对3.0版本的优化方案,请看以下代码,

    private int mCacheRefCount = 0;//缓存引用计数器 private int mDisplayRefCount = 0;//显示引用计数器 ... // 当前Bitmap是否被显示在UI界面上 public void setIsDisplayed(boolean isDisplayed) { synchronized (this) { if (isDisplayed) { mDisplayRefCount++; mHasBeenDisplayed = true; } else { mDisplayRefCount--; } } checkState(); } //标记是否被缓存 public void setIsCached(boolean isCached) { synchronized (this) { if (isCached) { mCacheRefCount++; } else { mCacheRefCount--; } } checkState(); } //用于检测Bitmap是否已经被回收 private synchronized void checkState() { if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed && hasValidBitmap()) { getBitmap().recycle(); } } private synchronized boolean hasValidBitmap() { Bitmap bitmap = getBitmap(); return bitmap != null && !bitmap.isRecycled(); }

    通过引用计数的方法(mDisplayRefCount 与 mCacheRefCount)来追踪一个bitmap目前是否有被显示或者是在缓存中. 当下面条件满足时回收bitmap。

    2.使用缓存,LruCache和DiskLruCache的结合 LruCache和DiskLruCache,大家一定不会陌生出于对性能和app的考虑,我们肯定是想着第一次从网络中加载到图片之后,能够将图片缓存在内存和sd卡中,这样,我们就不用频繁的去网络中加载图片,为了很好的控制内存问题,则会考虑使用LruCache作为Bitmap在内存中的存放容器,在sd卡则使用DiskLruCache来统一管理磁盘上的图片缓存。

    3.SoftReference和inBitmap参数的结合 在第二点中提及到,可以采用LruCache作为存放Bitmap的容器,而在LruCache中有一个方法值得留意,那就是entryRemoved,按照文档给出的说法,在LruCache容器满了需要淘汰存放其中的对象腾出空间的时候会调用此方法(注意,这里只是对象被淘汰出LruCache容器,但并不意味着对象的内存会立即被Dalvik虚拟机回收掉),此时可以在此方法中将Bitmap使用SoftReference包裹起来,并用事先准备好的一个HashSet容器来存放这些即将被回收的Bitmap,有人会问,这样存放有什么意义?之所以会这样存放,还需要再提及到inBitmap参数(在Android3.0才开始有的,详情查阅API中的BitmapFactory.Options参数信息),这个参数主要是提供给我们进行复用内存中的Bitmap。

    如果需要使用Bitmap的option参数还需要满足以下几个条件:

    Bitmap一定要是可变的,即inmutable设置一定为ture;Android4.4以下的平台,需要保证inBitmap和即将要得到decode的Bitmap的尺寸规格一致;Android4.4及其以上的平台,只需要满足inBitmap的尺寸大于要decode得到的Bitmap的尺寸规格即可; 4.降低采样率,inSampleSize的计算  

    直接上代码

    public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } long totalPixels = width / inSampleSize * height / inSampleSize ; final long totalReqPixelsCap = reqWidth * reqHeight * 2; while (totalPixels > totalReqPixelsCap) { inSampleSize *= 2; totalPixels /= 2; } } return inSampleSize; 5.采用decodeFileDescriptor来编码图片,比直接使用decodeFile更省内存

    查看BitmapFactory的源码,对比一下两者的实现,可以发现decodeFile()最终是以流的方式生成bitmap 

    decodeFile源码:

    [java]  view plain  copy public static Bitmap decodeFile(String pathName, Options opts) {       Bitmap bm = null;       InputStream stream = null;       try {           stream = new FileInputStream(pathName);           bm = decodeStream(stream, null, opts);       } catch (Exception e) {           /*  do nothing.              If the exception happened on open, bm will be null.          */       } finally {           if (stream != null) {               try {                   stream.close();               } catch (IOException e) {                   // do nothing here               }           }       }       return bm;   }  

    decodeFileDescriptor的源码,可以找到native本地方法decodeFileDescriptor,通过底层生成bitmap

    decodeFileDescriptor源码:

    [java]  view plain  copy    public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) {          if (nativeIsSeekable(fd)) {              Bitmap bm = nativeDecodeFileDescriptor(fd, outPadding, opts);              if (bm == null && opts != null && opts.inBitmap != null) {                  throw new IllegalArgumentException("Problem decoding into existing bitmap");              }              return finishDecode(bm, outPadding, opts);          } else {              FileInputStream fis = new FileInputStream(fd);              try {                  return decodeStream(fis, outPadding, opts);              } finally {                  try {                      fis.close();                  } catch (Throwable t) {/* ignore */}              }          }      }      private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,Rect padding, Options opts);  

    最新回复(0)