首页 > 代码库 > Android 内存+文件+网络三级缓存

Android 内存+文件+网络三级缓存

package com.panpass.main;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

/* 
 * 图片管理 
 * 异步获取图片,直接调用loadImage()函数,该函数自己推断是从缓存还是网络载入 
 * 同步获取图片,直接调用getBitmap()函数。该函数自己推断是从缓存还是网络载入 
 * 仅从本地获取图片,调用getBitmapFromNative() 
 * 仅从网络载入图片,调用getBitmapFromHttp() 
 * 
 */
public class ImageManager {

	public static ImageManager im = null;
	private final static String TAG = "ImageManager";

	private ImageMemoryCache imageMemoryCache; // 内存缓存

	private ImageFileCache imageFileCache; // 文件缓存

	// 正在下载的image列表
	public static HashMap<String, Handler> ongoingTaskMap = new HashMap<String, Handler>();

	// 等待下载的image列表
	public static HashMap<String, Handler> waitingTaskMap = new HashMap<String, Handler>();

	// 同一时候下载图片的线程个数
	final static int MAX_DOWNLOAD_IMAGE_THREAD = 4;

	private final Handler downloadStatusHandler = new Handler() {
		public void handleMessage(Message msg) {
			startDownloadNext();
		}
	};

	public static ImageManager getInstance(Context mContext) {

		if (im == null) {

			synchronized (ImageManager.class) {

				if (im == null) {

					im = new ImageManager(mContext);
				}
			}
		}

		return im;

	}

	public ImageManager(Context mContext) {
		imageMemoryCache = new ImageMemoryCache();
		imageFileCache = new ImageFileCache(mContext);
	}

	/**
	 * 获取图片。多线程的入口
	 */
	public void loadBitmap(String urlStr, Handler handler) {
		String url = urlStr.hashCode() + "";

		// 先从内存缓存中获取。取到直接载入
		Bitmap bitmap = getBitmapFromNative(urlStr);
		if (bitmap != null) {
			Message msg = Message.obtain();
			Bundle bundle = new Bundle();
			bundle.putString("url", url);
			msg.obj = bitmap;
			msg.setData(bundle);
			handler.sendMessage(msg);
		} else {
			downloadBmpOnNewThread(urlStr, handler);
		}
	}

	/**
	 * 新起线程下载图片
	 */
	private void downloadBmpOnNewThread(final String urlStr,
			final Handler handler) {
		final String url = urlStr.hashCode() + "";
		if (ongoingTaskMap.size() >= MAX_DOWNLOAD_IMAGE_THREAD) {
			synchronized (waitingTaskMap) {
				waitingTaskMap.put(url, handler);
			}
		} else {
			synchronized (ongoingTaskMap) {
				ongoingTaskMap.put(url, handler);
			}
			new Thread() {
				public void run() {
					Bitmap bmp = getBitmapFromHttp(urlStr);
					// 不论下载是否成功,都从下载队列中移除,再由业务逻辑推断是否又一次下载
					// 下载图片使用了httpClientRequest,本身已经带了重连机制
					synchronized (ongoingTaskMap) {
						ongoingTaskMap.remove(url);
					}

					if (downloadStatusHandler != null) {
						downloadStatusHandler.sendEmptyMessage(0);

					}
					Message msg = Message.obtain();
					msg.obj = bmp;
					Bundle bundle = new Bundle();
					bundle.putString("url", url);
					msg.setData(bundle);

					if (handler != null) {
						handler.sendMessage(msg);
					}
				}
			}.start();
		}
	}

	/**
	 * 依次从内存。缓存文件,网络上载入单个bitmap,不考虑线程的问题
	 */
	public Bitmap getBitmap(String urlStr) {

		String url = urlStr.hashCode() + "";
		// 从内存缓存中获取图片
		Bitmap bitmap = imageMemoryCache.getBitmapFromMemory(url);
		if (bitmap == null) {
			// 文件缓存中获取
			bitmap = imageFileCache.getImageFromFile(url);
			if (bitmap != null) {
				// 加入到内存缓存
				imageMemoryCache.addBitmapToMemory(url, bitmap);
			} else {
				// 从网络获取
				bitmap = getBitmapFromHttp(urlStr);
			}
		}
		return bitmap;
	}

	/**
	 * 从内存或者缓存文件里获取bitmap
	 */
	public Bitmap getBitmapFromNative(String urlStr) {

		String url = urlStr.hashCode() + "";
		Bitmap bitmap = null;
		bitmap = imageMemoryCache.getBitmapFromMemory(url);

		if (bitmap == null) {
			bitmap = imageFileCache.getImageFromFile(url);
			if (bitmap != null) {
				// 加入到内存缓存
				imageMemoryCache.addBitmapToMemory(url, bitmap);
			}
		}
		return bitmap;
	}

	/**
	 * 通过网络下载图片,与线程无关
	 */
	public Bitmap getBitmapFromHttp(String urlStr) {

		String url = urlStr.hashCode() + "";
		Bitmap bmp = null;

		try {
			byte[] tmpPicByte = getImageBytes(urlStr);

			if (tmpPicByte != null) {
				bmp = BitmapFactory.decodeByteArray(tmpPicByte, 0,
						tmpPicByte.length);
			}
			tmpPicByte = null;
		} catch (Exception e) {
			e.printStackTrace();
		}

		if (bmp != null) {
			// 加入到文件缓存
			imageFileCache.saveBitmapToFile(bmp, url);
			// 加入到内存缓存
			imageMemoryCache.addBitmapToMemory(url, bmp);
		}
		return bmp;
	}

	/**
	 * 下载链接的图片资源
	 * 
	 * @param url
	 * 
	 * @return 图片
	 */
	public byte[] getImageBytes(String urlStr) {
		if (urlStr != null && !"".equals(urlStr)) {
			HttpGet get = new HttpGet(urlStr);
			get.addHeader("Accept-Encoding", "indentity");
			HttpClient client = new DefaultHttpClient();
			HttpResponse response = null;
			try {
				response = client.execute(get);

				if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {

					InputStream is = response.getEntity().getContent();

					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					byte[] buf = new byte[1024];
					int len = -1;
					while ((len = is.read(buf)) != -1) {
						baos.write(buf, 0, len);
					}

					return baos.toByteArray();

				}
			} catch (ClientProtocolException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}

		}
		return null;
	}

	/**
	 * 取出等待队列第一个任务,開始下载
	 */
	private void startDownloadNext() {
		synchronized (waitingTaskMap) {
			Iterator<Entry<String, Handler>> iter = waitingTaskMap.entrySet()
					.iterator();

			if (iter.hasNext()) {

				Map.Entry<String, Handler> entry = (Map.Entry<String, Handler>) iter
						.next();

				if (entry != null) {
					waitingTaskMap.remove(entry.getKey());
					downloadBmpOnNewThread((String) entry.getKey(),
							(Handler) entry.getValue());
				}
			}
		}
	}

	/**
	 * 图片变为圆角
	 * 
	 * @param bitmap
	 *            :传入的bitmap
	 * @param pixels
	 *            :圆角的度数,值越大,圆角越大
	 * @return bitmap:加入圆角的bitmap
	 */
	public static Bitmap toRoundCorner(Bitmap bitmap, int pixels) {
		if (bitmap == null)
			return null;
		Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
				bitmap.getHeight(), Config.ARGB_8888);
		Canvas canvas = new Canvas(output);
		final int color = 0xff424242;
		final Paint paint = new Paint();
		final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
		final RectF rectF = new RectF(rect);
		final float roundPx = pixels;
		paint.setAntiAlias(true);
		canvas.drawARGB(0, 0, 0, 0);
		paint.setColor(color);
		canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

		// paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));

		canvas.drawBitmap(bitmap, rect, rect, paint);
		return output;
	}

}
package com.panpass.main;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;public class ImageFileCache {<span style="white-space:pre">	</span>private File cacheDir;<span style="white-space:pre">	</span>public ImageFileCache(Context context) {<span style="white-space:pre">		</span>// 假设有SD卡则在SD卡中建一个LazyList的文件夹存放缓存的图片<span style="white-space:pre">		</span>// 没有SD卡就放在系统的缓存文件夹中<span style="white-space:pre">		</span>if (android.os.Environment.getExternalStorageState().equals(<span style="white-space:pre">				</span>android.os.Environment.MEDIA_MOUNTED))<span style="white-space:pre">			</span>cacheDir = new File(<span style="white-space:pre">					</span>android.os.Environment.getExternalStorageDirectory(),<span style="white-space:pre">					</span>"LazyList");<span style="white-space:pre">		</span>else<span style="white-space:pre">			</span>cacheDir = context.getCacheDir();<span style="white-space:pre">		</span>if (!cacheDir.exists())<span style="white-space:pre">			</span>cacheDir.mkdirs();<span style="white-space:pre">	</span>}<span style="white-space:pre">	</span>public Bitmap getImageFromFile(String url) {<span style="white-space:pre">		</span>// 将url的hashCode作为缓存的文件名称<span style="white-space:pre">		</span>// Another possible solution<span style="white-space:pre">		</span>// String filename = URLEncoder.encode(url);<span style="white-space:pre">		</span>File f = new File(cacheDir, url);<span style="white-space:pre">		</span>if(f.exists()){<span style="white-space:pre">			</span>return BitmapFactory.decodeFile(f.getAbsolutePath());<span style="white-space:pre">		</span>}<span style="white-space:pre">		</span>return null;<span style="white-space:pre">	</span>}<span style="white-space:pre">	</span>public void saveBitmapToFile(Bitmap bm ,String url) {<span style="white-space:pre">		</span>File file=new File(cacheDir,url);//将要保存图片的路径<span style="white-space:pre">		</span>try {<span style="white-space:pre">			</span>BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));<span style="white-space:pre">			</span>bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);<span style="white-space:pre">			</span>bos.flush();<span style="white-space:pre">			</span>bos.close();<span style="white-space:pre">		</span>} catch (IOException e) {<span style="white-space:pre">			</span>e.printStackTrace();<span style="white-space:pre">		</span>}<span style="white-space:pre">	</span>}<span style="white-space:pre">	</span>public void clear() {<span style="white-space:pre">		</span>File[] files = cacheDir.listFiles();<span style="white-space:pre">		</span>if (files == null)<span style="white-space:pre">			</span>return;<span style="white-space:pre">		</span>for (File f : files)<span style="white-space:pre">			</span>f.delete();<span style="white-space:pre">	</span>}}
package com.panpass.main;import java.util.Collections;import java.util.Iterator;import java.util.LinkedHashMap;import java.util.Map;import java.util.Map.Entry;import android.graphics.Bitmap;import android.util.Log;public class ImageMemoryCache {<span style="white-space:pre">	</span>private static final String TAG = "MemoryCache";<span style="white-space:pre">	</span>/**<span style="white-space:pre">	</span> * 放入缓存时是个同步操作 LinkedHashMap构造方法的最后一个參数true代表这个map里的元素将依照最 近使用次数由少到多排列,<span style="white-space:pre">	</span> * 即LRU。<span style="white-space:pre">	</span> * 这种优点是假设要将缓存中的元素替换,则先遍历出近期最少使用的元素来替换以提高效率<span style="white-space:pre">	</span> */<span style="white-space:pre">	</span>private Map<String, Bitmap> cache = Collections<span style="white-space:pre">			</span>.synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true));<span style="white-space:pre">	</span>// 缓存中图片所占用的字节,初始0,将通过此变量严格控制缓存所占用的堆内存<span style="white-space:pre">	</span>private long size = 0;// current allocated size<span style="white-space:pre">	</span>// 缓存仅仅能占用的最大堆内存<span style="white-space:pre">	</span>private long limit = 1000000;// max memory in bytes<span style="white-space:pre">	</span>public ImageMemoryCache() {<span style="white-space:pre">		</span>// use 25% of available heap size<span style="white-space:pre">		</span>setLimit(Runtime.getRuntime().maxMemory() / 4);<span style="white-space:pre">	</span>}<span style="white-space:pre">	</span>public void setLimit(long new_limit) {<span style="white-space:pre">		</span>limit = new_limit;<span style="white-space:pre">		</span>Log.i(TAG, "MemoryCache will use up to " + limit / 1024. / 1024. + "MB");<span style="white-space:pre">	</span>}<span style="white-space:pre">	</span>public Bitmap getBitmapFromMemory(String id) {<span style="white-space:pre">		</span>try {<span style="white-space:pre">			</span>if (!cache.containsKey(id))<span style="white-space:pre">				</span>return null;<span style="white-space:pre">			</span>return cache.get(id);<span style="white-space:pre">		</span>} catch (NullPointerException ex) {<span style="white-space:pre">			</span>return null;<span style="white-space:pre">		</span>}<span style="white-space:pre">	</span>}<span style="white-space:pre">	</span>public void addBitmapToMemory(String id, Bitmap bitmap) {<span style="white-space:pre">		</span>try {<span style="white-space:pre">			</span>if (cache.containsKey(id))<span style="white-space:pre">				</span>size -= getSizeInBytes(cache.get(id));<span style="white-space:pre">			</span>cache.put(id, bitmap);<span style="white-space:pre">			</span>size += getSizeInBytes(bitmap);<span style="white-space:pre">			</span>checkSize();<span style="white-space:pre">		</span>} catch (Throwable th) {<span style="white-space:pre">			</span>th.printStackTrace();<span style="white-space:pre">		</span>}<span style="white-space:pre">	</span>}<span style="white-space:pre">	</span>/**<span style="white-space:pre">	</span> * 严格控制堆内存,假设超过将首先替换近期最少使用的那个图片缓存<span style="white-space:pre">	</span> * <span style="white-space:pre">	</span> */<span style="white-space:pre">	</span>private void checkSize() {<span style="white-space:pre">		</span>Log.i(TAG, "cache size=" + size + " length=" + cache.size());<span style="white-space:pre">		</span>if (size > limit) {<span style="white-space:pre">			</span>// 先遍历近期最少使用的元素<span style="white-space:pre">			</span>Iterator<Entry<String, Bitmap>> iter = cache.entrySet().iterator();<span style="white-space:pre">			</span>while (iter.hasNext()) {<span style="white-space:pre">				</span>Entry<String, Bitmap> entry = iter.next();<span style="white-space:pre">				</span>size -= getSizeInBytes(entry.getValue());<span style="white-space:pre">				</span>iter.remove();<span style="white-space:pre">				</span>if (size <= limit)<span style="white-space:pre">					</span>break;<span style="white-space:pre">			</span>}<span style="white-space:pre">			</span>Log.i(TAG, "Clean cache. New size " + cache.size());<span style="white-space:pre">		</span>}<span style="white-space:pre">	</span>}<span style="white-space:pre">	</span>public void clear() {<span style="white-space:pre">		</span>cache.clear();<span style="white-space:pre">	</span>}<span style="white-space:pre">	</span>/**<span style="white-space:pre">	</span> * 图片占用的内存<span style="white-space:pre">	</span> * <span style="white-space:pre">	</span> * @param bitmap<span style="white-space:pre">	</span> * @return<span style="white-space:pre">	</span> */<span style="white-space:pre">	</span>long getSizeInBytes(Bitmap bitmap) {<span style="white-space:pre">		</span>if (bitmap == null)<span style="white-space:pre">			</span>return 0;<span style="white-space:pre">		</span>return bitmap.getRowBytes() * bitmap.getHeight();<span style="white-space:pre">	</span>}}
package com.panpass.main;import android.app.Activity;import android.content.Context;import android.graphics.Bitmap;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.Window;import android.widget.ImageView;import com.example.imgdemo.R;public class MainActivity extends Activity {<span style="white-space:pre">	</span>private Context mContext = this;<span style="white-space:pre">	</span>private ImageView img1,img2,img3,img4,img5;<span style="white-space:pre">	</span>@Override<span style="white-space:pre">	</span>protected void onCreate(Bundle savedInstanceState) {<span style="white-space:pre">		</span>super.onCreate(savedInstanceState);<span style="white-space:pre">		</span>getWindow().requestFeature(Window.FEATURE_NO_TITLE);<span style="white-space:pre">		</span>setContentView(R.layout.main_acticity);<span style="white-space:pre">		</span>img1 = (ImageView) findViewById(R.id.img1);<span style="white-space:pre">		</span>img2 = (ImageView) findViewById(R.id.img2);<span style="white-space:pre">		</span>img3 = (ImageView) findViewById(R.id.img3);<span style="white-space:pre">		</span>img4 = (ImageView) findViewById(R.id.img4);<span style="white-space:pre">		</span>img5 = (ImageView) findViewById(R.id.img5);<span style="white-space:pre">		</span>ImageManager.getInstance(mContext).loadBitmap("https://www.baidu.com/img/baidu_jgylogo3.gif?v=26599715.gif", new Handler(){<span style="white-space:pre">			</span>@Override<span style="white-space:pre">			</span>public void handleMessage(Message msg) {<span style="white-space:pre">				</span>super.handleMessage(msg);<span style="white-space:pre">				</span>img1.setImageBitmap((Bitmap)msg.obj);<span style="white-space:pre">			</span>}<span style="white-space:pre">		</span>});<span style="white-space:pre">		</span>ImageManager.getInstance(mContext).loadBitmap("https://www.baidu.com/img/baidu_jgylogo3.gif?v=26599715.gif", new Handler(){<span style="white-space:pre">			</span>@Override<span style="white-space:pre">			</span>public void handleMessage(Message msg) {<span style="white-space:pre">				</span>super.handleMessage(msg);<span style="white-space:pre">				</span>img2.setImageBitmap((Bitmap)msg.obj);<span style="white-space:pre">			</span>}<span style="white-space:pre">		</span>});<span style="white-space:pre">		</span>ImageManager.getInstance(mContext).loadBitmap("https://www.baidu.com/img/baidu_jgylogo3.gif?

v=26599715.gif", new Handler(){<span style="white-space:pre"> </span>@Override<span style="white-space:pre"> </span>public void handleMessage(Message msg) {<span style="white-space:pre"> </span>super.handleMessage(msg);<span style="white-space:pre"> </span>img3.setImageBitmap((Bitmap)msg.obj);<span style="white-space:pre"> </span>}<span style="white-space:pre"> </span>});<span style="white-space:pre"> </span>ImageManager.getInstance(mContext).loadBitmap("https://www.baidu.com/img/baidu_jgylogo3.gif?v=26599715.gif", new Handler(){<span style="white-space:pre"> </span>@Override<span style="white-space:pre"> </span>public void handleMessage(Message msg) {<span style="white-space:pre"> </span>super.handleMessage(msg);<span style="white-space:pre"> </span>img4.setImageBitmap((Bitmap)msg.obj);<span style="white-space:pre"> </span>}<span style="white-space:pre"> </span>});<span style="white-space:pre"> </span>ImageManager.getInstance(mContext).loadBitmap("https://www.baidu.com/img/baidu_jgylogo3.gif?

v=26599715.gif", new Handler(){<span style="white-space:pre"> </span>@Override<span style="white-space:pre"> </span>public void handleMessage(Message msg) {<span style="white-space:pre"> </span>super.handleMessage(msg);<span style="white-space:pre"> </span>img5.setImageBitmap((Bitmap)msg.obj);<span style="white-space:pre"> </span>}<span style="white-space:pre"> </span>});<span style="white-space:pre"> </span>//<span style="white-space:pre"> </span>img2.setImageBitmap(ImageManager.getInstance(mContext).getBitmap("https://www.baidu.com/img/baidu_jgylogo3.gif?

v=26599715.gif"));<span style="white-space:pre"> </span>//<span style="white-space:pre"> </span>//<span style="white-space:pre"> </span>img3.setImageBitmap(ImageManager.getInstance(mContext).getBitmap("https://www.baidu.com/img/baidu_jgylogo3.gif?v=26599715.gif"));<span style="white-space:pre"> </span>//<span style="white-space:pre"> </span>img4.setImageBitmap(ImageManager.getInstance(mContext).getBitmap("https://www.baidu.com/img/baidu_jgylogo3.gif?v=26599715.gif"));<span style="white-space:pre"> </span>//<span style="white-space:pre"> </span>img5.setImageBitmap(ImageManager.getInstance(mContext).getBitmap("https://www.baidu.com/img/baidu_jgylogo3.gif?v=26599715.gif"));<span style="white-space:pre"> </span>}}


Android 内存+文件+网络三级缓存