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

原文链接http://blog.csdn.net/guolin_blog/article/details/9316683

一.高效加载大图

1.查看程序可用内存大小

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

因此在展示高分辨率图片的时候,最好先将图片进行压缩。压缩后的图片大小应该和用来展示它的控件大小相近,在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用我们相当多宝贵的内存,而且在性能上还可能会带来负面影响。

每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这个技巧让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩。如下代码所示:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;//禁止为Bitmap分配内存
BitmapFactory.decodeResource(getResources(),R.id.myimage,options);
int imageHeihht = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

加载图片前考虑是完整显示图片还是要压缩后再显示,就需要考虑以下因素:

  • 预估整张图片占用的内存
  • 为了加在一张图片,你愿意提供多少内存
  • 用于展示的图片控件的实际大小
  • 当前设备的屏幕尺寸和分辨率
    对图片压缩需要使用BitmapFactory.Options中的inSampleSize(例如:2048 * 1536像素的图片,inSampleSize= 4,图片被压缩成 512 * 384 所占的内存大小为512 * 384 *4 = 0.75M (假设图片是ARGB_8888类型,即每个像素点占用4个字节)
    下面的方法可以根据宽高计算出合适的inSampleSize值:
public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){
    //源图片的高度和宽度
    final int height  = options.outHeight;
    final int width = options.outWidth;
    int inSampleSaze= 1;
    if(height>reqHeight||width>reqWidth){
        //计算实际宽高和目标宽高的比率
        final int heightRatio = Math.round((float)height/(float)reqHeight);
        final int widthRatio = Math.round((float)height/(float)reqHeight);
        //选择宽高比例较小的作为InSampleSize的值,这样保证最终图片的宽高是大于目标的宽高
        inSampleSize = heightRatio>widthRatio?widthRatio:heightRatio;
        
    }
    return inSampleSize;
}

获取到inSampleSize值以后再把inJustDecodeBounds设置为false,就可以使用压缩后的图片了

public static Bitmap decodeSampleBitmapFromResource(Resources res,int resId,int reqWidth,int reqHeight){
    //第一次设置inJustDecodeBounds为true,来获取图片大小
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res,resId,options);
    //调用方法计算inSampleSize
    options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);
    //使用insamplesize在此解析图片
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res,resId,options);
}

下面的代码非常简单的将任意一张图片设置压缩成100*100的缩略图,并显示在ImageView上

mImageView.setImageBitmap(decodeSampleBitmapFromResource(getResource(),R.id.myimage,100,100));

二.使用图片缓存技术

防止频繁的显示多张图片 以及回收过的图片再次显示导致大量的加载而引起OOM

内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是==把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。==

为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如:

  • 你的设备可以为每个应用程序分配多大的内存?
  • 设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?
  • 你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。
  • 图片的尺寸和大小,还有每张图片会占据多少内存空间。
  • 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。
  • 你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效

下面是一个使用 LruCache 来缓存图片的例子:

private LruCache<String,Bitmap>mMemoryChahe;
@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
        protect 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 getBitmapFromMemoryCache(String key){
    return mMemoryCache.get(key);
}

在这个例子当中,使用了系统分配给应用程序的八分之一内存来作为缓存大小。在中高配置的手机当中,这大概会有4兆(32/8)的缓存空间。一个全屏幕的 GridView 使用4张 800x480分辨率的图片来填充,则大概会占用1.5兆的空间(800 * 480 * 4)。因此,这个缓存大小可以存储2.5页的图片。

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

public void loadBitmap(int resId,ImageView imageview){
    final String imageKay = String valueOf(resId);
    Bitmap bitmap = mMemoryCache.getBitmapFromMemoryCache(imageKey);
    if(bitmap!=null){
        imageview.setImageBitmap(bitmap);
    }else{
        imageview.setImageResource(R.drawable.image_placeholder);
        //缓存
        BitmapWorkerTask task = new BitmapWorkTask(imageview);
        task.execute(resId);
    }
    
}

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

推荐阅读更多精彩内容