Android性能优化:谈谈Bitmap的内存管理与优化

最近除了忙着项目开发上的事情,还有就是准备我的毕业论文,有一小段时间没写博客了,今晚难得想总结一下,刚好又有一点时间,于是凑合着来一篇,好了,唠叨话不多说,直接入正题。从事Android移动端的开发以来,想必是经常要与内存问题打交道的,说到Android开发中遇到的内存问题,像Bitmap这种吃内存的大户稍微处理不当就很容易造成OOM,当然,目前已经有很多知名的开源图片加载框架,例如:ImageLoader,Picasso等等,这些框架已经能够很好的解决了Bitmap造成的OOM问题,虽然这些框架能够节省很多开发者的宝贵时间,但是也会遇到一种情况,很多初学者只是会简单的去调用这些框架的提供的接口,被问到框架内部的一些实现原理,基本上都是脑中一片空白。从我的观点出发,我认为如果能够掌握一些框架原理,想必对我们进行应用调优的意义是非常重大的,今天,主要是是想谈谈,如果没有了图片加载框架,我们要怎么去处理Bitmap的内存问题呢? 谈到Bitmap处理的问题,我们可能要先来了解一些基础的知识,关于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内存问题。

1.Bitmap的引用计数方式(针对Android3.0之前平台的优化方案,先上Demo Code)

mDisplayRefCount = 0;//显示引用计数器…(boolean isDisplayed) {synchronized (this) {if (isDisplayed) {mDisplayRefCount++;mHasBeenDisplayed = true;} else {mDisplayRefCount–;}}checkState();}(boolean isCached) {synchronized (this) {if (isCached) {mCacheRefCount++;} else {mCacheRefCount–;}}checkState();}() {if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed&& hasValidBitmap()) {getBitmap().recycle();}}() {Bitmap bitmap = getBitmap();return bitmap != null && !bitmap.isRecycled();}

上面的实例代码,它使用了引用计数的方法(mDisplayRefCount 与 mCacheRefCount)来追踪一个bitmap目前是否有被显示或者是在缓存中. 当下面条件满足时回收bitmap: mDisplayRefCount 与 mCacheRefCount 的引用计数均为 0. bitmap不为null, 并且它还没有被回收.

2.使用缓存,LruCache和DiskLruCache的结合 关于LruCache和DiskLruCache,大家一定不会陌生(有疑问的朋友可以去API官网搜一下LruCache,而DiskLrucCache可以参考一下这篇不错的文章: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,如果设置了此参数,且满足以下条件的时候:

在满足以上条件的时候,系统对图片进行decoder的时候会检查内存中是否有可复用的Bitmap,避免我们频繁的去SD卡上加载图片而造成系统性能的下降,毕竟从直接从内存中复用要比在SD卡上进行IO操作的效率要提高几十倍。写了太多文字,下面接着给出几段Demo Code

Set<SoftReference<Bitmap>> mReusableBitmaps;private LruCache<String, BitmapDrawable> mMemoryCache;// 用来盛放被LruCache淘汰出列的Bitmapif (Utils.hasHoneycomb()) {mReusableBitmaps =Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());}mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {(boolean evicted, String key,BitmapDrawable oldValue, BitmapDrawable newValue) {if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {((RecyclingBitmapDrawable) oldValue).setIsCached(false);} else {if (Utils.hasHoneycomb()) {mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));}}}….}(BitmapFactory.Options options,ImageCache cache) {//将inMutable设置true,inBitmap生效的条件之一options.inMutable = true;if (cache != null) {// 尝试寻找可以内存中课复用的的BitmapBitmap inBitmap = cache.getBitmapFromReusableSet(options);if (inBitmap != null) {options.inBitmap = inBitmap;}}}// 获取当前可以满足复用条件的Bitmap,存在则返回该Bitmap,不存在则返回nullprotected 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().get();if (null != item && item.isMutable()) {if (canUseForInBitmap(item, options)) {bitmap = item;iterator.remove();break;}} else {iterator.remove();}}}}return bitmap;}canUseForInBitmap(Bitmap candidate, BitmapFactory.Options targetOptions) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {// Android4.4开始,被复用的Bitmap尺寸规格大于等于需要的解码规格即可满足复用条件int width = targetOptions.outWidth / targetOptions.inSampleSize;int height = targetOptions.outHeight / targetOptions.inSampleSize;int byteCount = width * height * getBytesPerPixel(candidate.getConfig());return byteCount <= candidate.getAllocationByteCount();}// Android4.4之前,必须满足被复用的Bitmap和请求的Bitmap尺寸规格一致才能被复用return candidate.getWidth() == targetOptions.outWidth&& candidate.getHeight() == targetOptions.outHeight&& targetOptions.inSampleSize == 1;}上天完全是为了坚强你的意志,才在道路上设下重重的障碍。

Android性能优化:谈谈Bitmap的内存管理与优化

相关文章:

你感兴趣的文章:

标签云: