您当前的位置: 首页 >  性能优化
  • 0浏览

    0关注

    674博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

性能优化专题三--内存优化(图片三级缓存)

沙漠一只雕得儿得儿 发布时间:2020-05-12 22:02:38 ,浏览量:0

图片作为内存消耗大户,一直是开发人员尝试优化的重点对象。Bitmap的内存从3.0以前的位于native,到后来改成jvm,再到8.0又改回到native。jvm每个进程都有内存上限,而native则没有限制(不是没有影响,至少不会oom),所以把内存大户Bitmap挪到native可能是很多人的梦想,但native的管理和实现明显比jvm更为复杂,除非有现成实现,很少有人去动这一块。

手段一:使用inSampleSize采样率压缩,尺寸压缩:

这个是老生常谈了,使用options.inJustDecodeBounds来获取原始尺寸,然后按需使用options.inSampleSize来采样图片到接近view尺寸。

BitmapFactory 在解码图片时,可以带一个Options,有一些比较有用的功能,比如:

  • inTargetDensity 表示要被画出来时的目标像素密度

  • inSampleSize 这个值是一个int,当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数。例如,width=100,height=100,inSampleSize=2,那么就会将bitmap处理为,width=50,height=50,宽高降为1 / 2,像素数降为1 / 4

  • inJustDecodeBounds 字面意思就可以理解就是只解析图片的边界,有时如果只是为了获取图片的大小就可以用这个,而不必直接加载整张图片。

  • inPreferredConfig 默认会使用ARGB_8888,在这个模式下一个像素点将会占用4个byte,而对一些没有透明度要求或者图片质量要求不高的图片,可以使用RGB_565,一个像素只会占用2个byte,一下可以省下50%内存。

  • inPurgeable和inInputShareable 这两个需要一起使用,BitmapFactory.java的源码里面有注释,大致意思是表示在系统内存不足时是否可以回收这个bitmap,有点类似软引用,但是实际在5.0以后这两个属性已经被忽略,因为系统认为回收后再解码实际会反而可能导致性能问题

  • inBitmap 官方推荐使用的参数,表示重复利用图片内存,减少内存分配,在4.4以前只有相同大小的图片内存区域可以复用,4.4以后只要原有的图片比将要解码的图片大既可以复用了。

手段二:合理选择Bitmap的像素格式:

ARGB8888格式的图片,每像素占用 4 Byte,而 RGB565则是 2 Byte。如果是 ARGB8888 那么就是一个像素4个字节,如果是 RGB565 那就是2个字节。

格式描述ALPHA_8只有一个alpha通道,每个像素用一个字节(8位)存储ARGB_4444这个从API 13开始不建议使用,因为质量太差ARGB_8888ARGB四个通道,每个像素用四个字节(32位)存储RGB_565每个像素占2字节,其中红色占5bit,绿色占6bit,蓝色占5bit

ALPHA8 没必要用,因为我们随便用个颜色就可以搞定的;

ARGB4444 虽然占用内存只有 ARGB8888 的一半,不过已经被官方嫌弃『又要占省内存,又要看着爽,臣妾做不到啊T T』;

ARGB8888、RGB565 :默认会使用ARGB_8888,在这个模式下一个像素点将会占用4个byte,而对一些没有透明度要求或者图片质量要求不高的图片,可以使用RGB_565,一个像素只会占用2个byte,一下可以省下50%内存。

手段三:将图片放到合适的文件夹下:

同一张图片,放在不同目录下,会生成不同大小的bitmap,因为它的宽高都被缩放了,所以图片资源应该尽可能放在高密度的资源文件夹下,这样可以节省图片的内存开支,一般建议放在xxhdpi下,目前主流手机都是这个dpi,并且UI给我们提供切图时也应该尽量面向高密度的屏幕设备来提供

jpg 是一种有损压缩的图片存储格式,而 png 则是 无损压缩的图片存储格式,显而易见,jpg 会比 png 小.

Bitmap 在内存当中占用的大小其实取决于:

  • 色彩格式,前面我们已经提到,如果是 ARGB8888 那么就是一个像素4个字节,如果是 RGB565 那就是2个字节

  • 原始文件存放的资源目录(是hdpi还是 xhdpi 还是 xxhdpi 可不能傻傻分不清楚)

  • 目标屏幕的密度(所以同等条件下,红米在资源方面消耗的内存肯定是要小于三星S6的)

手段四:设计合理的缓存机制(内存缓存,复用池,硬盘缓存):

内存缓存:可以使用官方已经帮我们实现好的LurCache;

复用池:用来装载内存缓存LRU中被抛弃掉的,但是可能又会马上使用到的那些图片,我们在复用池中缓存下来,防止立马被GC回收掉;

硬盘缓存:有JakeWharton大神实现的DiskLruCache,下载地址:https://github.com/JakeWharton/DiskLruCache

为什么要设置复用池呢?

因为在官方8.0Android系统中,将图片放在native层中进行了处理,例如回收机制,在java层我们无法进行干预,为了能够不立即将图片交给native层处理,我们利用复用池中的弱引用暂时保存在java层中,以方便我们需要使用刚刚被内存缓存LRU放弃掉的图片,而不是重复decode图片再放入到LRU队列中,降低性能消耗。

下面就是我们的三级缓存机制:内存缓存、复用池、磁盘缓存

完整项目地址:https://github.com/buder-cp/base_component_learn/tree/master/performanceOPT/buderdn07

/**
 * 管理内存中的图片
 */
public class ImageCache {

    private static ImageCache instance;
    private Context context;
    private LruCache memoryCache;
    private DiskLruCache diskLruCache;

    BitmapFactory.Options options = new BitmapFactory.Options();

    /**
     * 定义一个复用沲
     */
    public static Set reuseablePool;


    public static ImageCache getInstance() {
        if (null == instance) {
            synchronized (ImageCache.class) {
                if (null == instance) {
                    instance = new ImageCache();
                }
            }
        }
        return instance;
    }

    //引用队列
    ReferenceQueue referenceQueue;
    Thread clearReferenceQueue;
    boolean shutDown;

    private ReferenceQueue getReferenceQueue() {
        if (null == referenceQueue) {
            //当弱用引需要被回收的时候,会进到这个队列中
            referenceQueue = new ReferenceQueue();
            //单开一个线程,去扫描引用队列中GC扫到的内容,交到native层去释放
            clearReferenceQueue = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!shutDown) {
                        try {
                            //remove是阻塞式的
                            Reference reference = referenceQueue.remove();
                            Bitmap bitmap = reference.get();
                            if (null != bitmap && !bitmap.isRecycled()) {
                                bitmap.recycle();
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            clearReferenceQueue.start();
        }
        return referenceQueue;
    }

    //dir是用来存放图片文件的路径
    public void init(Context context, String dir) {
        this.context = context.getApplicationContext();

        //复用池
        reuseablePool = Collections.synchronizedSet(new HashSet());

        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        //获取程序最大可用内存 单位是M
        int memoryClass = am.getMemoryClass();
        //参数表示能够缓存的内存最大值  单位是byte
        memoryCache = new LruCache(memoryClass / 8 * 1024 * 1024) {
            /**
             * @return value占用的内存大小
             */
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //19之前   必需同等大小,才能复用  inSampleSize=1
                if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
                    return value.getAllocationByteCount();
                }
                return value.getByteCount();
            }

            /**
             * 拒接策略:当从lru缓存中移除后,可以获取到被移除的Bitmap
             * 当lru满了,bitmap从lru中移除对象时,会回调
             */
            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                if (oldValue.isMutable()) {//如果是设置成能复用的内存块,拉到java层来管理
                    //3.0以下   Bitmap   native
                    //3.0以后---8.0之前  java
                    //8。0开始      native
                    //lru缓存中多余图片放到一个复用池中
                    reuseablePool.add(new WeakReference(oldValue, referenceQueue));
                } else {
                    //oldValue就是移出来的对象
                    oldValue.recycle();
                }
            }
        };
        //valueCount:表示一个key对应valueCount个文件
        try {
            diskLruCache = DiskLruCache.open(new File(dir), BuildConfig.VERSION_CODE, 1, 10 * 1024 * 1024);
        } catch (Exception e) {
            e.printStackTrace();
        }

        getReferenceQueue();
    }

    /**
     * 加入内存缓存
     */
    public void putBitmapToMemeory(String key, Bitmap bitmap) {
        memoryCache.put(key, bitmap);
    }

    public Bitmap getBitmapFromMemory(String key) {
        return memoryCache.get(key);
    }

    public void clearMemoryCache() {
        memoryCache.evictAll();
    }

    //获取复用池中的内容
    public Bitmap getReuseable(int w, int h, int inSampleSize) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            return null;
        }
        Bitmap reuseable = null;
        Iterator iterator = reuseablePool.iterator();
        while (iterator.hasNext()) {
            Bitmap bitmap = iterator.next().get();
            if (null != bitmap) {
                //可以复用
                if (checkInBitmap(bitmap, w, h, inSampleSize)) {
                    reuseable = bitmap;
                    iterator.remove();
                    break;
                } else {
                    iterator.remove();
                }
            }
        }
        return reuseable;

    }

    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;
        }
        if (inSampleSize >= 1) {
            w /= inSampleSize;
            h /= inSampleSize;
        }
        int byteCount = w * h * getPixelsCount(bitmap.getConfig());
        return byteCount             
关注
打赏
1657159701
查看更多评论
0.0380s