Android 图片缓存、加载器
来源:互联网 发布:电脑看书软件app 编辑:程序博客网 时间:2024/05/16 05:04
前言
关于缓存的文章网络上有很多,本文旨在提供一份优秀缓存机制的同时,使大家理解其运行原理,供各位交流、进一步提升。
一、代码结构及缓存基本原理
二、图片加载器ImageWorker
/** * 根据url加载一个图片到ImageView上,未加载成功的时候显示加载图片<loadingImageId> * * @param url 图片网络地址. * @param imageView 图片View * @param loadingImageId 加载图片资源ID * @Description: * @Author Justlcw * @Date 2014-3-6 */ public void loadImage(String url, ImageView imageView, int loadingImageId) { //如果图片网络地址为空,直接返回. if(TextUtils.isEmpty(url)) { return; } Bitmap bitmap = null; if (mImageCache != null) { //先从内存缓存中取. bitmap = mImageCache.getBitmapFromMemCache(url); } if (bitmap != null) { //如果取到了,直接显示. imageView.setImageBitmap(bitmap); } else if (cancelPotentialWork(url, imageView)) { //如果没有取到,取消之前相同的加载线程,取消成功,就启动一个新的加载线程. final BitmapWorkerTask task = new BitmapWorkerTask(imageView); final AsyncDrawable asyncDrawable = new AsyncDrawable(mContext.getResources(), getLoadingImage(loadingImageId), task); imageView.setImageDrawable(asyncDrawable); task.execute(url); } }
如图,ImageWorker里面含有一个ImageCache(图片缓存),当开始加载一张网络图片的时候,首先从ImageCache(图片缓存)的内存缓存中获取,如果获取到了直接显示,否则启动一个后台线程加载图片。
1,看下BitmapWorkerTask在后台做了什么事
@Override protected Bitmap doInBackground(String... params) { url = params[0]; Bitmap bitmap = null; // 1.如果图片缓存不为null. // 2.如果当前线程没有被取消. // 3.如果被弱引用的ImageView没有被销毁. // 4.如果加载程序无须退出. if (mImageCache != null && !isCancelled() && getAttachedImageView() != null && !mExitTasksEarly) { //从硬盘缓存中去. bitmap = mImageCache.getBitmapFromDiskCache(url); } // 1.如果从硬盘缓存中获取的图片为Null. // 2.如果当前线程没有被取消. // 3.如果被弱引用的ImageView没有被销毁. // 4.如果加载程序无须退出. if (bitmap == null && !isCancelled() && getAttachedImageView() != null && !mExitTasksEarly) { //后台获取图片<网络获取> bitmap = processBitmap(params[0]); } // 1.如果加载过后的图片不为null. // 2.如果图片缓存不为null. if (bitmap != null && mImageCache != null) { //添加到缓存中. mImageCache.addBitmapToCache(url, bitmap); } return bitmap; }
先忽视if判断,执行语句表明:后台线程先是从ImageCache(图片缓存)的硬盘缓存中获取图片,如果获取不到再从网络获取;当获取成功后,又将图片放入ImageCache(图片缓存)中,这刚好验证了上面缓存的基本原理。
2,cancelPotentialWork(url, imageView)这个方法的作用
/** * 取消指定url的图片加载线程. * @param url 图片网络地址 * @param imageView 图片View * @return 如果有相同的线程在进行,返回false,否则返回 true. * @Description: * @Author Justlcw * @Date 2014-3-6 */ public static boolean cancelPotentialWork(String url, ImageView imageView) { //从ImageView中获取加载线程. final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (bitmapWorkerTask != null) { final String taskUrl = bitmapWorkerTask.url; if (TextUtils.isEmpty(taskUrl) || !taskUrl.equals(url)) { //如果加载的url为空,获得加载的url不等指定的url,则取消当前的加载线程. bitmapWorkerTask.cancel(true); LogUtil.d(TAG, "cancel load : " + taskUrl); } else { // 如果加载的url等于指定的url,则说明不需要重新加载,返回false. return false; } } return true; }
回过头看下ImageWorker的内部类那张图片,当传入一个ImageView开始加载一张图片的时候,启动的加载线程(BitmapWorkerTask)会弱引用这个ImageView;同时ImageView又设置了一个AsyncDrawable(extends BitmapDrawable),而这个AsyncDrawable又弱引用了加载线程BitmapWorkerTask。
方法原理:当开始加载线程BitmapWorkerTask的时候,判断这个ImageView是否已经有一个相同的线程在运作了,如果有就不开始当前线程,如果没有就或者ImageView的加载线程加载的资源不同于当前,就取消这个原来的线程,开始新的加载线程。
三、图片缓存ImageCache
从上面的图可以看出,ImageCache包含了一个硬盘缓存ImageDiskLruCache和一个内存缓存LruCache<String, Bitmap>。
LruCache<String, Bitmap>由google的V4包提供,工作原理看源码即可理解,这里不做介绍。ImageDiskLruCache这个类继承于DiskLruCache,主要是将文件(File)的存取拓展成图片(Bitmap)的存取,所以重点看下其父类DiskLruCache:
1,DiskLruCache的构造
/** * 打开一个硬盘缓存. * @Description: * @Author Justlcw * @Date 2014-3-5 */ public final static DiskLruCache openCache(Context context, String cacheName, long maxByteSize) { File cacheDir = CacheUtils.getEnabledCacheDir(context, cacheName); if (cacheDir.isDirectory() && cacheDir.canWrite() && CacheUtils.getUsableSpace(cacheDir) > maxByteSize) { return new DiskLruCache(cacheDir, maxByteSize); } return null; }
/** * 获得可使用的<缓存主目录>(先外部,后外部) * @Description: * @Author Justlcw * @Date 2014-3-6 */ public static File getEnabledCacheDir(Context context, String cacheName) { String cachePath; if(isExternalStorageRWable()) { cachePath = Environment.getExternalStorageDirectory().getPath(); } else { cachePath = context.getCacheDir().getPath(); } File cacheFile = new File(cachePath + CacheConfig.DISK_CACHE_NAME + cacheName); //如果缓存目录不存在,创建缓存目录. if (!cacheFile.exists()) { cacheFile.mkdirs(); } return cacheFile; }
如果外部存储(SD卡)可以使用,DiskLruCache则在外部存储中创建一个指定缓存名称的文件夹;如果不可用,则使用内存存储并创建指定文件夹。
同时还会对文件夹的可用空间进行判断,如果以上条件都允许,则返回一个DiskLruCache。
2,DiskLruCache的存入
/** * 把一个字节数组以文件存放在缓存中. * @param key 关键字 * @param inputStream 字节流 * @return 缓存文件路径. * @Description: * @Author Justlcw * @Date 2014-3-5 */ public final String put(String key, InputStream inputStream) { BufferedInputStream bufferedInputStream = null; OutputStream bufferOps= null; try { bufferedInputStream = new BufferedInputStream(inputStream); String filePath = createFilePath(key); bufferOps = new BufferedOutputStream(new FileOutputStream(filePath)); byte[] b = new byte[CacheConfig.IO_BUFFER_SIZE]; int count; while ((count = bufferedInputStream.read(b)) > 0) { bufferOps.write(b, 0, count); } bufferOps.flush(); LogUtil.d(TAG, "put success : " + key); onPutSuccess(key, filePath); flushCache(); return filePath; } catch (IOException e) { LogUtil.d(TAG, "store failed to store: " + key, e); } finally { try { if(bufferOps != null) { bufferOps.close(); } if(bufferedInputStream != null) { bufferedInputStream.close(); } if(inputStream != null) { inputStream.close(); } } catch (IOException e) { LogUtil.d(TAG, "close stream error : "+e.getMessage()); } } return ""; }
/** * 放入一个文件. * @param key 关键字 * @param filePath 文件路径 */ protected final void onPutSuccess(String key, String filePath) { mLinkedHashMap.put(key, filePath); cacheNumSize = mLinkedHashMap.size(); cacheByteSize += new File(filePath).length(); }
/** * 缓存大小溢出处理. * @Description: * @Author Justlcw * @Date 2014-3-5 */ protected final void flushCache() { Entry<String, String> eldestEntry; File eldestFile; long eldestFileSize; int count = 0; //超过最大缓存文件个数 or 超过最大空间大小 移除不常用的文件,并且一次最多只能移除4个. while (count < MAX_REMOVALS && (cacheNumSize > maxCacheNumSize || cacheByteSize > maxCacheByteSize)) { eldestEntry = mLinkedHashMap.entrySet().iterator().next(); eldestFile = new File(eldestEntry.getValue()); eldestFileSize = eldestFile.length(); mLinkedHashMap.remove(eldestEntry.getKey()); eldestFile.delete(); cacheNumSize = mLinkedHashMap.size(); cacheByteSize -= eldestFileSize; count++; LogUtil.d(TAG, "flushCache - Removed :" + eldestFile.getAbsolutePath()+ ", " + eldestFileSize); } }
当文件存入成功的时候,做了两步操作:
1.将文件的路径放入到当前使用的缓存文件列表中,并且更新缓存文件总共的数量及大小。
2.判断当前缓存文件有没有超出上限限制 (数量限制 和 大小限制)
3,DiskLruCache的取出
/** * 根据key返回缓存文件. * @param key key * @Description: * @Author Justlcw * @Date 2014-3-6 */ public final File get(String key) { if(containsKey(key)) { final File file = new File(createFilePath(key)); if(file.exists()) { return file; } } return null; } /** * 缓存中是否有key的数据保存 * @param key 关键字 * @Description: * @Author Justlcw * @Date 2014-3-5 */ public final boolean containsKey(String key) { //是否在map中. if (mLinkedHashMap.containsKey(key)) { return true; } //如果不在map中,看是或否在文件夹中. final String existingFile = createFilePath(key); if (new File(existingFile).exists()) { //如果存在,放入map后期直接提取. onPutSuccess(key, existingFile); return true; } return false; }
根据key得到相应的文件路径,如果这个路径在当前使用的缓存中,直接返回这个文件;如果不在判断是否在原来的缓存文件夹中,如果在就是取出来放入当前使用的缓存列表中,否表示不在,则去下载。
四、图片加载器的使用
@Override public View getView(int position, View convertView, ViewGroup parent) { if(convertView == null) { convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_grid, parent, false); } ImageView imageView = (ImageView)convertView.findViewById(R.id.imageView); mImageLoader.loadImage(imgURLArray[position], imageView, R.drawable.bg_loading); return convertView; }
在activity中放一个gridview,然后设置一个adapter,adapter使用图片加载的代码如上。
图片的内存缓存大小使用的是应用分得内存大小的1/8,所有的配置都在CacheConfig类里面配置。
实际效果及缓存文件夹如下:
不足之处,望指点,望体谅。 附: 源码链接
- Android 图片缓存、加载器
- Android 图片缓存、加载器
- android 图片加载+缓存技术
- Android图片加载缓存类
- android图片加载与缓存
- android异步图片加载中的图片缓存
- Android图片加载优化--图片缓存
- android本地图片加载器,LruCache缓存机制
- android 新闻图片加载,缓存处理
- android 之图片异步加载,带缓存。
- android 图片缓存 异步加载 简要介绍
- Android异步加载网络图片 + 双缓存
- android 异步加载网络图片缓存机制
- android加载图片优化(三级缓存)
- Android图片加载缓存库<1>
- Android图片加载缓存库<2>
- Android图片加载缓存库<3>
- Android 图片如何高效加载与缓存
- 聚类分析学习
- 面向对象的思维(与结构化思维比较)
- 恒企教育集团招聘程序员
- PMEM原理分析
- eXtremeDB 3.1
- Android 图片缓存、加载器
- 格子刷油漆
- 剔除不需要的CKEditor组件
- 由NSString的copy和strong/retain引出o-c的copy机制 (一)
- JAVA菜单快捷键
- [LeetCode]Reverse Words in a String
- 可执行文件(ELF)格式的理解
- POJ 1265 Area (计算几何)(Pick定理)
- How dvm calls native method