Android之图片缓存管理

如果每次加载同一张图片都要从网络获取,那代价实在太大了。所以同一张图片只要从网络获取一次就够了,然后在本地缓存起来,之后加载同一张图片时就从缓存中加载就可以了。从内存缓存读取图片是最快的,但是因为内存容量有限,所以最好再加上文件缓存。文件缓存空间也不是无限大的,容量越大读取效率越低,因此可以设置一个限定大小比如10M,或者限定保存时间比如一天。
因此,加载图片的流程应该是:
1、先从内存缓存中获取,取到则返回,取不到则进行下一步;
2、从文件缓存中获取,取到则返回并更新到内存缓存,取不到则进行下一步;

3、从网络下载图片,并更新到内存缓存和文件缓存。

后两个步骤纯碎属于业务逻辑,暂且不表,这里来看一下手Q使用的图片缓存管理策略。
说到缓存管理,首先谈一下java中的强引用和弱引用

  • 强引用:最普遍的引用,若一个对象具有强引用,那么GC绝不会回收它。如A a = new A()
  • 弱引用: 弱引用又分为以下三类:
    • 软引用(SoftReference): 这类引用只有当内存空间不足GC才会回收它
    • 弱引用(WeakReference): 这类引用拥有更短的生命周期,GC扫描过程中一旦发现了此类引用,不管当前内存是否足够,立即回收
    • 虚引用(PhantomRefence): 这类引用并不会决定对象的生命周期,如果一个对象仅持有虚引用,则任何时刻都可能被回收

下面来看看这样一个图片缓存类,为了更大限度使用缓存,它使用了强引用缓存(强引用)和弱引用缓存(弱引用)双重缓存,强引用缓存不会轻易被回收,用来保存常用数据,不常用的转入弱引用缓存。**

ImageCache.javapublic class ImageCache {private static final String TAG = "ImageCache";//CustomLruCache是一个继承了LruCache的继承类,它代表强引用缓存,它的缓存大小一般由业务方提供private CustomLruCache<String, Drawable> mMemoryCache;// Default memory cache size//这里设置的是弱引用缓存以及它所占据的空间大小private static final int DEFAULT_MEM_CACHE_SIZE = 5; // 5MBprivate final HashMap<String, WeakReference<Drawable>> mRefCache = new HashMap<String, WeakReference<Drawable>>();public ImageCache(int memSize) {memSize = Math.max(memSize, DEFAULT_MEM_CACHE_SIZE);QLog.d(TAG, "Memory cache size = " + memSize + "MB");mMemoryCache = new CustomLruCache<String, Drawable>(memSize * 1024 * 1024) {//这里重写了LruCache的sizeOf方法,来计算每个图片资源所占用内存的字节数@Overrideprotected int sizeOf(String key, Drawable drawable) {if (drawable instanceof BitmapDrawable) {Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();if (bitmap != null) {//若是bitmap位图则直接计算它的大小return bitmap.getRowBytes() * bitmap.getHeight();}return 0;} else if (drawable instanceof AnimationDrawable) {//若是逐帧动画,则首先获取它所有的帧数,再计算总共的大小AnimationDrawable anim = (AnimationDrawable) drawable;int count = anim.getNumberOfFrames();int memSize = 0;for (int i = 0; i < count; i++) {Drawable dr = anim.getFrame(i);if (dr instanceof BitmapDrawable) {Bitmap bitmap = ((BitmapDrawable) dr).getBitmap();if (bitmap != null) {memSize += bitmap.getRowBytes() * bitmap.getHeight();}}}return memSize;}return 0;}};}//从缓存中获取图片public Drawable getImageFromMemCache(String key) {Drawable memDrawable = null;if (mMemoryCache != null) {//首先从强引用缓存中获取图片,若找到的话,把元素移动到CustomLruCache的最后面,从而保证它在LRU算法中最后被删除?//疑问,其实LinkedHashMap本身就存在LRU的算法机制,因此,get的时候,会自动移入到队列尾部memDrawable = mMemoryCache.remove(key);if (memDrawable != null) {memDrawable = memDrawable.getConstantState().newDrawable();mMemoryCache.put(key, memDrawable);return memDrawable;}}//强引用缓存中没有找到,开始在弱引用缓存中查找WeakReference<Drawable> ref = mRefCache.get(key);if (ref != null) {//若找到的话,这里是否添加一步,将其从弱引用缓存移入强引用缓存中比较好memDrawable = ref.get();if (memDrawable == null) {mRefCache.remove(key);}}return memDrawable;}//添加图片到缓存,这里不理解为什么要向强引用缓存和弱引用缓存都要添加一份public void addImageToCache(String data, Drawable drawable) {// Add to memory cacheif (mMemoryCache != null && mMemoryCache.get(data) == null) {mMemoryCache.put(data, drawable);mRefCache.put(data, new WeakReference<Drawable>(drawable));}}//从缓存中删除资源public void removeImageFromCache(String data) {if (mRefCache != null) {mRefCache.remove(data);}if (mMemoryCache != null) {mMemoryCache.remove(data);}}public Drawable getImageFromDiskCache(String pathName) {// TODO 暂不支持disk cachereturn null;}public void clearCaches() {// mDiskCache.clearCache();mMemoryCache.evictAll();mRefCache.clear();}
}   

整个缓存策略是使用弱引用缓存和强引用缓存配合使用,并结合LRUCache,在尽可能地利用缓存的基础上,也大大提高了缓存命中率。我个人觉得这个类有改进的地方,比如,当LRUCache在移除元素的时候,默认是直接删除掉。这里更好的方式是重写LRUCache的entryRemoved方法,使得强引用缓存满的时候,会根据LRU算法将最近最久没有被使用的图片自动移入弱引用缓存,如下:

 mMemoryCache = new CustomLruCache<String, Drawable>(memSize * 1024 * 1024) {//这里重写了LruCache的sizeOf方法,来计算每个图片资源所占用内存的字节数@Overrideprotected int sizeOf(String key, Drawable drawable) {.........}当强引用缓存满时,会自动调用这个方法,这时候,将移除的元素添加到弱引用缓存中@Overrideprotected void entryRemoved(boolean evicted, String key, Drawable oldDrawable, Drawable newDrawable) {if (oldDawable != null) {mRefCache.put(data, new WeakReference<Drawable>(oldDawable));}}};    

接下来看内存缓存类:ImageMemoryCache
public class ImageMemoryCache {private static final int SOFT_CACHE_SIZE = 15;  //软引用缓存容量private static LruCache mLruCache;  //硬引用缓存private static LinkedHashMap> mSoftCache;  //软引用缓存public ImageMemoryCache(Context context) {int memClass = ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();int cacheSize = 1024 * 1024 * memClass / 4;  //硬引用缓存容量,为系统可用内存的1/4mLruCache = new LruCache(cacheSize) {@Overrideprotected int sizeOf(String key, Bitmap value) {if (value != null)return value.getRowBytes() * value.getHeight();elsereturn 0;}@Overrideprotected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {if (oldValue != null)// 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存mSoftCache.put(key, new SoftReference(oldValue));}};mSoftCache = new LinkedHashMap>(SOFT_CACHE_SIZE, 0.75f, true) {private static final long serialVersionUID = 6040103833179403725L;@Overrideprotected boolean removeEldestEntry(Entry> 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 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();}
}
接下来看内存缓存类:ImageMemoryCache
public class ImageFileCache {private static final String CACHDIR = "ImgCach";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) {    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;}public void saveBitmap(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 dirFile = new File(dir);if (!dirFile.exists())dirFile.mkdirs();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 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());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);}private int freeSpaceOnSd() {StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;return (int) sdFreeMB;} private String convertUrlToFileName(String url) {String[] strs = url.split("/");return strs[strs.length - 1] + WHOLESALE_CONV;}private String getDirectory() {String dir = getSDPath() + "/" + CACHDIR;return dir;}private 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 "";}} private class FileLastModifSort implements Comparator {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;}}}}从网络获取图片:
public class ImageGetFromHttp {private static final String LOG_TAG = "ImageGetFromHttp";public static Bitmap downloadBitmap(String url) {final HttpClient client = new DefaultHttpClient();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(LOG_TAG, "Error " + statusCode + " while retrieving bitmap from " + url);return null;}final HttpEntity entity = response.getEntity();if (entity != null) {InputStream inputStream = null;try {inputStream = entity.getContent();FilterInputStream fit = new FlushedInputStream(inputStream);return BitmapFactory.decodeStream(fit);} finally {if (inputStream != null) {inputStream.close();inputStream = null;}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 {client.getConnectionManager().shutdown();}return null;}static class FlushedInputStream extends FilterInputStream {public FlushedInputStream(InputStream inputStream) {super(inputStream);}@Overridepublic 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;}}
}
最后,获取一张图片的流程就如下代码所示:public Bitmap getBitmap(String url) {// 从内存缓存中获取图片Bitmap result = memoryCache.getBitmapFromCache(url);if (result == null) {// 文件缓存中获取result = fileCache.getImage(url);if (result == null) {// 从网络获取result = ImageGetFromHttp.downloadBitmap(url);if (result != null) {fileCache.saveBitmap(result, url);memoryCache.addBitmapToCache(url, result);}} else {// 添加到内存缓存memoryCache.addBitmapToCache(url, result);}}return result;
}



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/295534.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql 非空语法_mysql从入门到优化(1)基本操作上

这是数据库系列的第一篇文章&#xff0c;主要是对mysql的基本操作有一个了解。本系列的教程会先从基础出发&#xff0c;逐步过渡到优化。一、前提在这里我们不会从如何去安装数据库开始讲起&#xff0c;而是在安装完之后从操作数据库开始&#xff0c;文中所有的代码均在我自己的…

“凡尔赛文学”疯狂刷屏!数学家们也拼命“装”了起来,哈哈哈哈哈

全世界只有3.14 % 的人关注了爆炸吧知识凡尔赛文学与数学结合起来完美无缺大家好&#xff0c;超模君昨天在写稿时&#xff0c;表妹过来告诉我&#xff1a;“表哥你的科普文章都out了&#xff01;现在凡尔赛文学才是主流&#xff01;”超模君很疑惑&#xff0c;凡尔赛文学的画风…

org.hibernate.InvalidMappingException: Could not parse mapping document from resource

在写hibernate时&#xff0c;若运行出现"org.hibernate.InvalidMappingException: Could not parse mapping document from resource"问题&#xff0c;首先确定jar包导入无误; 接下来看 *.hbm.xml文件中的字段&#xff1a; <!DOCTYPE hibernate-mapping PUBLIC &q…

.NET 6新特性试用 | 可空引用类型

前言在查看《隐式using指令》功能时&#xff0c;我们在csproj中发现这样一个属性&#xff1a;那么&#xff0c;Nullable到底是干嘛的&#xff1f;可为空上下文严格来说&#xff0c;这不是新特性&#xff0c;而是C# 8.0引入的特性之一。该特性用于指示引用类型是否接受null值:只…

zabbix JMX监控 tomcat

第一步&#xff1a;需要安装jdk1.# tar xvf jdk-7u21-linux-x64.tar.gz -C /usr/localource /etc/bashrc2.# ln -s /usr/local/jdk1.7.0_21 /usr/local/jdk3.# echo JAVA_HOME/usr/local/jdk >> /etc/bashrc4.# echo PATH$PATH:${JAVA_HOME}/bin/ >> /etc/bashrc5.…

Android之Base64

Base64介绍 Base64是一种基于64个可打印字符来表示二进制数据的表示方法,从本质上看Base64编码就是将三字节转四字节。如将字符串“Man”用Base64编码。 如果数据的长度不是3的整数倍

mysql 1054 42s22_MySQL ERROR 1054(42S22)

修改用户的密码&#xff0c;网上搜到的命令为如下执行后报错  ERROR 1054(42S22) Unknown column password in ‘field list’错误的原因是 5.7版本下的mysql数据库下已经没有password这个字段了&#xff0c;password字段改成了authentication_string所以请使用一下命令>my…

这是不是帮女朋友拍照时的你?哈哈哈哈

1 就跟我房东说&#xff1a;现在打工人压力真大一样▼2 原来&#xff0c;连打工人都不配了吗&#xff1f;&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 原来这才是家大叶大▼4 给女朋友拍照时的你&#xff01;&#xff08;via.刘一杭三三 &#xff09;▼5 当法…

linux slub分配器浅析

在《linux内存管理浅析》中提到内核管理自己使用的内存时&#xff0c;使用了SLAB对象池。SLAB确实是比较复杂&#xff0c;所以一直以来都没有深入看一看。不过现在&#xff0c;linux内核中&#xff0c;SLAB已经被它的简化版--SLUB所代替。最近抽时间看了一下SLUB的代码&#xf…

openfire 插件开发例子

2019独角兽企业重金招聘Python工程师标准>>> 好久都没有写东西了。今天总结一下之前开发的一些openfire插件。 这次的插件需要提供一个HTTP的接口。通过HTTP来对openfire做一些操作。 插件的目录结构&#xff1a;项目名称“exampleplugin" src/main/javaorg/ji…

WPF实现一个彩虹按钮

WPF开发者QQ群&#xff1a; 340500857 | 微信群 -> 进入公众号主页 加入组织玩玩彩虹文字&#xff0c;这次用 LinearGradientBrush 并且制作成按钮&#xff0c;虽然没技术含量反而有些实用&#xff0c;这就是返璞归真吗。首先来回忆下 LinearGradientBrush 的用法。LinearG…

设计模式的分类和六大设计原则

学习设计模式我是大学研究《java与模式这本书》1024页&#xff0c;很多没有看懂&#xff0c;并且没有总结起来&#xff0c;这次一定要把设计原则和设计模式总结清楚。 设计模式的分类 设计模式分为三大类&#xff1a;创建型模式&#xff0c;共五种&#xff1a;工厂方法模式、…

Java IO的RandomAccessFile的使用(转)

现有如下的一个需求&#xff0c;向已存在1G数据的txt文本里末尾追加一行文字&#xff0c;内容如下“Lucene是一款非常优秀的全文检索库”。可能大多数朋友会觉得这个需求很easy&#xff0c;说实话&#xff0c;确实easy&#xff0c;然后XXX君开始实现了&#xff0c;直接使用Java…

nvidia控制面板点了没反应win7_为什么没有nvidia控制面板_win7没有nvidia控制面板怎么找回-系统城...

2016-10-31 16:15:46  浏览量&#xff1a;30668如果电脑显卡出现问题会导致屏幕画面不清楚&#xff0c;这时候win7系统自带nvidia控制面板就派上用场了。它能够对显卡进行设置&#xff0c;提升显卡功能&#xff0c;但有用户说win7怎么没有nvidia控制面板&#xff1f;找很久也…

公交车座椅上有个洞,竟是为了…很多人都不知道

全世界只有3.14 % 的人关注了爆炸吧知识坐公交车的时候你有没有发现公交车的座椅上通常来说中间都会有个洞洞的大小基本上刚好够一个手指头穿过那么这个洞到底有什么用呢&#xff1f;小编特意问了一圈同事们的回答真的脑洞大开有的说洞口刚好可以穿过手指是不是乘客无聊的时候可…

C# 如何判断某个 tcp 端口是否被占用?

咨询区 Ali&#xff1a;在 C# 中使用 TcpClient 或者其他通用的方式建立的 Socket&#xff0c;请问我如何判断这个端口是否被占用&#xff1f;比如下面的代码&#xff1a;TcpClient c; //I want to check here if port is free. c new TcpClient(ip, port);回答区 jro&#xf…

C#正则表达式编程(四)转致周公

正则表达式提供了功能强大、灵活而又高效的方法来处理文本。正则表达式的全面模式匹配表示法使您可以快速分析大量文本以找到特定的字符模式&#xff1b;提取、编辑、替换或删除文本子字符串&#xff1b;或将提取的字符串添加到集合以生成报告。对于处理字符串&#xff08;例如…

Quartz 的SB问题 GetNextValidTimeAfter 输出和输出 时区 不同步,好傻的方法?

测试代码如下DateTime kk new DateTime(2012, 6, 4, 15, 0, 0);Quartz.CronExpression cron new Quartz.CronExpression("0 14 15 ? * *");var dt cron.GetNextValidTimeAfter(kk);好傻好伤。dt的时候是 {2012/6/5 7:14:00} 跑出了一个7点来了。正确的期待值应该…

Android之switch控件的用法

在做一个蓝牙开关时候,用到了switch,记一下用法,其实跟Button是几乎一样的. 布局中: <Switch android:id="@+id/open" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textOff="蓝牙关闭中&q…

pythonresponse对象的属性_Scrapy中response属性以及内容提取

PythonPython开发Python语言Scrapy中response属性以及内容提取一.属性url &#xff1a;HTTP响应的url地址,str类型status&#xff1a;HTTP响应的状态码, int类型headers &#xff1a;HTTP响应的头部, 类字典类型, 可以调用get或者getlist方法对其进行访问body&#xff1a;HTTP响…