最近领养了一直小狗狗,据狗主人说是只阿拉斯加,求大伙见证。
不管他是不是阿拉斯加,我还是得养着,取名“蛋蛋”。
继续谈技术。
说到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 = null;
}
}
/**
* 根据给的key创建图片
*/
@Override
protected Bitmap create(String key) {
Bitmap bitmap = null;
FileHandler fileHandler = new FileHandler(
MongoApplication.getContext());
File file = fileHandler.findFileByName(key,
fileHandler.getImagePath());
if (file != null) {
bitmap = ImageUtils.readFileToBitmapWithCompress(file
.getAbsolutePath());
return bitmap;
}
return null;
};
};
public static ImageCache shareInstance() {
if (instance == null) {
synchronized (ImageCache.class) {
instance = new ImageCache();
}
}
return instance;
}
private ImageCache() {
}
/**
* 加载图片,如果没有,则加载默认图片
*
* @param resId
* @param imageView
*/
public Bitmap loadImage(String imageUrlString) {
Bitmap bitmap = null;
if(!TextUtils.isEmpty(imageUrlString)){
String imageKey = MD5Encoder.encoding(imageUrlString);
System.out.println("key:" + imageKey);
bitmap = mLruCache.get(imageKey);
}
if (bitmap != null && !bitmap.isRecycled()) {
Log.i(TAG, "在缓存池中找到了相应资源,直接返回已经存在的bitmap资源");
return bitmap;
} else {
// 默认图片R.drawable.ic_menu_add
Log.i(TAG, "没有在缓存池中找到相应资源,返回的bitmap为null");
return null;
}
}
/**
* 缓存图片
*
* @param imageNameString
* 文件名(不包含路径)
*/
public void cacheImage(Bitmap bitmap, String fileUrlString) {
if (fileUrlString == null || bitmap == null) {
Log.i(TAG, "参数传入有误,fileNameString或bitmap为空");
return;
}
if (bitmap != null) {
String imageKey = MD5Encoder.encoding(fileUrlString);
mLruCache.put(imageKey, bitmap);
Log.i(TAG, "缓存成功!缓存的图片url为:" + fileUrlString + "对应的key:" + imageKey);
} else
Log.i(TAG, "缓存失败!缓存池中已存在相应资源");
}
/**
* @param file
*/
public void cacheImage(File file, String fileUrlString) {
if (file == null || fileUrlString == null) {
return;
}
// Drawable drawable = Drawable.
Bitmap bitmap2 = loadImage(fileUrlString);
if (bitmap2 == null) {
// Drawable.createFromStream(inputStream, null);
// 将文件夹中的文件缓存起来
Bitmap bitmap = ImageUtils.readFileToBitmapWithCompress(file
.getAbsolutePath());
cacheImage(bitmap, fileUrlString);
} else {
System.out.println("缓存失败!缓存池中已存在相应资源");
}
}
}
何时缓存又是个问题,看如下代码可以解决
/**
* 图片下载器类
*
*/
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) {
}
}
}