自己动手封装图片三级缓存网络请求框架(类似imageloader)
2016-06-21 20:06
736 查看
图片三级缓存原理
三级缓存指内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里姑且也把它划到缓存的层次结构中。当根据url向网络拉取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,如果缓存文件中也没有,再从网络上通过http请求拉取图片。在键值对(key-value)中,这个图片缓存的key是图片url的hash值,value就是bitmap。所以,按照这个逻辑,只要一个url被下载过,其图片就被缓存起来了。关于Java中对象的软引用(SoftReference),如果一个对象具有软引用,内存空间足够,垃 圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高 速缓存。使用软引用能防止内存泄露,增强程序的健壮性。
总体设计
按模块分:控制图片加载逻辑模块、图片下载引擎模块、文件缓存模块、内存缓存模块。
核心模块分析
控制图片加载逻辑模块
此模块主要封装了图片加载的逻辑,是一个异步任务。具体代码如下:public class CacheImageAsyncTask extends AsyncTask<String, Integer, Bitmap> { private ImageView imageView; private ImageFileCache fileCache; private ImageMemoryCache memoryCache; private String imgType; private String url; private int toW = 0; private int toH = 0; public CacheImageAsyncTask(ImageView imageView) { this.imageView = imageView; fileCache = MyApplication.getFileCache(); memoryCache = MyApplication.getMemoryCache(); } public CacheImageAsyncTask(ImageView imageView,int toW,int toH) { this.imageView = imageView; fileCache = MyApplication.getFileCache(); memoryCache = MyApplication.getMemoryCache(); this.toH = toH; this.toW = toW; } /** * 加载图片给特定的imageview * * @param imageView */ public CacheImageAsyncTask(ImageView imageView, String imgType) { this.imageView = imageView; fileCache = MyApplication.getFileCache(); memoryCache = MyApplication.getMemoryCache(); this.imgType = imgType; } public Bitmap getBitmap(String url,int toW,int toH) { // 从内存缓存中获取图片 Bitmap result = memoryCache.getBitmapFromCache(url); if (result == null) { // 文件缓存中获取 result = fileCache.getImage(url); if (result == null) { // 从网络获取 result = ImageGetFromHttp.downloadBitmap(url,toW,toH); if (result != null) { fileCache.saveBitmap(result, url); memoryCache.addBitmapToCache(url, result); } } else { // 添加到内存缓存 memoryCache.addBitmapToCache(url, result); } } return result; } public Bitmap getBitmap(String url) { return getBitmap(url,0,0); } protected Bitmap doInBackground(String... params) { url = params[0]; if(toH!=0 && toW!=0){ return getBitmap(url,toW,toH); }else{ return getBitmap(url); } } @Override protected void onPostExecute(Bitmap bitmap) { if (bitmap != null) { if (imageView != null) { if (imgType != null && imgType.equals("avatar")) { bitmap = ImageUtil.getRoundBitmap(bitmap); } Object tag = imageView.getTag(); if (tag != null && tag instanceof String) { String s = (String) tag; if (s.equals(url)) { imageView.setImageBitmap(bitmap); } } } } } @Override protected void onCancelled() { super.onCancelled(); } }
内存缓存模块
内存缓存模块封装了lrucache。具体代码如下:public class ImageMemoryCache { /** * 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。 * 硬引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。 */ private static final int SOFT_CACHE_SIZE = 15; //软引用缓存容量 private static LruCache<String, Bitmap> mLruCache; //硬引用缓存 private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache; //软引用缓存 public ImageMemoryCache(Context context) { int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); int cacheSize = 1024 * 1024 * memClass / 4; //硬引用缓存容量,为系统可用内存的1/4 mLruCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { if (value != null) return value.getRowBytes() * value.getHeight(); else return 0; } @Override protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { if (oldValue != null) // 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存 mSoftCache.put(key, new SoftReference<Bitmap>(oldValue)); } }; mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f, true) { private static final long serialVersionUID = 6040103833179403725L; @Override protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) { if (size() > SOFT_CACHE_SIZE) { return true; } return false; } }; } /** * 从缓存中获取图片 */ public Bitmap getBitmapFromCache(String url) { Bitmap bitmap; //先从硬引用缓存中获取 synchronized (mLruCache) { bitmap = mLruCache.get(url); if (bitmap != null) { //如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除 mLruCache.remove(url); mLruCache.put(url, bitmap); return bitmap; } } //如果硬引用缓存中找不到,到软引用缓存中找 synchronized (mSoftCache) { SoftReference<Bitmap> bitmapReference = mSoftCache.get(url); if (bitmapReference != null) { bitmap = bitmapReference.get(); if (bitmap != null) { //将图片移回硬缓存 mLruCache.put(url, bitmap); mSoftCache.remove(url); return bitmap; } else { mSoftCache.remove(url); } } } return null; } /** * 添加图片到缓存 */ public void addBitmapToCache(String url, Bitmap bitmap) { if (bitmap != null) { synchronized (mLruCache) { mLruCache.put(url, bitmap); } } } public void clearCache() { mSoftCache.clear(); } }
文件缓存模块
public class ImageFileCache { private static final String CACHDIR = "wantToGoImgCache"; private static final String WHOLESALE_CONV = ".cach"; private static final int MB = 1024*1024; private static final int CACHE_SIZE = 10; private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10; public ImageFileCache() { //清理文件缓存 removeCache(getDirectory()); } /** 从缓存中获取图片 **/ public Bitmap getImage(final String url) { File file = new File(getDirectory(),MD5Util.md5(url)); if (file.exists()) { //Log.d("ImageFileCache","绝对路径:"+file.getAbsolutePath()); Bitmap bmp = BitmapFactory.decodeFile(file.getAbsolutePath()); if (bmp == null) { file.delete(); } else { updateFileTime(file.getAbsolutePath()); return bmp; } }else{ //Log.d("ImageFileCache","文件不存在"); } return null; } /** 将图片存入文件缓存 **/ public void saveBitmap(Bitmap bm, String url) { if (bm == null) { return; } //判断sdcard上的空间 if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) { //SD空间不足 return; } //进行映射 String filename = MD5Util.md5(url); //获取缓存目录 File dirFile = getDirectory(); //缓存目录不存在,建立缓存目录 if (!dirFile.exists()) dirFile.mkdirs(); //新建文件对象 File file = new File(dirFile,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"); } } /** * 计算存储目录下的文件大小, * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定 * 那么删除40%最近没有被使用的文件 */ private boolean removeCache(File dirFile) { File[] files = dirFile.listFiles(); if (files == null) { return true; } if (!Environment.getExternalStorageState().equals( 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()); 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; } /** 修改文件的最后修改时间 **/ public void updateFileTime(String path) { File file = new File(path); long newModifiedTime = System.currentTimeMillis(); file.setLastModified(newModifiedTime); } /** 计算sdcard上的剩余空间 **/ private int freeSpaceOnSd() { StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath()); double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB; return (int) sdFreeMB; } /** 获得缓存目录 **/ private File getDirectory() { File dir = new File(getSDPathFile(),CACHDIR); return dir; } /** 取SD卡路径 **/ private File getSDPathFile() { File storageDirectory = null; boolean sdCardExist = Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED); //判断sd卡是否存在 if (sdCardExist) { storageDirectory = Environment.getExternalStorageDirectory(); //获取根目录 } return storageDirectory; } /** * 根据文件的最后修改时间进行排序 */ 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; } } } }
图片下载引擎模块
一般会在下载时对图片做一个二次采样的操作,防止oompublic class ImageGetFromHttp { private static final String LOG_TAG = "ImageGetFromHttp"; public static Bitmap downloadBitmap(String url,int toW,int toH) { Bitmap ret = null; try { URL u = new URL(url); HttpURLConnection conn = (HttpURLConnection) u.openConnection(); conn.setConnectTimeout(3000); conn.setRequestMethod("GET"); conn.setRequestProperty("connection", "keep-alive"); conn.connect(); //获取输入流 InputStream inputStream = conn.getInputStream(); //读取图片数据到字节数组,便于后续的文件缓存操作,我们缓存的是二进制文件,而不是图片文件 byte[] data = StreamUtil.readStream(inputStream); //对图片进行二次采样 //进行图片的解码 //1.进行图片的二次采样, 第一次获取图片尺寸,第二次缩放加载图片 // 指定图片解码的时候,采用的参数 BitmapFactory.Options opts = new BitmapFactory.Options(); // 仅获取图片的宽高,图像的像素信息全都不加载 // 不会占用太多内存 opts.inJustDecodeBounds = true; // 注意,使用Options来设置解码的方式 Bitmap bitmap = BitmapFactory.decodeByteArray( data, 0, data.length, opts ); int picW = opts.outWidth; int picH = opts.outHeight; //Log.d("testCaiYangbefore", picW + " " + picH); ret = ImagSampleUtil.getAfterBitmap(data, opts, toW, toH); //Log.d("testCaiYangafter", ret.getWidth() + " " + ret.getHeight()); inputStream.close(); //断开网络连接 conn.disconnect(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (ProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return ret; } }
如何使用
ima = (ImageView) findViewById(R.id.imageView1); new CacheImageAsyncTask(ima,this).execute("http://zhibo.tianyuan161.com/uploads/start_logo/2014/0429/d33063dfed90e18a8135156f97f02177.png");
代码下载
代码初稿来自csdn,做了部分优化。https://github.com/zhujainxipan/FYForAndroidTest
相关文章推荐
- 自己动手写HTTP框架:异步任务篇
- Java_HttpURLConnection使用
- JavaScript---网络编程(10)--DHTML技术演示(3)-多选框
- JavaScript---网络编程(10)--DHTML技术演示(3)-多选框
- [源码]OKHttp及Http协议笔记
- Java Web学习总结(21)——http协议响应状态码大全以及常用状态码
- Java Web学习总结(21)——http协议响应状态码大全以及常用状态码
- Java Web学习总结(21)——http协议响应状态码大全以及常用状态码
- 1.2 移动网络注册
- win32汇编实现一个简单的TCP服务端程序(WinSock的简单认知应用)
- DDoS攻击——漫画版
- HTTP request failed! HTTP/1.1 505 HTTP Version Not Supported error
- 网络信息安全学习平台---注入关第7题
- swagger-editor====本地部署到http-server
- CSRF 攻击及应对之道
- Socket Server-基于线程池的TCP服务器
- 个人博客地址,http://devopslinux.com/
- okhttp连接池复用机制
- Exception in thread "http-bio-8080-exec-10" java.lang.OutOfMemoryError: PermGen space
- Android网络编程(二)