ListView,Gallery,GridView等控件,在载入大量图片时,很容易会产生OutOfMemoryError异常,即内存溢出.因为每个应用可用内存是有限的,但是图片却很占内存,JPG,PNG本身就是压缩格式,如果分辨率高,很可能保存到磁盘中只有几百K,但是读到内存中会占用十几M内存.图片一多,自然就OutOfMemoryError了.
解决这个问题,需要考虑两个方面.
一是按需载入图片,即读图片的缩略图.比如,每个ImageView在屏幕上的面积是120dp*120dp,但是这个图片是高清的,分辨率2560*1600,如果直接载入整个图片,占用的内存是2560*1600*4/1024/1024=15.625M,如果一屏显示5个Item,占用的内存就是78.125M!而Galaxy Nexus给每个应用分配的内存只有64M,低配机型更少.所以,这里必须根据需要的分辨率载入图片.BitmapFactory解析图片时可以传入一个BitmapFactory.Options参数.

BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = false;options.inSampleSize = inSampleSize;Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

这里最重要的是inSampleSize,这是一个int值,等于1时,返回原图,大于1时,返回宽高为原图1/inSampleSize的图片,
所以,我们需要先根据需要的大小,计算inSampleSize.

		BitmapFactory.Options options = new BitmapFactory.Options();                //设为true,不会去解析图片,只解析边界,即宽高		options.inJustDecodeBounds = true;		BitmapFactory.decodeFile(jpgPath, options);         //读出图片真实的宽高		int[] size = new int[2];		size[0] = options.outWidth;		size[1] = options.outHeight; 		float realWidth = size[0];		float realHeight = size[1]; 		// 如果图片尺寸比最大值小,直接返回		if (maxWidth > realWidth && maxHeight > realHeight) {			return 1;		}		// 计算宽高比		float target_ratio = (float) maxWidth / maxHeight;		float real_ratio = realWidth / realHeight;		int inSampleSize = 1;		if (real_ratio > target_ratio) {			// 如果width太大,height太小,以width为基准,把realWidth设为maxWidth,realHeight缩放			inSampleSize = (int) realWidth / maxWidth;		} else {			inSampleSize = (int) realHeight / maxHeight;		}

虽然经过上面的处理,图片占用的内存已经大幅减少,放个百来个Item也不会OutOfMemoryError了,但很多应用都是下拉刷新,增加Item,Item的数量不可控,如果所有的图片都放内存里,溢出也是早晚的事.这里就需要做个图片缓存,内存里只保留需要用到的,没用到的,释放.我这里做了二级缓存,第一级放内存,第二级放SD卡.
说到图片内存缓存,网上大部分文章推荐软引用(SoftReference) 和弱引用(WeakReference),但是从Android 2.3开始,所有软引用和弱引用的对象在GC时都会被回收,根本起不到缓存的作用,Google推荐使用LruCache(http://developer.android.com/reference/android/util/LruCache.html),该类可以分配指定大小的缓存空间,当达到临界值时,最先保存的对象会被挤出,释放内存.
下面是一个使用LruCache的适配器:

package com.pocketdigi.googlep_w_picpath; import java.util.List; import android.app.ActivityManager;import android.content.Context;import android.graphics.Bitmap;import android.support.v4.util.LruCache;import android.view.LayoutInflater;import android.view.View;import android.view.View.OnClickListener;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.TextView; import com.pocketdigi.googlep_w_picpath.mode.ApiResult.ImageData;import com.pocketdigi.utils.ImageUtils;import com.pocketdigi.utils.ImageUtils.DownBitmapListener;import com.pocketdigi.views.ImageViewer; public class GoogleImageAdapter extends BaseAdapter{	Context mContext;	List<ImageData> mImageList;	LayoutInflater mInflater;	boolean mBusy = false;	// 图片缓存,Key是url,Value就是Bitmap	LruCache<String, Bitmap> mImageCache;	public GoogleImageAdapter(Context context, List<ImageData> p_w_picpathList) {		mContext = context;		mImageList = p_w_picpathList;		mInflater = LayoutInflater.from(mContext);		//读取应用可用内存大小,这里读出来,单位是兆,Galaxy Nexus是64M		final int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();		//取1/8作为图片缓存		final int maxSize = 1024 * 1024 * memClass / 8;		mImageCache = new LruCache<String, Bitmap>(maxSize) {			// 必须覆写sizeOf方法,因为默认返回的是1,即统计的是对象的数量,而不是占用的内存			@Override			protected int sizeOf(String key, Bitmap value) {				// TODO 自动生成的方法存根				return value.getByteCount();			}		}; 	} 	@Override	public int getCount() {		// TODO 自动生成的方法存根		return mImageList.size();	} 	@Override	public Object getItem(int position) {		// TODO 自动生成的方法存根		return mImageList.get(position);	} 	@Override	public long getItemId(int position) {		// TODO 自动生成的方法存根		return position;	} 	@Override	public View getView(int position, View convertView, ViewGroup parent) {		// TODO 自动生成的方法存根		final ImageData p_w_picpathData = mImageList.get(position);		ViewHolder holder = null;		if (convertView == null) {			convertView = mInflater.inflate(R.layout.item_listview, null);			holder = new ViewHolder();			holder.tv_title = (TextView) convertView.findViewById(R.id.tv_title);			holder.iv_thumbnail = (ImageView) convertView.findViewById(R.id.iv_thumbnail);			holder.thumbnail_url = p_w_picpathData.getThumbnail_url();			convertView.setTag(holder);		} else {			holder = (ViewHolder) convertView.getTag();			if (!holder.thumbnail_url.equals(p_w_picpathData.getThumbnail_url())) {				holder.iv_thumbnail.setImageResource(R.drawable.loading);			}		} 		holder.tv_title.setText(p_w_picpathData.getAbs());		if (!isBusy()) {			final String imgUrl = p_w_picpathData.getThumbnail_url();			Bitmap bmp = mImageCache.get(imgUrl);			if (bmp != null) {				holder.iv_thumbnail.setImageBitmap(bmp);			} else {				loadBmpFromNetWork(imgUrl);			} 		} 		holder.iv_thumbnail.setOnClickListener(new OnClickListener() { 			@Override			public void onClick(View v) {				//点击ImageView显示大图				ImageViewer p_w_picpathViewer = new ImageViewer(mContext);				p_w_picpathViewer.setSourceView((ImageView) v);				p_w_picpathViewer.setJpgUrl(p_w_picpathData.getImage_url());				p_w_picpathViewer.show();			}		}); 		return convertView;	}	/**	 * 从网络下载图片	 * @param imgUrl	 */	private void loadBmpFromNetWork(final String imgUrl) {		//异步从网络下载图片,这个异步下载方法,是有作磁盘缓存的		//缓存原理,根据传入的url,计算md5,作缓存文件名,下载前先判断该文件存不存在,如果存在,从SD卡读,不存在,再去下载		//参数120,120,是返回120*120的bitmap,避免在载入大图时浪费内存		ImageUtils.asynchronousDownloadBitmap(imgUrl, 120, 120, new DownBitmapListener() {			@Override			public void onComplete(Bitmap bmp) {				// TODO 自动生成的方法存根				if(imgUrl!=null&&bmp!=null)				{					mImageCache.put(imgUrl, bmp);					notifyDataSetChanged();				}			} 			@Override			public void onProgressChanged(long total, long downloaded) {				// TODO 自动生成的方法存根 			}		});	} 	@Override	public int getItemViewType(int position) {		// TODO 自动生成的方法存根		return super.getItemViewType(position);	} 	@Override	public int getViewTypeCount() {		// TODO 自动生成的方法存根		return super.getViewTypeCount();	}	//用来保存各个控件的引用	static class ViewHolder {		TextView tv_title;		ImageView iv_thumbnail;		String thumbnail_url;	}   	public boolean isBusy() {		return mBusy;	} 	public void setBusy(boolean busy) {		this.mBusy = busy;	} }