在加载图片过程当中出现的OOM的几种状况:html
一、 加载的图片过大 二、 一次加载的图片过多 三、 以上两种状况兼有
出现OMM的主要缘由有两点:android
一、移动设备会限制每一个app所可以使用的内存,最小为16M,有的设备分配的会更多,如2四、32M、64M等等不一,总之会有限制,不会让你无限制的使用。缓存
二、在andorid中图片加载到内存中是以位图的方式存储的,在android2.3以后默认状况下使用ARGB_8888,这种方式下每一个像素要使用4各字节来存储。因此加载图片是会占用大量的内存。app
首先先来解决大图加载的问题,通常在实际应用中展现图片时,因屏幕尺寸及布局显示的缘由,咱们没有必要加载原始大图,只须要按照比例采样缩放便可。这样即节省内存又能保证图片不失真,具体实施步骤以下:异步
这里须要用的BitmapFactory的decode系列方法和BitmapFactory.Options。当使用decode系列方法加载图片时,必定要将Options的inJustDecodeBounds属性设置为true。ide
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(); }
二、自定义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工具类,为其添加一个线程池对象,用来管理用于下载图片的子线程。ui
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); } }); }
经过改造后用户体验明显好多了,效果图以下:
虽然效果有所提高,可是在加载过程当中还存在两个比较严重的问题:
一、图片错位显示
二、当咱们滑动速度过快的时候,图片加载速度过慢
通过分析缘由不难找出,主要是由于咱们时候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的问题主要有如下方法:
一、 不要加载原始大图,根据显示控件进行比例缩放后加载其缩略图。
二、 不要在主线程中加载图片,主要在listview和gridview中使用异步加载图片是要注意处理图片错位和无用线程的问题。
三、 使用缓存,根据实际状况肯定是否使用双缓存和缓存大小。