Bitmap内存管理

Bitmap内存计算:

简单地说是分辨率像素点大小 (长宽*像素点占用的字节)

  1. ARGB_8888 ARGB各占8位所以4个字节
  2. ARGB_4444 2个字节(不推荐使用,看起来质量太差)
  3. RGB_565 R 5位,G 6位,B 5位,即16位,2个字节
    更详细的讲解:
    https://www.cnblogs.com/dasusu/p/9789389.html

getByteCount
返回位图像素点的最小字节数,即内存。
Bitmap.Options
inDensity: 表示这个bitmap的像素密度,根据drawable/mipmap目录。
inTargetDensity: 表示屏幕的像素密度。
getResource().getDisplayMetrics().densityDpi。

Bitmap内存压缩

Bitmap.Options
inJustDecodeBounds: 设置true 读取图片outxxx参数如:outWidth、outHeight。
inPreferedConfig:设置图片解码后的像素格式,如ARGB_888/RGB565
inSampleSize:设置图片解码缩放比,值为2的倍数,1则不进行缩放,如设置4,则加载图片的宽高是原图的1/4,内存大小则是1/16。

对于内存的降低,无论是选择jpg还是png更或者是webp。其实都是毫无意义的。Jpg是属于有损压缩,我们看见的jpg比png文件小,那是因为压缩率高。这都是属于文件存储范畴。对于内存来说,我们加载一张不带alpha通道使用RGB_565格式的png与一张jpg占用的内存大小都是一样的。
对于内存的压缩我们能做的就是缩小图片尺寸与改变像素格式。
网上大多计算压缩比的方式如下:

private Bitmap getimage(String srcPath) {
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        //开始读入图片,此时把options.inJustDecodeBounds 设回true了
        newOpts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeFile(srcPath,newOpts);//此时返回bm为空

        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        float hh = 800f;
        float ww = 480f;
        //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int be = 1;//be=1表示不缩放
        if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;//设置缩放比例
        //重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
        bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
        return compressImage(bitmap);//压缩好比例大小后再进行质量压缩
    }

看见一个有一点点不一样的计算缩放比的方式,旨在记录一下:

 /**
     *
     * @param context
     * @param id 图片id
     * @param maxWidth 限制最大的宽
     * @param maxHeight 限制最大的高
     * @param hasAlpha 是否有透明度
     * @return
     */
    public static Bitmap resizeBitmap(Context context, int id, int maxWidth, int maxHeight, Boolean hasAlpha) {
        Resources resources = context.getResources();
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources, id, options);
        int w = options.outWidth;
        int h = options.outHeight;
        //设置缩放系数
        options.inSampleSize = calculateInSampleSize(w, h, maxWidth, maxHeight);
        if (!hasAlpha) {
            options.inPreferredConfig = Bitmap.Config.RGB_565;
        }
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(resources, id, options);
    }

    private static int calculateInSampleSize(int w, int h, int maxWidth, int maxHeight) {
        int inSampleSize = 1;
        if (w > maxWidth && h > maxHeight) {
            inSampleSize = 2;
            //循环使宽高小于最大的宽高
            while (w / inSampleSize > maxWidth && h / inSampleSize > maxHeight) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }

Bitmap缓存

Bitmap内存管理的官方文档
https://developer.android.google.cn/topic/performance/graphics/manage-memory.html#java
Google的图片浏览demo
https://developer.android.google.cn/samples/DisplayingBitmaps/index.html

内存缓存

首先是创建图片,不管是网络加载还是本地加载的,在这个创建的过程中,系统会为每一个图片申请一块内存,我们使用LruCache缓存这些bitmap,每张图片都会申请内存,会导致内存占用过多,所以就需要使用到bitmap的内存复用,就是当缓存的bitmap被回收的时候,我们使用一个复用池来存放这些bitmap,当有新的图片需要创建并申请内存的时候,我们把复用池中的bitmap内存给他,这样有利的减少内存的申请。这里使用HashSet作为复用池

 //线程安全
        resuablePool = Collections.synchronizedSet(new HashSet>());

        //maxSize 能够缓存的内存最大数
        mMemoryCache = new LruCache(memory / 8 * 1024 * 1024) {
            /**
             *getAllocationByteCount
    一般情况下getByteCount()与getAllocationByteCount()是相等的;通过复用Bitmap来解码图片,
如果被复用的Bitmap的内存比待分配内存的Bitmap大,
那么getByteCount()表示新解码图片占用内存的大小(并非实际内存大小,实际大小是复用的那个Bitmap的大小),
getAllocationByteCount()表示被复用Bitmap占用的内存大小。所以可能allocation比bytecount大。

             * @param key
             * @param value
             * @return value占用的内存
             */
            @Override
            protected int sizeOf(Integer key, Bitmap value) {
                if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
                    return value.getAllocationByteCount();
                }
                return value.getByteCount();
            }

            /**
             * 当bitmap从lruCache中移除时回调
             * @param evicted
             * @param key
             * @param oldValue
             * @param newValue
             */
            @Override
            protected void entryRemoved(boolean evicted, Integer key, Bitmap oldValue, Bitmap newValue) {
                if (oldValue.isMutable()) {
                    resuablePool.add(new WeakReference(oldValue));
                } else {
                    oldValue.recycle();
                }
            }
        };

但是这个内存复用是有条件的:

  • 可被复用的Bitmap必须设置inMutable为true;
  • Android4.4(API 19)之前只有格式为jpg、png,同等宽高(要求苛刻),inSampleSize为1的Bitmap才可以复用;
  • Android4.4(API 19)之前被复用的Bitmap的inPreferredConfig会覆盖待分配内存的Bitmap设置的inPreferredConfig;
  • Android4.4(API 19)之后被复用的Bitmap的内存必须大于等于需要申请内存的Bitmap的内存;

磁盘缓存
磁盘缓存使用DiskLruCache
https://github.com/JakeWharton/DiskLruCache

public void putBitmap2Disk(String key, Bitmap bitmap) {
        DiskLruCache.Snapshot snapshot = null;
        OutputStream os = null;
        try {
            snapshot = diskLruCache.get(key);
            if (snapshot == null) {
                DiskLruCache.Editor edit = diskLruCache.edit(key);
                if (edit != null) {
                    os = edit.newOutputStream(0);
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);
                    edit.commit();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (snapshot != null) {
                snapshot.close();
            }
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

当内存没有图片,就要从磁盘获取,这个时候获取到的新图片就应该使用内存复用了

/**
     * 
     * @param key
     * @param reusable 复用池里的图片
     * @return
     */
    public Bitmap getBitmapFromDisk(String key,Bitmap reusable) {
        DiskLruCache.Snapshot snapshot = null;
        Bitmap bitmap = null;
        try {
            snapshot = diskLruCache.get(key);
            if (snapshot == null) {
                return null;
            }
            //获得文件输入流 读取bitmap
            //is的close snapshot.close() 已经帮助close了
            InputStream is = snapshot.getInputStream(0);
            //内存复用
            options.inMutable = true;
            options.inBitmap = reusable;
            bitmap = BitmapFactory.decodeStream(is,null,options);
            if (bitmap != null){
                mMemoryCache.put(key,bitmap);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (snapshot != null) {
                snapshot.close();
            }
        }
        return bitmap;
    }
/**
     * 从复用池里获取可用于内存复用的bitmap
     * @param w
     * @param h
     * @param inSampleSize
     * @return
     */
    public Bitmap getReusable(int w, int h, int inSampleSize) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            return null;
        }
        Iterator> iterator = resuablePool.iterator();
        Bitmap reusable = null;
        //迭代查找符合复用条件的bitmap
        while (iterator.hasNext()) {
            Bitmap bitmap = iterator.next().get();
            if (bitmap != null) {
                if (checkInBitmap(bitmap, w, h, inSampleSize)) {
                    reusable = bitmap;
                    //移除复用池
                    iterator.remove();
                    break;
                }
            } else {
                iterator.remove();
            }
        }
        return reusable;
    }
private boolean checkInBitmap(Bitmap bitmap, int w, int h, int inSampleSize) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            return bitmap.getWidth() == w && bitmap.getHeight() == h
                    && inSampleSize == 1;
        }
        //如果缩放系数大于1 获得缩放后的宽高
        if (inSampleSize > 1) {
            w /= inSampleSize;
            h /= inSampleSize;
        }
        int byteCount = w * h * getPixeCount(bitmap.getConfig());
        return byteCount <= bitmap.getAllocationByteCount();
    }

    int getPixeCount(Bitmap.Config config) {
        if (config == Bitmap.Config.ARGB_8888) {
            return 4;
        }
        return 2;
    }

你可能感兴趣的:(Bitmap内存管理)