Android 图片缓存、加载器
来源:互联网 发布:淘宝哪家多肉土好 编辑:程序博客网 时间:2024/05/21 08:52
前言
关于缓存的文章网络上有很多,本文旨在提供一份优秀缓存机制的同时,使大家理解其运行原理,供各位交流、进一步提升。
一、代码结构及缓存基本原理
二、图片加载器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类里面配置。
实际效果及缓存文件夹如下:
不足之处,望指点,望体谅。 附: 源码链接
转自:http://blog.csdn.net/justlcw/article/details/20708487
- Android 图片缓存、加载器
- Android 图片缓存、加载器
- android 图片加载+缓存技术
- Android图片加载缓存类
- android图片加载与缓存
- android异步图片加载中的图片缓存
- Android图片加载优化--图片缓存
- android本地图片加载器,LruCache缓存机制
- android 新闻图片加载,缓存处理
- android 之图片异步加载,带缓存。
- android 图片缓存 异步加载 简要介绍
- Android异步加载网络图片 + 双缓存
- android 异步加载网络图片缓存机制
- android加载图片优化(三级缓存)
- Android图片加载缓存库<1>
- Android图片加载缓存库<2>
- Android图片加载缓存库<3>
- Android 图片如何高效加载与缓存
- ”舍得“大法:把自己的优点当缺点倒出去
- oracle 锁的机制
- poj 3468A Simple Problem with Integers(树状数组区间修改)
- Linux 平台 Oracle 单实例 从10.2.0.1 升级到10.2.0.5.4 步骤
- 使用FFMPEG类库分离出多媒体文件中的H.264码流
- Android 图片缓存、加载器
- 使用CSS修正一切:20多个常见Bug及其修正方法
- iOS自定义TableViewCell详解[两种方法]
- Android控件-GridView使用学习
- socket实现服务端与客户端的通讯
- debug正常,release有问题
- ibatis queryForObject() 、queryForList()、queryForMap()
- Julia的Dates库是重要和必要的补充!
- 【互动问答分享】第5期决胜云计算大数据时代Spark亚太研究院公益大讲堂