最近领养了一直小狗狗,据狗主人说是只阿拉斯加,求大伙见证。
不管他是不是阿拉斯加,我还是得养着,取名“蛋蛋”。
继续谈技术。
说到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) { } } }
续说ListView重用之加载图片,布布扣,bubuko.com
原文地址:http://blog.csdn.net/zjh171/article/details/38715337