android listview 滚动时异步加载图片的问题
LoadImage.java
package com.gowin.cach; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import android.graphics.Bitmap; import android.os.Handler; import android.os.Message; import android.widget.ImageView; public class LoadImage { private ExecutorService executorService; // 固定五个线程来 private ImageMemoryCache memoryCache;// 内存缓存 private ImageFileCache fileCache;// 文件缓存 private Map<String, ImageView> taskMap;// 存放任务 public LoadImage() { executorService = Executors.newFixedThreadPool(10); memoryCache = new ImageMemoryCache(); fileCache = new ImageFileCache(); taskMap = new HashMap<String, ImageView>(); } public void addTask(String url, ImageView img) { Bitmap bitmap=memoryCache.getBitmapFromCache(url); if(bitmap!=null) { img.setImageBitmap(bitmap); }else { synchronized (taskMap) { taskMap.put(Integer.toString(img.hashCode()), img); } } } public void doTask() { synchronized (taskMap) { Collection<ImageView> con = taskMap.values(); for (ImageView i : con) { if (i != null) { if (i.getTag() != null) { loadImage((String) i.getTag(), i); } } } taskMap.clear(); } } private void loadImage(String url, ImageView img) { /*** 加入新的任务 ***/ executorService.submit(new TaskWithResult(new TaskHandler(url, img), url)); } /*** 获得一个图片,从三个地方获取,首先是内存缓存,然后是文件缓存,最后从网络获取 ***/ private Bitmap getBitmap(String url) { // 从内存缓存中获取图片 Bitmap result; result = memoryCache.getBitmapFromCache(url); if (result == null) { // 文件缓存中获取 result = fileCache.getImage(url); if (result == null) { // 从网络获取 result = ImageGetForHttp.downloadBitmap(url); if (result != null) { memoryCache.addBitmapToCache(url, result); fileCache.saveBmpToSd(result, url); } } else { // 添加到内存缓存 memoryCache.addBitmapToCache(url, result); } } return result; } /*** 完成消息 ***/ private class TaskHandler extends Handler { String url; ImageView img; public TaskHandler(String url, ImageView img) { this.url = url; this.img = img; } @Override public void handleMessage(Message msg) { /*** 查看imageview需要显示的图片是否被改变 ***/ if (img.getTag().equals(url)) { if (msg.obj != null) { Bitmap bitmap = (Bitmap) msg.obj; img.setImageBitmap(bitmap); } } } } /*** 子线程任务 ***/ private class TaskWithResult implements Callable<String> { private String url; private Handler handler; public TaskWithResult(Handler handler, String url) { this.url = url; this.handler = handler; } @Override public String call() throws Exception { Message msg = new Message(); msg.obj = getBitmap(url); if (msg.obj != null) { handler.sendMessage(msg); } return url; } } }ImageMemoryCache.java
package com.gowin.cach; import java.lang.ref.SoftReference; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.concurrent.ConcurrentHashMap; import android.graphics.Bitmap; /**** *内存中的缓存 ****/ public class ImageMemoryCache { private static final int HARD_CACHE_CAPACITY = 30; private HashMap<String, Bitmap> mHardBitmapCache ; private final static ConcurrentHashMap<String, SoftReference<Bitmap>> mSoftBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(HARD_CACHE_CAPACITY / 2); public ImageMemoryCache() { mHardBitmapCache = new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 2, 0.75f, true) { /** * */ private static final long serialVersionUID = 1L; @Override protected boolean removeEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) { if (size() > HARD_CACHE_CAPACITY) { // Entries push-out of hard reference cache are transferred to soft reference cache mSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue())); return true; } else return false; } }; } /** * 从缓存中获取图片 */ public Bitmap getBitmapFromCache(String url) { // 先从mHardBitmapCache缓存中获取 synchronized (mHardBitmapCache) { final Bitmap bitmap =mHardBitmapCache.get(url); if (bitmap != null) { //如果找到的话,把元素移到linkedhashmap的最前面,从而保证在LRU算法中是最后被删除 mHardBitmapCache.remove(url); mHardBitmapCache.put(url,bitmap); return bitmap; } } //如果mHardBitmapCache中找不到,到mSoftBitmapCache中找 SoftReference<Bitmap>bitmapReference = mSoftBitmapCache.get(url); if (bitmapReference != null) { final Bitmap bitmap =bitmapReference.get(); if (bitmap != null) { //将图片移回硬缓存 mHardBitmapCache.put(url, bitmap); mSoftBitmapCache.remove(url); return bitmap; } else { mSoftBitmapCache.remove(url); } } return null; } /***添加图片到缓存***/ public void addBitmapToCache(String url, Bitmap bitmap) { if (bitmap != null) { synchronized (mHardBitmapCache) { mHardBitmapCache.put(url, bitmap); } } } }
package com.gowin.cach; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; import java.util.Comparator; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Environment; import android.os.StatFs; import android.util.Log; public class ImageFileCache { private static final String CACHDIR = "data/gowin/imgCach"; private static final String WHOLESALE_CONV = ".cach"; /**过期时间3天**/ private static final long mTimeDiff = 3 * 24 * 60 * 60 * 1000; public ImageFileCache() { //清理文件缓存 removeCache(getDirectory()); } public Bitmap getImage(final String url) { final String path = getDirectory() + "/" + convertUrlToFileName(url); File file = new File(path); if (file.exists()) { Bitmap bmp=BitmapFactory.decodeFile(path); if(bmp==null) { file.delete(); }else { updateFileTime(path); return bmp; } } return null; } /***缓存空间大小****/ private static final int FREE_SD_SPACE_NEEDED_TO_CACHE=10; public void saveBmpToSd(Bitmap bm, String url) { if (bm == null) { //需要保存的是一个空值 return; } //判断sdcard上的空间 if (FREE_SD_SPACE_NEEDED_TO_CACHE >freeSpaceOnSd()) { //SD空间不足 return; } String filename =convertUrlToFileName(url); String dir = getDirectory(); File file = new File(dir +"/" + filename); try { file.createNewFile(); OutputStream outStream = new FileOutputStream(file); bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream); outStream.flush(); outStream.close(); } catch (FileNotFoundException e) { Log.w("ImageFileCache","FileNotFoundException"); } catch (IOException e) { Log.w("ImageFileCache","IOException"); } } private static final int CACHE_SIZE=10; // 清理缓存 /** * 计算存储目录下的文件大小, * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定 * 那么删除40%最近没有被使用的文件 * * @param dirPath * @param filename */ private boolean removeCache(String dirPath) { File dir = new File(dirPath); File[] files = dir.listFiles(); if (files == null) { return true; } if (!android.os.Environment.getExternalStorageState().equals( android.os.Environment.MEDIA_MOUNTED)) { return false; } int dirSize = 0; for (int i = 0; i < files.length; i++) { if (files[i].getName().contains(WHOLESALE_CONV)) { dirSize += files[i].length(); } } if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) { int removeFactor = (int) ((0.4 * files.length) + 1); Arrays.sort(files, new FileLastModifSort()); Log.i("ImageFileCache", "清理缓存文件"); for (int i = 0; i < removeFactor; i++) { if (files[i].getName().contains(WHOLESALE_CONV)) { files[i].delete(); } } } if (freeSpaceOnSd() <= CACHE_SIZE) { return false; } return true; } /** * TODO 根据文件的最后修改时间进行排序 * */ private class FileLastModifSort implements Comparator<File> { public int compare(File arg0, File arg1) { if (arg0.lastModified() > arg1.lastModified()) { return 1; } else if (arg0.lastModified() == arg1.lastModified()) { return 0; } else { return -1; } } } /** * 删除过期文件 * * @param dirPath * @param filename */ public void removeExpiredCache(String dirPath, String filename) { File file = new File(dirPath, filename); if (System.currentTimeMillis() - file.lastModified() > mTimeDiff) { Log.i("ImageFileCache", "Clear some expiredcache files "); file.delete(); } } /** * 修改文件的最后修改时间 * 这里需要考虑,是否将使用的图片日期改为当前日期 * @param path */ public void updateFileTime(String path) { File file = new File(path); long newModifiedTime = System.currentTimeMillis(); file.setLastModified(newModifiedTime); } /** * 计算sdcard上的剩余空间 * @return */ private int MB=1024*1024; private int freeSpaceOnSd() { StatFs stat = new StatFs(Environment.getExternalStorageDirectory() .getPath()); double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB; return (int) sdFreeMB; } /**将url转成文件名**/ private String convertUrlToFileName(String url) { String[] strs = url.split("/"); return strs[strs.length - 1] + WHOLESALE_CONV; } /**获得缓存目录**/ private String getDirectory() { String dir = getSDPath() + "/" + CACHDIR; String substr = dir.substring(0, 4); if (substr.equals("/mnt")) { dir = dir.replace("/mnt", ""); } return dir; } /**** 取SD卡路径不带/ ****/ public String getSDPath() { File sdDir = null; boolean sdCardExist = Environment.getExternalStorageState().equals( android.os.Environment.MEDIA_MOUNTED); // 判断sd卡是否存在 if (sdCardExist) { sdDir = Environment.getExternalStorageDirectory();// 获取跟目录 } if(sdDir!=null) { return sdDir.toString(); }else { return ""; } } }
package com.gowin.cach; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.http.AndroidHttpClient; import android.util.Log; public class ImageGetForHttp { private static final String LOG_TAG="ImageGetForHttp"; public static Bitmap downloadBitmap(String url) { //final int IO_BUFFER_SIZE = 4 * 1024; // AndroidHttpClient is not allowed to be used from the main thread final HttpClient client = AndroidHttpClient.newInstance("Android"); final HttpGet getRequest = new HttpGet(url); try { HttpResponse response = client.execute(getRequest); final int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != HttpStatus.SC_OK) { Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url); return null; } final HttpEntity entity = response.getEntity(); if (entity != null) { InputStream inputStream = null; try { inputStream = entity.getContent(); // return BitmapFactory.decodeStream(inputStream); // Bug on slow connections, fixed in future release. FilterInputStream fit= new FlushedInputStream(inputStream); return BitmapFactory.decodeStream(fit); } finally { if (inputStream != null) { inputStream.close(); } entity.consumeContent(); } } } catch (IOException e) { getRequest.abort(); Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e); } catch (IllegalStateException e) { getRequest.abort(); Log.w(LOG_TAG, "Incorrect URL: " + url); } catch (Exception e) { getRequest.abort(); Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e); } finally { if ((client instanceof AndroidHttpClient)) { ((AndroidHttpClient) client).close(); } } return null; } /* * An InputStream that skips the exact number of bytes provided, unless it reaches EOF. */ static class FlushedInputStream extends FilterInputStream { public FlushedInputStream(InputStream inputStream) { super(inputStream); } @Override public long skip(long n) throws IOException { long totalBytesSkipped = 0L; while (totalBytesSkipped < n) { long bytesSkipped = in.skip(n - totalBytesSkipped); if (bytesSkipped == 0L) { int b = read(); if (b < 0) { break; // we reached EOF } else { bytesSkipped = 1; // we read one byte } } totalBytesSkipped += bytesSkipped; } return totalBytesSkipped; } } }
使用时,要救imageview的tag是需要下载的url
在getview时先addtask
当滚动停止的时候调用doTask