我们在项目中,不可避免地会遇到要去加载网络图片,这些天倒腾下写出了些小东西,用于图片缓存的一些个工具。自己尝试写了下(一步一步来):
1.正常的加载图片(直接指定图片id)
imageView.setImageResource(DEFAULT_PIC_ID);
2.访问网络图片
大体思路是:先显示一张默认图片,然后开启一个线程去判断缓存TAG是否有图片,有则拿来显示,无则判断文件缓存,无则再去网络下载图片,并保存到本地文件目录。
2.1 新建图片加载项实体
public class PhotoToLoad {
protected String url;
protected ImageView imageView;
protected PhotoToLoad(String url, ImageView imageView) {
this.url = url;
this.imageView = imageView;
}
}
2.2 新建图片加载线程
class PhotoLoader implements Runnable {
PhotoToLoad photoToLoad;
PhotoLoader(PhotoToLoad photoToLoad) {
this.photoToLoad = photoToLoad;
}
@Override
public void run() {
if (imageViewReused(photoToLoad))
return;
Bitmap bmp = getBitmap(photoToLoad.url);
memoryCache.put(photoToLoad.url, bmp);
if (imageViewReused(photoToLoad))
return;
BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);
// 更新的操作放在UI线程中
Activity a = (Activity) photoToLoad.imageView.getContext();
a.runOnUiThread(bd);
}
}
2.3 去网络获取Bitmap方法
private Bitmap getBitmap(String url) {
// TODO 不明白为嘛这么搞?根据URL去创建一个新的文件
File f = fileCache.getFile(url);
// 先从文件缓存中查找是否有
Bitmap b = decodeFile(f);
if (b != null)
return b;
// 最后从指定的url中下载图片
try {
Bitmap bitmap = null;
URL imageUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) imageUrl.openConnection();
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(true);
InputStream is = conn.getInputStream();
OutputStream os = new FileOutputStream(f);
CopyStream(is, os);
os.close();
bitmap = decodeFile(f);
return bitmap;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
2.4 文件缓存类
public class FileCache {
/**
* 文件集目录
*/
private File cacheDir;
private Context mContext;
public FileCache(final Context context) {
mContext = context;
// 1.如果有SD卡则在SD卡中建一个LazyList的目录存放缓存的图片
// 2.没有SD卡就放在系统的缓存目录中
if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
//cacheDir = new File(android.os.Environment.getExternalStorageDirectory(), "LazyList");
cacheDir = new File(android.os.Environment.getExternalStorageDirectory(), mContext.getPackageName() + File.separator + "images");
else
cacheDir = context.getCacheDir();
if (!cacheDir.exists())
cacheDir.mkdirs();
}
public File getFile(String url) {
// 将Url的hashCode作为缓存的文件名
String fileName = String.valueOf(url.hashCode()) + ".png";
File f = new File(cacheDir, fileName);
return f;
}
/**
* 清除该目录下所有的文件
* @author chenxinyi2 2015年4月29日 下午2:42:54
*/
public void clear() {
File[] files = cacheDir.listFiles();
if (files == null)
return;
for (File f : files) {
f.delete();
}
}
}
2..5 内存缓存
public class MemoryCache {
/**
* TAG
*/
private final String TAG = MemoryCache.class.getSimpleName();
/**
* 缓存只能使用的最大堆内存
* max memory in bytes
*/
private long limit = 1000000;
/**
* 1.放入缓存是个同步操作
* 2.LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即LRU
* 3.这样的好处就是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率
*/
private Map cache = Collections.synchronizedMap(new LinkedHashMap(10, 1.5f, true));
/**
* 缓存中图片所占用的字节数,初始0,通过此变量严格控制缓存所占用的堆内存
*/
private long size = 0;
protected MemoryCache() {
// 这里严格限制只能使用最大JVM内存的 1 / 4.
this.limit = Runtime.getRuntime().maxMemory() / 4;
}
protected void put(String id, Bitmap bitmap) {
if (cache.containsKey(id))
size -= getSizeInBytes(cache.get(id));
cache.put(id, bitmap);
size += getSizeInBytes(bitmap);
checkSize();
}
/**
* 严格控制堆内存,如果超过将首先替换最近最少使用的那个图片缓存
* @author chenxinyi2 2015年4月29日 下午3:11:32
*/
private void checkSize() {
Log.i(TAG, "cache size=" + size + ", length=" + cache.size());
// 如果堆内存超出限制
if (size > limit) {
// 遍历最近最少使用的元素
Iterator> iter = cache.entrySet().iterator();
while(iter.hasNext()) {
Entry entry = iter.next();
size -= getSizeInBytes(entry.getValue());
iter.remove();
if (size <= limit)
break;
}
}
}
/**
* 获取bitmap
* @author chenxinyi2 2015年4月28日 下午4:27:29
* @param id
* @return
*/
protected Bitmap getBitmap(String id) {
// TODO 这里考虑吃掉空指针异常
if (!cache.containsKey(id))
return null;
return cache.get(id);
}
/**
* 图片占用的内存
*
* @param bitmap
* @return
*/
protected long getSizeInBytes(Bitmap bitmap) {
if (bitmap == null)
return 0;
return bitmap.getRowBytes() * bitmap.getHeight();
}
}
2.6 相关方法和类
/**
* 用于在UI线程中更新界面
* @author chenxinyi2
* @version 1.0.0 2015年4月29日 下午3:46:30
* @see
* @since JDK 1.7.0_45
*/
class BitmapDisplayer implements Runnable{
Bitmap bitmap;
PhotoToLoad photoToLoad;
public BitmapDisplayer(Bitmap b, PhotoToLoad p) {
bitmap = b;
photoToLoad = p;
}
@Override
public void run() {
if (imageViewReused(photoToLoad))
return;
if (bitmap != null)
photoToLoad.imageView.setImageBitmap(bitmap);
else
photoToLoad.imageView.setImageResource(DEFAULT_PIC_ID);
}
}
/**
* 图片是否在缓存中有值
*
* @param photoToLoad
* @return 缓存中是否有值
*/
boolean imageViewReused(PhotoToLoad photoToLoad) {
String tag = imageViews.get(photoToLoad.imageView);
if (tag == null || !tag.equals(photoToLoad.url))
return false;
return true;
}
private void CopyStream(InputStream is, OutputStream os) {
final int buffer_size = 1024;
try {
byte[] bytes = new byte[buffer_size];
for (;;) {
int count = is.read(bytes, 0, buffer_size);
if (count == -1)
break;
os.write(bytes, 0, count);
}
} catch (Exception ex) {
}
}