搜档网
当前位置:搜档网 › Android之批量加载图片OOM问题解决方案(烟台杰瑞教育原创)

Android之批量加载图片OOM问题解决方案(烟台杰瑞教育原创)

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

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

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

1、加载的图片过大

2、一次加载的图片过多

3、以上两种情况兼有

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

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

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

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

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

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

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

options.inJustDecodeBounds=true;

BitmapFactory.decodeFile(path, options);

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;

}

//计算图片的缩放比例

options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

options.inJustDecodeBounds = false;

Bitmap bitmap= BitmapFactory.decodeFile(path, options);

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

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

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();

}

@Override

public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder holder=null;

if(convertView==null){

convertView =

LayoutInflater.from(this.mContext).inflate(https://www.sodocs.net/doc/1610589283.html,yout.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(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、使用缓存,根据实际情况确定是否使用双缓存和缓存大小。

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

相关主题