【28】Bitmap优化

在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.使用缓存,LruCache和DiskLruCache的结合 关于LruCache和DiskLruCache,大家一定不会陌生(有疑问的朋友可以去API官网搜一下LruCache,而DiskLrucCache可以参考一下这篇不错的文章:DiskLruCache使用介绍),出于对性能和app的考虑,我们肯定是想着第一次从网络中加载到图片之后,能够将图片缓存在内存和sd卡中,这样,我们就不用频繁的去网络中加载图片,为了很好的控制内存问题,则会考虑使用LruCache作为Bitmap在内存中的存放容器,在sd卡则使用DiskLruCache来统一管理磁盘上的图片缓存。
2.SoftReference和inBitmap参数的结合 在第二点中提及到,可以采用LruCache作为存放Bitmap的容器,而在LruCache中有一个方法值得留意,那就是entryRemoved,按照文档给出的说法,在LruCache容器满了需要淘汰存放其中的对象腾出空间的时候会调用此方法(注意,这里只是对象被淘汰出LruCache容器,但并不意味着对象的内存会立即被Dalvik虚拟机回收掉),此时可以在此方法中将Bitmap使用SoftReference包裹起来,并用事先准备好的一个HashSet容器来存放这些即将被回收的Bitmap,有人会问,这样存放有什么意义?之所以会这样存放,还需要再提及到inBitmap参数(在Android3.0才开始有的,详情查阅API中的BitmapFactory.Options参数信息),这个参数主要是提供给我们进行复用内存中的Bitmap,如果设置了此参数,且满足以下条件的时候:
Bitmap一定要是可变的,即inmutable设置一定为ture;
Android4.4以下的平台,需要保证inBitmap和即将要得到decode的Bitmap的尺寸规格一致;
Android4.4及其以上的平台,只需要满足inBitmap的尺寸大于要decode得到的Bitmap的尺寸规格即可;

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


Set> mReusableBitmaps;
private LruCache mMemoryCache;

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

mMemoryCache = new LruCache(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(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> 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;
}

3.降低采样率,inSampleSize的计算 相信大家对inSampleSize是一定不会陌生的,所以此处不再做过多的介绍,关于降低采样率对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;
}

4.采用decodeFileDescriptor来编码图片

参考资料:
http://www.mamicode.com/info-detail-516558.html

你可能感兴趣的:(【28】Bitmap优化)