高效加载大图、多图解决方案,有效避免程序OOM

1、高效加载大图

设备给每个应用程序分配的内存大小是有限的,如果加载一张大图到内存中可能会导致OOM。我们通过下面的方法就可以看到每个应用程序最高可用内存是多少。

int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
Log.d("TAG", "Max memory is " + maxMemory + "KB");

因此在展示高分辨率图片时需要对图片进行压缩,压缩后图片的大小应该和展示控件的大小接近。试想一下,如果一个特别小的控件展示一个非常大的图片,这无疑是一种内存的浪费,而且在性能上会带来很大的影像。下面看下如果对图片进行压缩。
BitmapFactory提供了很多解析方法:

  • BitmapFactory.decodeResource:解析资源文件中的图片
  • BitmapFactory.decodeFile():解析磁盘中的图片
  • BitmapFactory.decodeStream():解析网络上的图片
    每个方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的options.inJustDecodeBounds设置为true,就可以让解析方法禁止为Bitmap分配内存,返回值不再是一个Bitmap,而是null。但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这样我们就可以根据outWidth、outHeight和显示控件的大小计算出一个合适的值赋值给BitmapFactory.Options的inSampleSize,这样就完成了图片的压缩。下面看下常规的压缩代码。
    首先根据outWidth、outHeight和显示控件的大小计算出缩小的比例。
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int inSampleSize = 1;
        int bitmapWidth = options.outWidth;
        int bitmapHeight = options.outHeight;
        if (bitmapWidth > reqWidth || bitmapHeight > reqHeight) {
            int widthRatio = Math.round(bitmapWidth * 1.0f / reqWidth);
            int heightRatio = Math.round(bitmapHeight * 1.0f / reqHeight);
            //选出宽高比中较小的那个,保证压缩后图片的宽高大于等于显示控件的宽高
            //当bitmapWidth < reqWidth || bitmapHeight < reqHeight为true时此时inSampleSize的值可能等于0
            //而当options.inSampleSize在小于1时,会当做1来处理。所以当Bitmap的宽或高小于控件的宽高时图片不会被压缩
            //这样的话依然可能会导致oom
            inSampleSize = Math.min(widthRatio, heightRatio);
        }
        return inSampleSize;
    }

上面代码很简单,但需要注意的是:当bitmapWidth < reqWidth || bitmapHeight < reqHeight为true时此时inSampleSize的值可能等于0
,而当options.inSampleSize在小于1时,会当做1来处理。所以当Bitmap的宽或高小于控件的宽高时图片不会被压缩这样的话依然可能会导致oom。

下面看下图片压缩的代码

 BitmapFactory.Options options = new BitmapFactory.Options();
//只解析图片宽高属性不加载到内存中
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.mipmap.origin, options);
options.inSampleSize = calculateInSampleSize(options, iv_origin.getWidth(), iv_origin.getHeight());
//设置为false,再次解析
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.origin, options);
iv_origin.setImageBitmap(bitmap);

首先你要将BitmapFactory.Options的inJustDecodeBounds属性设置为true,解析一次图片。然后将BitmapFactory.Options连同期望的宽度和高度一起传递到到calculateInSampleSize方法中,就可以得到合适的inSampleSize值了。之后再解析一次图片,使用新获取到的inSampleSize值,并把inJustDecodeBounds设置为false,就可以得到压缩后的图片了。

2、使用图片缓存技术

当使用RecyclerView控件展示图片,随着不断的滑动,加载的图片也就会越多,对内存的消耗也就会越大,当快速滑动列表,还可能会导致OOM。
有的人可能会问,在滑动过程中将不可见的图片回收不就行了。是的这样可能不会带来OOM,但是当你需要再次展示之前的图片时就需要重新将图片加载到内存中,然后才能显示,如果网络比较差时,体验就很差。

那该怎么办呢?其实比较好的解决方案当然是使用缓存了,它可以让组件快速地重复加载图片。下面就看下如何使用内存缓存技术来对图片进行缓存,从而让你的程序在能加载很多图片的时候提高响应的速度和流畅性。

内存缓存技术最核心的类就是LruCache,它的算法原理就是将最近使用的对象存储在LinkedHashMap中,将最近最少使用的对象在缓存值达到预定值之前从内存中删除。

下面看下LruCache的使用

private LruCache<String, Bitmap> mMemoryCache;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
    // LruCache通过构造函数传入缓存值,以KB为单位。
    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    // 使用最大可用内存值的1/8作为缓存的大小。
    int cacheSize = maxMemory / 8;
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // 重写此方法来衡量每张图片的大小,默认返回图片数量。
            return bitmap.getByteCount() / 1024;
        }
    };
}
 
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}
 
public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。如果找到了相应的键值,则会立刻更新ImageView ,否则开启一个后台线程来加载这张图片。

public void loadBitmap(int resId, ImageView imageView) {
    final String imageKey = String.valueOf(resId);
    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    } else {
        imageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        task.execute(resId);
    }
}

BitmapWorkerTask 还要把新加载的图片的键值对放到缓存中。

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    // 在后台加载图片。
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final Bitmap bitmap = decodeSampledBitmapFromResource(
                getResources(), params[0], 100, 100);
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
        return bitmap;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容