一、OOM问题出现的场景和原因

  一个好的app总少不了精美的图片,所以Android开发中图片的加载总是避免不了的,而在加载图片过程中,如果处理不当则会出现OOM的问题。那么如何彻底解决这个问题呢?本文将具体介绍这方面的知识。

  首先我们来总结一下,在加载图片过程中出现的OOM的场景无非就这么几种:

1、  加载的图片过大

2、  一次加载的图片过多

3、  以上两种情况兼有

  那么为什么在以上场景下会出现OOM问题呢?实际上在API文档中有着明确的说明,出现OMM的主要原因有两点:

1、移动设备会限制每个app所能够使用的内存,最小为16M,有的设备分配的会更多,如24、32M、64M等等不一,总之会有限制,不会让你无限制的使用。

2、在andorid中图片加载到内存中是以位图的方式存储的,在android2.3之后默认情况下使用ARGB_8888,这种方式下每个像素要使用4各字节来存储。所以加载图片是会占用大量的内存。

场景和原因我们都分析完了,下面我们来看看如何解决这些问题。

二、解决大图加载问题

  首先先来解决大图加载的问题,一般在实际应用中展示图片时,因屏幕尺寸及布局显示的原因,我们没有必要加载原始大图,只需要按照比例采样缩放即可。这样即节省内存又能保证图片不失真,具体实施步骤如下:

1、在不加载图片内容的基础上,去解码图片得到图片的尺寸信息

  这里需要用的BitmapFactory的decode系列方法和BitmapFactory.Options。当使用decode系列方法加载图片时,一定要将Options的inJustDecodeBounds属性设置为true。

    BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeFile(path, options);

2、根据获取的图片的尺寸和要展示在界面的尺寸计算缩放比例。

public 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) {
if (width > height) {
inSampleSize = Math.round((float) height / (float) reqHeight);
} else {
inSampleSize = Math.round((float) width / (float) reqWidth);
}
}
return inSampleSize;
}

3、根据计算的比例缩放图片。

//计算图片的缩放比例
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
Bitmap bitmap= BitmapFactory.decodeFile(path, options);

  根据缩放比例,会比原始大图节省很多内存,效果图如下:

三、批量加载大图

  下面我们看看如何批量加载大图,首先第一步还是我们上面所讲到的,要根据界面展示图片控件的大小来确定图片的缩放比例。在此我们使用gridview加载本地图片为例,具体步骤如下:

1、通过系统提供的contentprovider加载外部存储器中的所有图片地址

     private void loadPhotoPaths(){
Cursor cursor= getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
while(cursor.moveToNext()){
String path = cursor.getString(cursor.getColumnIndex(MediaColumns.DATA));
paths.add(path);
}
cursor.close();
}

2、自定义adapter,在adapter的getview方法中加载图片

    @Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder=null;
if(convertView==null){
convertView = LayoutInflater.from(this.mContext).inflate(R.layout.grid_item_layout, null);
holder = new ViewHolder();
holder.photo=(ImageView)convertView.findViewById(R.id.photo);
convertView.setTag(holder);
}else{
holder=(ViewHolder)convertView.getTag();
} final String path = this.paths.get(position);
holder.photo.setImageBitmap(imageLoader.getBitmapFromCache(path));
return convertView;
}

  通过以上关键两个步骤后,我们发现程序运行后,用户体验特别差,半天没有反应,很明显这是因为我们在主线程中加载大量的图片,这是不合适的。在这里我们要将图片的加载工作放到子线程中进行,改造自定义的ImageLoader工具类,为其添加一个线程池对象,用来管理用于下载图片的子线程。

    private ExecutorService executor;
private ImageLoader(Context mContxt) {
super();
executor = Executors.newFixedThreadPool(3);
}
//加载图片的异步方法,含有回调监听
public void loadImage(final ImageView view,
final String path,
final int reqWidth,
final int reqHeight,
final onBitmapLoadedListener callback){ final Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
Bitmap bitmap = (Bitmap)msg.obj;
callback.displayImage(view, bitmap);
break; default:
break;
}
} }; executor.execute(new Runnable() {
@Override
public void run() {
Bitmap bitmap = loadBitmapInBackground(path, reqWidth,
reqHeight);
putBitmapInMemey(path, bitmap); Message msg = mHandler.obtainMessage(1);
msg.obj = bitmap;
mHandler.sendMessage(msg);
}
});
}

  通过改造后用户体验明显好多了,效果图如下:

  虽然效果有所提升,但是在加载过程中还存在两个比较严重的问题:

1、  图片错位显示

2、  当我们滑动速度过快的时候,图片加载速度过慢

  经过分析原因不难找出,主要是因为我们时候holder缓存了grid的item进行重用和线程池中的加载任务过多所造成的,只需要对程序稍作修改,具体如下:

  Adapter中:

        holder.photo.setImageResource(R.drawable.ic_launcher);
holder.photo.setTag(path);
imageLoader.loadImage(holder.photo,
path,
DensityUtil.dip2px(80),
DensityUtil.dip2px(80),
new onBitmapLoadedListener() {
@Override
public void displayImage(ImageView view, Bitmap bitmap) {
String imagePath= view.getTag().toString();
if(imagePath.equals(path)){
view.setImageBitmap(bitmap);
}
}
});

  ImageLoader中:

executor.execute(new Runnable() {
@Override
public void run() {
String key = view.getTag().toString();
if (key.equals(path)) {
Bitmap bitmap = loadBitmapInBackground(path, reqWidth,
reqHeight);
putBitmapInMemey(path, bitmap); Message msg = mHandler.obtainMessage(1);
msg.obj = bitmap;
mHandler.sendMessage(msg);
}
}
});

  为了获得更好的用户体验,我们还可以继续优化,即对图片进行缓存,缓存我们可以分为两个部分内存缓存磁盘缓存,本文例子加载的是本地图片所有只进行了内存缓存。对ImageLoader对象继续修改,添加LruCache对象用于缓存图片。

private ImageLoader(Context mContxt) {
super();
executor = Executors.newFixedThreadPool(3);
//将应用的八分之一作为图片缓存
ActivityManager am=(ActivityManager)mContxt.getSystemService(Context.ACTIVITY_SERVICE);
int maxSize = am.getMemoryClass()*1024*1024/8;
mCache = new LruCache<String, Bitmap>(maxSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes()*value.getHeight();
}
};
} //存图片到缓存
public void putBitmapInMemey(String path,Bitmap bitmap){
if(path==null)
return;
if(bitmap==null)
return;
if(getBitmapFromCache(path)==null){
this.mCache.put(path, bitmap);
}
} public Bitmap getBitmapFromCache(String path){
return mCache.get(path);
}

  在loadImage方法中异步加载图片前先从内存中取,具体代码请下载案例

四、总结

  总结一下解决加载图片出现OOM的问题主要有以下方法:

1、  不要加载原始大图,根据显示控件进行比例缩放后加载其缩略图。

2、  不要在主线程中加载图片,主要在listview和gridview中使用异步加载图片是要注意处理图片错位和无用线程的问题。

3、  使用缓存,根据实际情况确定是否使用双缓存和缓存大小。

  

  小伙伴们看懂了嘛?想要自己测试的,可以点击“下载工程”运行测试哦!

作者:杰瑞教育
出处:http://www.cnblogs.com/jerehedu/ 
本文版权归烟台杰瑞教育科技有限公司和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
 

Android之批量加载图片OOM问题解决方案的更多相关文章

  1. Android加载图片OOM错误解决方式

    前几天做项目的时候,甲方要求是PAD (SAMSUNG P600 10.1寸 2560*1600)的PAD上显示高分辨率的大图片. SQLITE採用BOLD方式存储图片,这个存取过程就不说了哈,网上一 ...

  2. android 加载图片oom若干方案小结

    本文根据网上提供的一些技术方案加上自己实际开发中遇到的情况小结. 众所周知,每个Android应用程序在运行时都有一定的内存限制,限制大小一般为16MB或24MB(视手机而定).一般我们可以通过获取当 ...

  3. Android实例-TRectangle加载图片(XE8+小米2)

    结果: 1.加载图片很流畅,可以做背景用. 2.现在是加载了正形与圆形,其他形状能不能加载呢?自己测试哦,要多动手才行. 3.需要把图片打到包里哦(路径为“assets\internal\”). 实例 ...

  4. android ListView异步加载图片(双缓存)

    首先声明,参考博客地址:http://www.iteye.com/topic/685986 对于ListView,相信很多人都很熟悉,因为确实太常见了,所以,做的用户体验更好,就成了我们的追求... ...

  5. 又优化了一下 Android ListView 异步加载图片

    写这篇文章并不是教大家怎么样用listview异步加载图片,因为这样的文章在网上已经有很多了,比如这位仁兄写的就很好: http://www.iteye.com/topic/685986 我也是因为看 ...

  6. 【Android】Bitmap加载图片错误 java.lang.OutOfMemoryError: bitmap size exceeds VM budget

    今天测试程序的时候出现下面的错误日志信息,程序当场挂掉 07-09 14:11:25.434: W/System.err(4890): java.lang.OutOfMemoryError: bitm ...

  7. Android GridView异步加载图片和加载大量图片时出现Out Of Memory问题

    我们在使用GridView或者ListView时,通常会遇到两个棘手的问题: 1.每个Item获取的数据所用的时间太长会导致程序长时间黑屏,更甚会导致程序ANR,也就是Application No R ...

  8. 转:Android ListView 异步加载图片

    http://www.iteye.com/topic/1118828 http://www.iteye.com/topic/1127914 这样做无疑是非常可取的方法,但是加载图片时仍然会感觉到轻微的 ...

  9. android实现异步加载图片类

    其中牵涉到的关键知识点 1,回调机制,不过回调接口的实现方式有多种多样,可以是一个类继承该接口,也可以是作为一个方法参数: 可以参照自己的这篇博客: http://www.cnblogs.com/bo ...

随机推荐

  1. iOS 11开发教程(七)编写第一个iOS11代码Hello,World

    iOS 11开发教程(七)编写第一个iOS11代码Hello,World 代码就是用来实现某一特定的功能,而用计算机语言编写的命令序列的集合.现在就来通过代码在文本框中实现显示“Hello,World ...

  2. windows svn 客户端连不上linux svn server

    采坑记录:linux服务器上svn://127.0.0.1可以正常使用,windows客户端远程连接不上,说明是端口号的问题. linux正常配置了iptables开启了3690端口,连接不上. 干脆 ...

  3. BlocksKit(1)-基本类型的分类

    BlocksKit(1)-基本类型的分类 BlocksKit是一个用block的方式来解决我们日常用对集合对象遍历.对象快速生成使用.block作为委托对象的一种综合便利封装库.这个库主要分三个大块C ...

  4. Go Web编程 第四章--处理请求

    请求和响应 Request结构 URL字段 Header字段 Body字段 Form, PostForm, MultipartForm字段 在处理器函数中,我们可以通过Request获取各个字段的详细 ...

  5. hdu 2266 dfs

    题意:在数字之间添加运算符号,使得结果等于题目中要求的Sample Input123456789 321 1Sample Output181 这题虽然看起来比较简单,但是之前和差的状态不太好表示,因此 ...

  6. Lunix/Mac下根据最后修改时间复制文件和文件夹,保持原有的目录结构

    度娘知道:http://zhidao.baidu.com/link?url=DD47jm6qDgT7yxsnz9e-NC4Fqd33oRoiIwcGLkw5TL4cbf50VKY2IONbHKH0IE ...

  7. 射洪中学讲课PPT

    咸鱼跑去射洪中学讲课去啦. 高中生好萌呀!!!! 下面是讲课PPT day3 简单吹了一波noip2015如何骗分,所以课件就是去网上到处kuai题解而已,所以课件我就不上传了. day4 题目+题解 ...

  8. Codeforces Round #293 (Div. 2) A. Vitaly and Strings

    A. Vitaly and Strings time limit per test 1 second memory limit per test 256 megabytes input standar ...

  9. 配置主从Mysql

    怎么安装mysql数据库,这里不说了,只说它的主从复制,步骤如下: 1.主从服务器分别作以下操作:  1.1.版本一致  1.2.初始化表,并在后台启动mysql  1.3.修改root的密码 2.修 ...

  10. 数据库中间件OneProxy and onemysql

    http://blog.itpub.net/28944233/cid-176181-list-1/ http://www.onexsoft.com/ 官网 http://pan.baidu.com/s ...