Android之批量加载图片OOM问题解决方案

一、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) {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方法中加载图片

@Overridepublic 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);}loadImage(final ImageView view,final String path,final int reqWidth,final int reqHeight,final onBitmapLoadedListener callback){final Handler mHandler = new Handler(){@Overridepublic 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() {@Overridepublic 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中:

愚公因此敢移山,矢志不渝是前行,握紧拳头勇登攀,

Android之批量加载图片OOM问题解决方案

相关文章:

你感兴趣的文章:

标签云: