Android性能优化:谈话Bitmap内存管理和优化

最近除了那些忙着项目开发的事情,目前正在准备个人论文。短的时间没有写博客,今晚可贵想总结。只要有一点时间。所以,为了凑合用,行。唠叨罗嗦,直接进入正题。java

从事Android自移动终端的发展,想必是经常要与内存问题打交道的,说到Android开发中遇到的内存问题,像Bitmap这样的吃内存的大户略微处理不当就很是easy形成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)缓存

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:
mDisplayRefCount 与 mCacheRefCount 的引用计数均为 0.
bitmap不为null, 并且它尚未被回收.markdown

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,假设设置了此參数,且知足如下条件的时候:app

  • Bitmap必定要是可变的,即inmutable设置必定为ture;
  • Android4.4如下的平台,需要保证inBitmap和即将要获得decode的Bitmap的尺寸规格一致;
  • Android4.4及其以上的平台,仅仅需要知足inBitmap的尺寸大于要decode获得的Bitmap的尺寸规格就能够;

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

Set<SoftReference<Bitmap>> mReusableBitmaps;
private LruCache<String, BitmapDrawable> mMemoryCache;

// 用来盛放被LruCache淘汰出列的Bitmap
if (Utils.hasHoneycomb()) {
    mReusableBitmaps =
            Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
}

mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {

    // 当LruCache淘汰对象的时候被调用,用于在内存中重用Bitmap,提升载入图片的性能
    @Override
    protected void entryRemoved(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()));
            }
        }
    }
....
}

private static void addInBitmapOptions(BitmapFactory.Options options,
        ImageCache cache) {
        //将inMutable设置true,inBitmap生效的条件之中的一个
    options.inMutable = true;

    if (cache != null) {
        // 尝试寻找可以内存中课复用的的Bitmap
        Bitmap inBitmap = cache.getBitmapFromReusableSet(options);

        if (inBitmap != null) {

            options.inBitmap = inBitmap;
        }
    }
}

// 获取当前可以知足复用条件的Bitmap,存在则返回该Bitmap,不存在则返回null
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().get();

                if (null != item && item.isMutable()) {

                    if (canUseForInBitmap(item, options)) {
                        bitmap = item;
                        iterator.remove();
                        break;
                    }
                } else {

                    iterator.remove();
                }
            }
        }
    }
    return bitmap;
}

//推断是否知足使用inBitmap的条件
static boolean 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;
}

4.减小採样率,inSampleSize的计算
相信你们对inSampleSize是必定不会陌生的,因此此处再也不作过多的介绍,关于减小採样率对inSampleSize的计算方法。我看到网上的算法有很是多。如下的这段算法应该是最好的算法了,当中还考虑了那种宽高相差很是悬殊的图片(好比:全景图)的处理。ide

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来编码图片(临时不知道原理。欢迎高手指点迷津)
关于採用decodeFileDescriptor去处理图片可以节省内存这方面。我在写代码的时候进行过尝试。确实想比其它的decode方法要节省内存,查询了网上的解释。不是很是清楚,本身看了一些源码也弄不出个名堂,为何使用这样的方式就可以节省内存一些呢,假设有明确当中原理的高手。欢迎解答个人疑惑性能

到此,关于Bitmap处理的几个优化点已经分析完成,就眼下来讲,可能你们在开发的过程习惯了使用框架来载入图片,因此不大在乎图片内存处理的相关问题,假设你想知道一些优化Bitmap内存原理或者想本身作一个优秀的图片载入框架。但愿本文可以为你提供一点点思路。假设读者以为文章有错误,欢迎在下方评论中批评指正。学习

相关文章
相关标签/搜索