android 网络图片双缓存

本文转自:http://blog.csdn.net/a79412906/article/details/10180583

Android每次加载图片很浪费时间。所以设计了一个图片缓存技术来解决每次android手机加载图片的问题

内存的读取速度是最快的,然后是文件的读取速度,最后是网络资源的读取

既然内存的读取时间最快,我们好好利用内存资源。将内存再分两层缓存

强引用缓存不会轻易被回收,来保存常用数据,不常用的资源放入软引用缓存中。

对于硬引用和软引用的介绍:

⑴强引用(StrongReference)
    强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

⑵软引用(SoftReference)

如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存

 1,对于强引用和软引用的使用,我们首先去强引用缓存中去找图片资源,当没有发现时,就去软引用缓存中。当强引用的超额时,将最后使用的资源放入软引用缓存中,使用到软引用的资源时 ,则将资源重新放回强引用缓存池中。

 2,内存缓存池中找不到,就去文件中查找,

 

3,再找不到就只好去网络上下载了,下载到以后,分别将资源放入文件缓存和内存缓存中

 

Android对于InputStream流有个小bug在慢速网络的情况下可能产生中断,可以考虑重写FilterInputStream处理
skip方法来解决这个bug。 BitmapFactory类的decodeStream方法在网络超时或较慢的时候无法获取完整的数据,这里我 们通
过继承FilterInputStream类的skip方法来强制实现flush流中的数据,主要原理就是检查是否到文件末端,告诉http类是否继续。

static class FlushedInputStream extends FilterInputStream {

public FlushedInputStream(InputStream inputStream) {

super(inputStream);

}

@Override

public 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;

}

}

主界面读取图片

[java] view plaincopy

  1. public class MainActivity extends Activity {
  2. private ImageMemoryCache memoryCache;
  3. private ImageFileCache fileCache;
  4. private ImageView imageView;
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.main);
  9. memoryCache=new ImageMemoryCache(this);
  10. fileCache=new ImageFileCache();
  11. imageView=(ImageView) findViewById(R.id.img);
  12. Bitmap b=getBitmap("http://f.hiphotos.baidu.com/album/w%3D2048/sign=7aa167f79f2f07085f052d00dd1cb999/472309f7905298228f794c7bd6ca7bcb0b46d4c4.jpg");
  13. imageView.setImageBitmap(b);
  14. }
  15. public Bitmap getBitmap(String url) {
  16. // 从内存缓存中获取图片
  17. Bitmap result = memoryCache.getBitmapFromCache(url);
  18. if (result == null) {
  19. // 文件缓存中获取
  20. result = fileCache.getImage(url);
  21. if (result == null) {
  22. // 从网络获取
  23. result = ImageGetFromHttp.downloadBitmap(url);
  24. if (result != null) {
  25. fileCache.saveBitmap(result, url);
  26. memoryCache.addBitmapToCache(url, result);
  27. }
  28. } else {
  29. // 添加到内存缓存
  30. memoryCache.addBitmapToCache(url, result);
  31. }
  32. }
  33. return result;
  34. }
  35. }

内存中读取

[java] view plaincopy

  1. public class ImageMemoryCache {
  2. /**
  3. * 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。
  4. * 硬引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。
  5. */
  6. private static final int SOFT_CACHE_SIZE = 15;  //软引用缓存容量
  7. private static LruCache<String, Bitmap> mLruCache;  //硬引用缓存
  8. private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache;  //软引用缓存
  9. public ImageMemoryCache(Context context) {
  10. int memClass = ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
  11. int cacheSize = 1024 * 1024 * memClass / 4;  //硬引用缓存容量,为系统可用内存的1/4
  12. mLruCache = new LruCache<String, Bitmap>(cacheSize) {
  13. @Override
  14. protected int sizeOf(String key, Bitmap value) {
  15. if (value != null)
  16. return value.getRowBytes() * value.getHeight();
  17. else
  18. return 0;
  19. }
  20. @Override
  21. protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
  22. if (oldValue != null)
  23. // 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存
  24. mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
  25. }
  26. };
  27. mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f, true) {
  28. private static final long serialVersionUID = 6040103833179403725L;
  29. @Override
  30. protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {
  31. if (size() > SOFT_CACHE_SIZE){
  32. return true;
  33. }
  34. return false;
  35. }
  36. };
  37. }
  38. /**
  39. * 从缓存中获取图片
  40. */
  41. public Bitmap getBitmapFromCache(String url) {
  42. Bitmap bitmap;
  43. //先从硬引用缓存中获取
  44. synchronized (mLruCache) {
  45. bitmap = mLruCache.get(url);
  46. if (bitmap != null) {
  47. //如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除
  48. mLruCache.remove(url);
  49. mLruCache.put(url, bitmap);
  50. return bitmap;
  51. }
  52. }
  53. //如果硬引用缓存中找不到,到软引用缓存中找
  54. synchronized (mSoftCache) {
  55. SoftReference<Bitmap> bitmapReference = mSoftCache.get(url);
  56. if (bitmapReference != null) {
  57. bitmap = bitmapReference.get();
  58. if (bitmap != null) {
  59. //将图片移回硬缓存
  60. mLruCache.put(url, bitmap);
  61. mSoftCache.remove(url);
  62. return bitmap;
  63. } else {
  64. mSoftCache.remove(url);
  65. }
  66. }
  67. }
  68. return null;
  69. }
  70. /**
  71. * 添加图片到缓存
  72. */
  73. public void addBitmapToCache(String url, Bitmap bitmap) {
  74. if (bitmap != null) {
  75. synchronized (mLruCache) {
  76. mLruCache.put(url, bitmap);
  77. }
  78. }
  79. }
  80. public void clearCache() {
  81. mSoftCache.clear();
  82. }
  83. }

[java] view plaincopy

  1. public class ImageFileCache {
  2. private static final String CACHDIR = "ImgCach";
  3. private static final String WHOLESALE_CONV = ".cach";
  4. private static final int MB = 1024*1024;
  5. private static final int CACHE_SIZE = 10;
  6. private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;
  7. public ImageFileCache() {
  8. //清理文件缓存
  9. removeCache(getDirectory());
  10. }
  11. /** 从缓存中获取图片 **/
  12. public Bitmap getImage(final String url) {
  13. final String path = getDirectory() + "/" + convertUrlToFileName(url);
  14. File file = new File(path);
  15. if (file.exists()) {
  16. Bitmap bmp = BitmapFactory.decodeFile(path);
  17. if (bmp == null) {
  18. file.delete();
  19. } else {
  20. updateFileTime(path);
  21. return bmp;
  22. }
  23. }
  24. return null;
  25. }
  26. /** 将图片存入文件缓存 **/
  27. public void saveBitmap(Bitmap bm, String url) {
  28. if (bm == null) {
  29. return;
  30. }
  31. //判断sdcard上的空间
  32. if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
  33. //SD空间不足
  34. return;
  35. }
  36. String filename = convertUrlToFileName(url);
  37. String dir = getDirectory();
  38. File dirFile = new File(dir);
  39. if (!dirFile.exists())
  40. dirFile.mkdirs();
  41. File file = new File(dir +"/" + filename);
  42. try {
  43. file.createNewFile();
  44. OutputStream outStream = new FileOutputStream(file);
  45. bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
  46. outStream.flush();
  47. outStream.close();
  48. } catch (FileNotFoundException e) {
  49. Log.w("ImageFileCache", "FileNotFoundException");
  50. } catch (IOException e) {
  51. Log.w("ImageFileCache", "IOException");
  52. }
  53. }
  54. /**
  55. * 计算存储目录下的文件大小,
  56. * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
  57. * 那么删除40%最近没有被使用的文件
  58. */
  59. private boolean removeCache(String dirPath) {
  60. File dir = new File(dirPath);
  61. File[] files = dir.listFiles();
  62. if (files == null) {
  63. return true;
  64. }
  65. if (!android.os.Environment.getExternalStorageState().equals(
  66. android.os.Environment.MEDIA_MOUNTED)) {
  67. return false;
  68. }
  69. int dirSize = 0;
  70. for (int i = 0; i < files.length; i++) {
  71. if (files[i].getName().contains(WHOLESALE_CONV)) {
  72. dirSize += files[i].length();
  73. }
  74. }
  75. if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
  76. int removeFactor = (int) ((0.4 * files.length) + 1);
  77. Arrays.sort(files, new FileLastModifSort());
  78. for (int i = 0; i < removeFactor; i++) {
  79. if (files[i].getName().contains(WHOLESALE_CONV)) {
  80. files[i].delete();
  81. }
  82. }
  83. }
  84. if (freeSpaceOnSd() <= CACHE_SIZE) {
  85. return false;
  86. }
  87. return true;
  88. }
  89. /** 修改文件的最后修改时间 **/
  90. public void updateFileTime(String path) {
  91. File file = new File(path);
  92. long newModifiedTime = System.currentTimeMillis();
  93. file.setLastModified(newModifiedTime);
  94. }
  95. /** 计算sdcard上的剩余空间 **/
  96. private int freeSpaceOnSd() {
  97. StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
  98. double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
  99. return (int) sdFreeMB;
  100. }
  101. /** 将url转成文件名 **/
  102. private String convertUrlToFileName(String url) {
  103. String[] strs = url.split("/");
  104. return strs[strs.length - 1] + WHOLESALE_CONV;
  105. }
  106. /** 获得缓存目录 **/
  107. private String getDirectory() {
  108. String dir = getSDPath() + "/" + CACHDIR;
  109. return dir;
  110. }
  111. /** 取SD卡路径 **/
  112. private String getSDPath() {
  113. File sdDir = null;
  114. boolean sdCardExist = Environment.getExternalStorageState().equals(
  115. android.os.Environment.MEDIA_MOUNTED);  //判断sd卡是否存在
  116. if (sdCardExist) {
  117. sdDir = Environment.getExternalStorageDirectory();  //获取根目录
  118. }
  119. if (sdDir != null) {
  120. return sdDir.toString();
  121. } else {
  122. return "";
  123. }
  124. }
  125. /**
  126. * 根据文件的最后修改时间进行排序
  127. */
  128. private class FileLastModifSort implements Comparator<File> {
  129. public int compare(File arg0, File arg1) {
  130. if (arg0.lastModified() > arg1.lastModified()) {
  131. return 1;
  132. } else if (arg0.lastModified() == arg1.lastModified()) {
  133. return 0;
  134. } else {
  135. return -1;
  136. }
  137. }
  138. }
  139. }

网络下载图片

[java] view plaincopy

  1. public class ImageGetFromHttp {
  2. private static final String LOG_TAG = "ImageGetFromHttp";
  3. public static Bitmap downloadBitmap(String url) {
  4. final HttpClient client = new DefaultHttpClient();
  5. final HttpGet getRequest = new HttpGet(url);
  6. try {
  7. HttpResponse response = client.execute(getRequest);
  8. final int statusCode = response.getStatusLine().getStatusCode();
  9. if (statusCode != HttpStatus.SC_OK) {
  10. Log.w(LOG_TAG, "Error " + statusCode + " while retrieving bitmap from " + url);
  11. return null;
  12. }
  13. final HttpEntity entity = response.getEntity();
  14. if (entity != null) {
  15. InputStream inputStream = null;
  16. try {
  17. inputStream = entity.getContent();
  18. FilterInputStream fit = new FlushedInputStream(inputStream);
  19. return BitmapFactory.decodeStream(fit);
  20. } finally {
  21. if (inputStream != null) {
  22. inputStream.close();
  23. inputStream = null;
  24. }
  25. entity.consumeContent();
  26. }
  27. }
  28. } catch (IOException e) {
  29. getRequest.abort();
  30. Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
  31. } catch (IllegalStateException e) {
  32. getRequest.abort();
  33. Log.w(LOG_TAG, "Incorrect URL: " + url);
  34. } catch (Exception e) {
  35. getRequest.abort();
  36. Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
  37. } finally {
  38. client.getConnectionManager().shutdown();
  39. }
  40. return null;
  41. }
  42. /*
  43. * An InputStream that skips the exact number of bytes provided, unless it reaches EOF.
  44. */
  45. static class FlushedInputStream extends FilterInputStream {
  46. public FlushedInputStream(InputStream inputStream) {
  47. super(inputStream);
  48. }
  49. @Override
  50. public long skip(long n) throws IOException {
  51. long totalBytesSkipped = 0L;
  52. while (totalBytesSkipped < n) {
  53. long bytesSkipped = in.skip(n - totalBytesSkipped);
  54. if (bytesSkipped == 0L) {
  55. int b = read();
  56. if (b < 0) {
  57. break;  // we reached EOF
  58. } else {
  59. bytesSkipped = 1; // we read one byte
  60. }
  61. }
  62. totalBytesSkipped += bytesSkipped;
  63. }
  64. return totalBytesSkipped;
  65. }
  66. }
  67. }

权限

[html] view plaincopy

    1. <uses-permission android:name="android.permission.INTERNET" />
    2. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

android 网络图片双缓存

时间: 2024-09-29 00:50:37

android 网络图片双缓存的相关文章

Android中用双缓存技术,加载网络图片

最近在学校参加一个比赛,写的一个Android应用,里面要加载大量的网络图片,可是用传统的方法图片一多就会造成程序出现内存溢出而崩溃.因为自己也在学习中,所以看了很多博客和视频,然后参照这些大神的写源码,自己写了一个加载网络图片工具类. 里面要用到一个经典的图片缓存库DiskLruCache 下载地址为:  DiskLruCache下载 下面是使用这个类实现的 双缓存网络图片加载 [java] view plain copy public class DiskLruCacheUtils { pr

android 使用LruCache缓存网络图片

加载图片,图片如果达到一定的上限,如果没有一种合理的机制对图片进行释放必然会引起程序的崩溃. 为了避免这种情况,我们可以使用Android中LruCache来缓存下载的图片,防止程序出现OOM. 打开activity_main.xml作为程序的主布局,加入如下代码: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.an

Android 框架修炼-自己封装双缓存管理框架库

一.概述 Android开发中,网络请求是很重要的一部分,而缓存网络请求来的图片或者响应结果字符串或者结果流,既可以省流量,同时也可以帮助我们 解决无网或弱网情况下加载情况,当然也可以提升程序性能效率.纵所周知,缓存管理中肯定需要用到内存缓存,这里我们采用LruCache来管理内存的缓存. LruCahce虽然速度快,但是只是内存级别的缓存,为了实现持久化的缓存,我们还需要文件级别的缓存,也就是说我们要把缓存保存到文件,而文件则是保存 到手机存储或者SD卡存储中,即实现Disk级别的缓存,这里我

android listview 异步加载图片并防止错位+双缓存

网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 convertView 不会出现错位现象, 重用 convertView 但没有异步操作也不会有问题. 我简单分析一下: 当重用 convertView 时,最初一屏显示 7 条记录, getView 被调用 7 次,创建了 7 个 convertView. 当 Item1 划出屏幕, Item8 进入屏幕时,这时没有为 Item8 创建新的 view 实例, Ite

Android性能优化之实现双缓存的图片异步加载工具(LruCache+SoftReference) - 拿来即用

之前在郭大神的博客看到使用LruCache算法实现图片缓存的.这里仿效他的思路,自己也写了一个. 并加入ConcurrentHashMap<String, SoftReference<Bitmap>>去实现二级缓存,因为ConcurrentHashMap是多个锁的线程安全,支持高并发.很适合这种频繁访问读取内存的操作. 下面整个思路是,使用了系统提供的LruCache类做一级缓存, 大小为运行内存的1/8,当LruCache容量要满的时候,会自动将系统移除的图片放到二级缓存中,但为

Android之ListView异步加载网络图片(优化缓存机制)【转】

网上关于这个方面的文章也不少,基本的思路是线程+缓存来解决.下面提出一些优化: 1.采用线程池 2.内存缓存+文件缓存 3.内存缓存中网上很多是采用SoftReference来防止堆溢出,这儿严格限制只能使用最大JVM内存的1/4 4.对下载的图片进行按比例缩放,以减少内存的消耗 具体的代码里面说明.先放上内存缓存类的代码MemoryCache.java: public class MemoryCache { private static final String TAG = "MemoryCa

Android图片管理组件(双缓存+异步加载)

转自:http://www.oschina.net/code/snippet_219356_18887?p=3#comments ImageManager2这个类具有异步从网络下载图片,从sd读取本地图片,内存缓存,硬盘缓存,图片使用动画渐现等功能,已经将其应用在包含大量图片的应用中一年多,没有出现oom Android程序常常会内存溢出,网上也有很多解决方案,如软引用,手动调用recycle等等.但经过我们实践发现这些方案,都没能起到很好的效果,我们的应用依然会出现很多oom,尤其我们的应用包

android下载网络图片并缓存

异步下载网络图片,并提供是否缓存至内存或外部文件的功能 异步加载类AsyncImageLoader public void downloadImage(final String url, final ImageCallback callback); public void downloadImage(final String url, final boolean cache2Memory, final ImageCallback callback); public void setCache2F

(BUG已修改,最优化)安卓ListView异步加载网络图片与缓存软引用图片,线程池,只加载当前屏之说明

原文:http://blog.csdn.net/java_jh/article/details/20068915 迟点出更新的.这个还有BUG.因为软引应不给力了.2.3之后 前几天的原文有一个线程管理与加载源过多,造成浪费流量的问题.下面对这进下改进的一些说明(红色为新加) 这两天一直在优化这个问题.google也很多种做法.但发现都是比较不全面. 比如: 一些只实现了异步加载,却没有线程池与软引用. 一些是用AsynTast的, 一些有了线程池但加载所有的图片,这样造成具大资源浪费 一些是用