首页 > 代码库 > 续说ListView重用之加载图片

续说ListView重用之加载图片

最近领养了一直小狗狗,据狗主人说是只阿拉斯加,求大伙见证。



不管他是不是阿拉斯加,我还是得养着,取名“蛋蛋”。


继续谈技术。

说到listview里加载图片永远是个说不完的话题。

在listview中如果每个item都有图片需要下载的话,我们就得考虑由于大量图片加载而导致的oom(out of memory)问题。

一个典型的做法是,下载图片的时候看看缓存中有没有该图片,如果缓存中没有,就从sd卡中读取,如果sd卡中还没有,再去服务器下载,下载下来的图片先放在sd卡中,并放到缓存中。如此周而复始。

这其中涉及到的就是缓存怎么设计,比较通用的做法就是使用LRU算法来缓存图片,先在手机端设置一个内存区域用于缓存图片,然后将我们下载的图片以键值对的形式丢进去,这样我们就能取到相应的图片啦,而且速度很快的说。因为大家要知道,从内存中读取图片肯定比从文件读取快。

LRU缓存的代码如下:

/**
 * 图片缓存 注意:图片缓存使用的是经典的LRU算法,每个文件对应一个key,通过该key定位到缓存的图片
 * 需要注意的是,取到的所有key是图片的url,value是drawable类型
 * 为了确保key的统一性,图片的url统一做md5加密,也就是说key全都是32位字符串
 * 
 * @author kyson
 * 
 */
public class ImageCache {
	private static final String TAG = "ImageCache";
	// 获取系统分配给每个应用程序的最大内存,每个应用系统分配32M
	private static int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
	// 给LruCache分配1/8 4M
	private static int cacheSize = maxMemory / 8;

	private static ImageCache instance;

	private static LruCache<String, Bitmap> mLruCache = new LruCache<String, Bitmap>(
			cacheSize) {
		// 删除时是否释放
		// public boolean isRecycleWhenRemove = true;

		@Override
		protected int sizeOf(String key, Bitmap value) {
			// TODO Auto-generated method stub
			// return super.sizeOf(key, value);
			return value.getByteCount() / 1024;
		}

		@Override
		protected void entryRemoved(boolean evicted, String key,
				Bitmap oldValue, Bitmap newValue) {
			super.entryRemoved(evicted, key, oldValue, newValue);
			Log.i("------", "系统最大内存:" + maxMemory);
			if (evicted && null != oldValue && !oldValue.isRecycled()) {
				oldValue.recycle();
				oldValue = http://www.mamicode.com/null;>

何时缓存又是个问题,看如下代码可以解决

/**
 * 图片下载器类
 * 
 */
public class ImageDownLoad {
	private static final String TAG = "ImageDownLoad";

	ImageDownloadListener mListener = null;

	private static int ALIVE_TIME = 30;
	// 核心线程数
	private static int CORE_SIZE = 5;
	// 最大任务数
	private static int MAX_SIZE = 15;

	private static ArrayBlockingQueue<Runnable> runnables = new ArrayBlockingQueue<Runnable>(25);

	private static ThreadFactory factory = Executors.defaultThreadFactory();
	// 线程池
	private static ThreadPoolExecutor mImageThreadPool;

	private ImageCache mImageCache;
	private FileHandler mFileHandler;

	private Context mContext;
	private static ImageDownLoad instance;

	public static ImageDownLoad shareInstance(Context context) {
		if (instance == null) {
			synchronized (ImageCache.class) {
				instance = new ImageDownLoad(context);
			}
		}
		return instance;
	}

	public ImageDownLoad(Context context) {
		mContext = context;
		mFileHandler = new FileHandler(context);
		mImageThreadPool = getThreadPool();
		mImageCache = ImageCache.shareInstance();
	}

	public void cancelAllTasks() {

	}

	/**
	 * 获取线程池的方法,因为涉及到并发的问题,我们加上同步锁
	 * 
	 * @return
	 */
	public ThreadPoolExecutor getThreadPool() {
		if (mImageThreadPool == null) {
			synchronized (ThreadPoolExecutor.class) {
				if (mImageThreadPool == null) {
					// 为了下载图片更加的流畅,我们用了2个线程来下载图片
					mImageThreadPool = new ThreadPoolExecutor(CORE_SIZE, MAX_SIZE, ALIVE_TIME, TimeUnit.SECONDS, runnables, factory, new ThreadPoolExecutor.DiscardOldestPolicy());
				}
			}
		}
		return mImageThreadPool;
	}

	/**
	 * 从内存缓存中获取一个Bitmap
	 * 
	 * @param key
	 * @return
	 */
	public Bitmap getBitmapFromMemCache(String key) {
		return mImageCache.loadImage(key);
	}

	/**
	 * 添加Bitmap到内存缓存
	 * 
	 * @param key
	 * @param bitmap
	 */
	public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
		if (getBitmapFromMemCache(key) == null && bitmap != null) {
			mImageCache.cacheImage(bitmap, key);
		}
	}

	/**
	 * 下载
	 */
	public void downloadImageByUrl(final String imageUrlString, final ImageDownloadListener listener) {
		if (imageUrlString == null) {
			Log.i(TAG, "downloadImageByUrl imageUrlString is null");
			return;
		}
		final String subUrl = MD5Encoder.encoding(imageUrlString);
		final Handler handler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				super.handleMessage(msg);
				// 回调
				if (msg.obj != null) {
					listener.imageDownloadFinished((Bitmap) msg.obj, imageUrlString);
					File file = mFileHandler.createEmptyFileToDownloadDirectory(subUrl);
					ImageUtils.writeBitmap2File((Bitmap) msg.obj, file);
					addBitmapToMemoryCache(imageUrlString, (Bitmap) msg.obj);
				} else {
					Log.i(TAG, "下载成功,但图片为空!!!!!");
				}
			}
		};

		// 如果文件系统中不存在,则下载
		mImageThreadPool.execute(new Runnable() {
			// 下载文件
			@Override
			public void run() {
				Bitmap bitmap = getBitmapFormUrl(imageUrlString);
				if (bitmap != null) {
					Message msg = handler.obtainMessage();
					msg.obj = bitmap;
					handler.sendMessage(msg);
					// 将Bitmap 加入内存缓存
				}
			}
		});
	}

	/**
	 * 根据url下载并返回图片
	 * 
	 * @param url
	 * @return
	 */
	private Bitmap getBitmapFormUrl(String url) {
		Bitmap bitmap = null;
		// 初始化图片下载器
		FileDownLoad fileDownLoad = new FileDownLoad(mContext);
		// 下载图片,并返回是否成功
		File file = fileDownLoad.downloadByUrl(url);
		// 如果下载成功,则到指定路径找到图片,并缓存图片,最后加载图片
		if (file != null) {
			Log.i(TAG, "图片下载成功!通过FileDownLoad");
			try {
				Log.i("wenjuan", "image download to file" + file.getPath());
				bitmap = BitmapFactory.decodeFile(file.getPath());
			} catch (OutOfMemoryError err) {
				Log.i("<>", "decodefile");
				Runtime.getRuntime().gc();
				bitmap = null;
			}
		} else {
			Log.i(TAG, "图片下载失败!重新下载,调用图片下载器的自带下载器");
			bitmap = getBitmapFormUrlWithHttpURLConnection(url);
		}
		return bitmap;
	}

	/**
	 * 图片下载器自带的文件下载器
	 * 
	 * @param url
	 * @return
	 */
	private Bitmap getBitmapFormUrlWithHttpURLConnection(String url) {
		Bitmap bitmap = null;
		HttpURLConnection con = null;
		try {
			URL mImageUrl = new URL(url);
			con = (HttpURLConnection) mImageUrl.openConnection();
			con.setConnectTimeout(10 * 1000);
			con.setReadTimeout(10 * 1000);
			con.setRequestMethod("GET");
			con.setDoInput(true);
			con.setDoOutput(true);
			bitmap = BitmapFactory.decodeStream(con.getInputStream());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (ProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (OutOfMemoryError ee) {
			Runtime.getRuntime().gc();
			bitmap = null;
		} finally {
			if (con != null) {
				con.disconnect();
			}
		}
		return bitmap;
	}

	/**
	 * 设置监听器
	 * 
	 * @param callback
	 */
	public void setImageDownloadFinishListener(ImageDownloadListener imageDownloadListener) {
		this.mListener = imageDownloadListener;
	}

	/**
	 * 封装消息 传参用
	 * 
	 * @author kyson
	 * 
	 */
	class MessageData {
		public Bitmap bitmap;
		public String imageUrlString;
	}

}


然后,是涉及到下载的东东

/**
 * 文件下载器
 * 
 * @author kyson
 * 
 */
public class FileDownLoad {
	private static final int STATUS_OK = 200;
	private FileHandler mFileHandler;

	public FileDownLoad(Context context) {
		mFileHandler = new FileHandler(context);
	}

	/**
	 * 下载经过压缩的文件
	 * 
	 * @param urlString
	 *            文件url
	 * @return
	 */
	public boolean downloadByUrlByGzip(String urlString, String fileName) {
		boolean downloadSucceed = false;

		AndroidHttpClient httpClient = AndroidHttpClient.newInstance(null);
		httpClient.getParams().setParameter("Accept-Encoding", "gzip");
		HttpPost post = new HttpPost(urlString);
		HttpResponse response = null;

		try {
			response = httpClient.execute(post);
			if (response.getStatusLine().getStatusCode() == STATUS_OK) {
				HttpEntity entity = response.getEntity();
				File file = mFileHandler.createEmptyFileToDownloadDirectory(fileName);

				InputStream inputStream = AndroidHttpClient.getUngzippedContent(entity);
				downloadSucceed = StreamUtil.writeStreamToFile(inputStream, file);
			} else {
				System.out.println("服务器连接有问题");
			}

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			httpClient.close();
		}
		return downloadSucceed;
	}

	/**
	 * 下载文件到download目录
	 * 
	 * @param urlString
	 *            url地址
	 * @param fileName
	 *            指定文件名
	 * @return
	 */
	public boolean downloadByUrl(String urlString, String fileName) {
		boolean downloadSucceed = false;
		AndroidHttpClient httpClient = AndroidHttpClient.newInstance(null);
		// HttpPost post = new HttpPost(urlString);
		HttpGet get = new HttpGet(urlString);

		HttpResponse response = null;

		try {
			response = httpClient.execute(get);
			if (response.getStatusLine().getStatusCode() == STATUS_OK) {
				HttpEntity entity = response.getEntity();
				File file = mFileHandler.createEmptyFileToDownloadDirectory(fileName);
				entity.writeTo(new FileOutputStream(file));
				downloadSucceed = true;
			} else {
				System.out.println("文件下载:服务器连接有问题");
			}

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			httpClient.close();
		}
		return downloadSucceed;
	}

	/**
	 * 下载并返回文件
	 * 
	 * @param urlString
	 * @return
	 */
	public File downloadByUrl(String urlString) {
		AndroidHttpClient httpClient = AndroidHttpClient.newInstance(null);
		HttpGet get = new HttpGet(urlString);
		File file = null;
		try {
			HttpResponse response = httpClient.execute(get);
			if (response.getStatusLine().getStatusCode() == STATUS_OK) {
				HttpEntity entity = response.getEntity();
				file = mFileHandler.createEmptyFileToDownloadDirectory(MD5Encoder.encoding(urlString));
				entity.writeTo(new FileOutputStream(file));
			} else if (response.getStatusLine().getStatusCode() == 404) {
				System.out.println("文件下载:下载文件不存在");
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			httpClient.close();
			httpClient = null;
		}
		return file;
	}
}



最后是那个imageview,用于加载

/**
 * 用于加载图片的视图
 */
public class AutoloadImageView extends ImageView {

	public final int DEFAULTIMAGEID = -1;
	private Context mContext;
	private ImageCache mImageCache;

	// private FileHandler fileHandler;

	public AutoloadImageView(Context context) {
		super(context);
		this.mContext = context;
		mImageCache = ImageCache.shareInstance();
		// fileHandler = new FileHandler(mContext);
	}

	public AutoloadImageView(Context context, AttributeSet paramAttributeSet) {
		super(context, paramAttributeSet);
		this.mContext = context;
		mImageCache = ImageCache.shareInstance();
		// fileHandler = new FileHandler(mContext);
	}

	public AutoloadImageView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		this.mContext = context;
		mImageCache = ImageCache.shareInstance();
		// fileHandler = new FileHandler(mContext);
	}

	/**
	 * 加载图片
	 * @param drawable
	 *            图片
	 */
	public void loadImage(Drawable drawable) {
		// drawable = MainApplication.getContext().getResources()
		// .getDrawable(R.drawable.button_focused);
		this.setImageDrawable(drawable);
	}

	/**
	 * 加载图片
	 * 
	 * @param imageUrlString
	 *            图片url
	 */
	private void loadImage(final String imageUrlString) {
		//设置tag
		this.setTag(imageUrlString);
		/**
		 * 调用网络接口请求图片
		 */
		ImageDownLoad imageDownLoad = new ImageDownLoad(mContext);

		imageDownLoad.downloadImageByUrl(imageUrlString, new ImageDownloadListener() {

			@Override
			public void imageDownloadFinished(Bitmap bitmap, String fileNameString) {
				try {
					if (mContext != null && bitmap != null && !bitmap.isRecycled()) {
						Log.e("kyson", "AutoloadImageView.this"+AutoloadImageView.this);
						AutoloadImageView.this.setImageBitmap(bitmap);
					}
				} catch (IllegalArgumentException e) {
					Log.i("wenjuan", "can not draw bitmap because it is recycled");
				}
			}

			@Override
			public void imageDownloadFinished(Drawable image, String fileNameString) {
			}
		});

	}

	/**
	 * 加载图片
	 * 
	 * @param imageUrlString
	 *            图片url,
	 * @param drawableId默认图片id
	 */
	public void loadImage(String imageUrlString, int drawableId) {
		Bitmap bitmap = getBitmapFromCache(imageUrlString);
		if (bitmap != null) {
			this.setImageBitmap(bitmap);
		} else {
			BitmapFactory.Options opts = new BitmapFactory.Options();
			opts.inJustDecodeBounds = true;
			if (this.DEFAULTIMAGEID == drawableId) {
				BitmapFactory.decodeResource(getResources(), R.drawable.default_image_icon, opts);
			} else {
				BitmapFactory.decodeResource(getResources(), drawableId, opts);
			}
			opts.inSampleSize = computeSampleSize(opts, -1, 480 * 800);
			opts.inJustDecodeBounds = false;
			Bitmap bmp = null;
			try {
				bmp = BitmapFactory.decodeResource(getResources(), drawableId, opts);
				this.setImageDrawable(new BitmapDrawable(bmp));
			} catch (OutOfMemoryError err) {
				if (bmp != null) {
					bmp.recycle();
					bmp = null;
				}
			}

			if (imageUrlString != null) {
				// BitmapWorkerTask bitmaptask = new BitmapWorkerTask(this);
				// bitmaptask.execute(imageUrlString);

				this.loadImage(imageUrlString);
			}
		}
	}

	public int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
		int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);

		int roundedSize;
		if (initialSize <= 8) {
			roundedSize = 1;
			while (roundedSize < initialSize) {
				roundedSize <<= 1;
			}
		} else {
			roundedSize = (initialSize + 7) / 8 * 8;
		}

		return roundedSize;
	}

	private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
		double w = options.outWidth;
		double h = options.outHeight;

		int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
		int upperBound = (minSideLength == -1) ? 128 : (int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));

		if (upperBound < lowerBound) {
			// return the larger one when there is no overlapping zone.
			return lowerBound;
		}

		if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
			return 1;
		} else if (minSideLength == -1) {
			return lowerBound;
		} else {
			return upperBound;
		}
	}

	private Bitmap getBitmapFromCache(String url) {
		if (TextUtils.isEmpty(url)) {
			return null;
		}
		return mImageCache.loadImage(url);
	}

	/**
	 * listview中用这个加载方式
	 * 
	 * @param imageUrlString
	 * @param drawableId
	 * @param pos
	 */
	// public void loadImage(String imageUrlString, int drawableId, int pos) {
	// Bitmap bitmap = getBitmapFromCache(imageUrlString);
	// if (bitmap != null) {
	// this.setImageBitmap(bitmap);
	// } else {
	// if (!TextUtils.isEmpty(imageUrlString)) {
	// String subUrl = MD5Encoder.encoding(imageUrlString);
	// if (fileHandler.isFileExists(subUrl)
	// && fileHandler.getFileSize(subUrl) != 0) {
	// // 从SD卡获取手机里面获取Bitmap
	// File file = fileHandler.findFileByName(subUrl,
	// fileHandler.getImagePath());
	// Log.i("wenjuan",
	// "image from  sd file " + file.getAbsolutePath());
	// Bitmap bitmap1 = ImageUtils
	// .readFileToBitmapWithCompress(file
	// .getAbsolutePath());
	// // 将Bitmap 加入内存缓存
	// if (bitmap1 != null) {
	// this.setImageBitmap(bitmap1);
	// mImageCache.cacheImage(bitmap1, imageUrlString);
	// }
	// } else {
	// Drawable drawable = null;
	// // 先加载默认图片
	// if (this.DEFAULTIMAGEID == drawableId) {
	// drawable = MainApplication.getContext().getResources()
	// .getDrawable(R.drawable.default_image_icon);
	// } else {
	// drawable = MainApplication.getContext().getResources()
	// .getDrawable(drawableId);
	// }
	// this.setImageDrawable(drawable);
	// this.loadImage(imageUrlString);
	// }
	//
	// }
	// }
	// }

	@Override
	protected void onDraw(Canvas canvas) {
		try {
			super.onDraw(canvas);
		} catch (Exception e) {

		}

	}
}