Android之远程图片获取与本地缓存

来源:互联网 发布:淘宝上的装修公司 编辑:程序博客网 时间:2024/04/29 14:21

原文地址:http://blog.csdn.net/weidi1989/article/details/8477672

概述
对于客户端——服务器端应用,从远程获取图片算是经常要用的一个功能,而图片资源往往会消耗比较大的流量,对应用来说,如果处理不好这个问题,那会让用户很崩溃,不知不觉手机流量就用完了,等用户发现是你的应用消耗掉了他手机流量的话,那么可想而知你的应用将面临什么样的命运。
另外一个问题就是加载速度,如果应用中图片加载速度很慢的话,那么用户同样会等到崩溃。
那么如何处理好图片资源的获取和管理呢?
*异步下载
*本地缓存
异步下载:
大家都知道,在android应用中UI线程5秒没响应的话就会抛出无响应异常,对于远程获取大的资源来说,这种异常还是很容易就会抛出来的,那么怎么避免这种问题的产生。在android中提供两种方法来做这件事情:

启动一个新的线程来获取资源,完成后通过Handler机制发送消息,并在UI线程中处理消息,从而达到在异步线程中获取图片,然后通过Handler Message来更新UI线程的过程。
使用android中提供的AsyncTask来完成。

具体的做法这里就不介绍了,查下API就可以了,或者是google、baidu下。这里主要来说本地缓存。

本地缓存:
对于图片资源来说,你不可能让应用每次获取的时候都重新到远程去下载(ListView),这样会浪费资源,但是你又不能让所有图片资源都放到内存中去(虽然这样加载会比较快),因为图片资源往往会占用很大的内存空间,容易导致OOM。那么如果下载下来的图片保存到SDCard中,下次直接从SDCard上去获取呢?这也是一种做法,我看了下,还是有不少应用采用这种方式的。采用LRU等一些算法可以保证sdcard被占用的空间只有一小部分,这样既保证了图片的加载、节省了流量、又使SDCard的空间只占用了一小部分。另外一种做法是资源直接保存在内存中,然后设置过期时间和LRU规则。

sdcard保存:
在sdcard上开辟一定的空间,需要先判断sdcard上剩余空间是否足够,如果足够的话就可以开辟一些空间,比如10M
当需要获取图片时,就先从sdcard上的目录中去找,如果找到的话,使用该图片,并更新图片最后被使用的时间。如果找不到,通过URL去download
去服务器端下载图片,如果下载成功了,放入到sdcard上,并使用,如果失败了,应该有重试机制。比如3次。
下载成功后保存到sdcard上,需要先判断10M空间是否已经用完,如果没有用完就保存,如果空间不足就根据LRU规则删除一些最近没有被用户的资源。
关键代码:
保存图片到SD卡上:

[java] view plaincopyprint?
  1. private void saveBmpToSd(Bitmap bm, Stringurl) {   
  2.         if (bm == null) {   
  3.             Log.w(TAG, " trying to savenull bitmap");   
  4.             return;   
  5.         }   
  6.          //判断sdcard上的空间   
  7.         if (FREE_SD_SPACE_NEEDED_TO_CACHE >freeSpaceOnSd()) {   
  8.             Log.w(TAG, "Low free space onsd, do not cache");   
  9.             return;   
  10.         }   
  11.         String filename =convertUrlToFileName(url);   
  12.         String dir = getDirectory(filename);   
  13.         File file = new File(dir +"/" + filename);   
  14.         try {   
  15.             file.createNewFile();   
  16.             OutputStream outStream = newFileOutputStream(file);   
  17.            bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);   
  18.             outStream.flush();   
  19.             outStream.close();   
  20.             Log.i(TAG, "Image saved tosd");   
  21.         } catch (FileNotFoundException e) {   
  22.             Log.w(TAG,"FileNotFoundException");   
  23.         } catch (IOException e) {   
  24.             Log.w(TAG,"IOException");   
  25.         }   
  26.     }   


计算sdcard上的空间:

[java] view plaincopyprint?
  1. /**
  2. * 计算sdcard上的剩余空间
  3. * @return
  4. */   
  5. private int freeSpaceOnSd() {   
  6.     StatFs stat = newStatFs(Environment.getExternalStorageDirectory() .getPath());   
  7.     double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;   
  8.     return (int) sdFreeMB;   
  9. }   


修改文件的最后修改时间 :

[java] view plaincopyprint?
  1. /**
  2. * 修改文件的最后修改时间
  3. * @param dir
  4. * @param fileName
  5. */   
  6. private void updateFileTime(String dir,String fileName) {   
  7.     File file = new File(dir,fileName);          
  8.     long newModifiedTime =System.currentTimeMillis();   
  9.     file.setLastModified(newModifiedTime);   
  10. }   


本地缓存优化:

[java] view plaincopyprint?
  1. /**
  2. *计算存储目录下的文件大小,当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
  3. * 那么删除40%最近没有被使用的文件
  4. * @param dirPath
  5. * @param filename
  6. */   
  7. private void removeCache(String dirPath) {   
  8.     File dir = new File(dirPath);   
  9.     File[] files = dir.listFiles();   
  10.     if (files == null) {   
  11.         return;   
  12.     }   
  13.     int dirSize = 0;   
  14.     for (int i =0; i < files.length;i++) {   
  15.         if(files[i].getName().contains(WHOLESALE_CONV)) {   
  16.             dirSize += files[i].length();   
  17.         }   
  18.     }   
  19.     if (dirSize > CACHE_SIZE * MB ||FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {   
  20.         int removeFactor = (int) ((0.4 *files.length) +1);   
  21.    
  22.         Arrays.sort(files, newFileLastModifSort());   
  23.    
  24.         Log.i(TAG, "Clear some expiredcache files ");   
  25.    
  26.         for (int i =0; i <removeFactor; i++) {   
  27.    
  28.             if(files[i].getName().contains(WHOLESALE_CONV)) {   
  29.    
  30.                 files[i].delete();                
  31.    
  32.             }   
  33.    
  34.         }   
  35.    
  36.     }   
  37.    
  38. }   
  39. /**
  40. * 删除过期文件
  41. * @param dirPath
  42. * @param filename
  43. */   
  44. private void removeExpiredCache(StringdirPath, String filename) {   
  45.    
  46.     File file = new File(dirPath,filename);   
  47.    
  48.     if (System.currentTimeMillis() -file.lastModified() > mTimeDiff) {   
  49.    
  50.         Log.i(TAG, "Clear some expiredcache files ");   
  51.    
  52.         file.delete();   
  53.    
  54.     }   
  55.    
  56. }   


文件使用时间排序:

[java] view plaincopyprint?
  1. /**
  2. * TODO 根据文件的最后修改时间进行排序 *
  3. */   
  4. classFileLastModifSort implements Comparator<File>{   
  5.     public int compare(File arg0, File arg1) {   
  6.         if (arg0.lastModified() >arg1.lastModified()) {   
  7.             return 1;   
  8.         } else if (arg0.lastModified() ==arg1.lastModified()) {   
  9.             return 0;   
  10.         } else {   
  11.             return -1;   
  12.         }   
  13.     }   
  14. }   


内存保存:
在内存中保存的话,只能保存一定的量,而不能一直往里面放,需要设置数据的过期时间、LRU等算法。这里有一个方法是把常用的数据放到一个缓存中(A),不常用的放到另外一个缓存中(B)。当要获取数据时先从A中去获取,如果A中不存在那么再去B中获取。B中的数据主要是A中LRU出来的数据,这里的内存回收主要针对B内存,从而保持A中的数据可以有效的被命中。

先定义A缓存:

[java] view plaincopyprint?
  1. private final HashMap<String, Bitmap>mHardBitmapCache =new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY/2, 0.75f,true) {   
  2.         @Override   
  3.         protected booleanremoveEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) {   
  4.             if (size() >HARD_CACHE_CAPACITY) {   
  5.                //当map的size大于30时,把最近不常用的key放到mSoftBitmapCache中,从而保证mHardBitmapCache的效率  
  6.                mSoftBitmapCache.put(eldest.getKey(), newSoftReference<Bitmap>(eldest.getValue()));   
  7.                 return true;   
  8.             } else   
  9.                 return false;   
  10.         }   
  11.     };   


再定于B缓存:

[java] view plaincopyprint?
  1. /**
  2.   *当mHardBitmapCache的key大于30的时候,会根据LRU算法把最近没有被使用的key放入到这个缓存中。
  3.   *Bitmap使用了SoftReference,当内存空间不足时,此cache中的bitmap会被垃圾回收掉
  4.   */   
  5. private final staticConcurrentHashMap<String, SoftReference<Bitmap>> mSoftBitmapCache =new ConcurrentHashMap<String,SoftReference<Bitmap>>(HARD_CACHE_CAPACITY / 2);   


从缓存中获取数据:

[java] view plaincopyprint?
  1. /**
  2.      * 从缓存中获取图片
  3.      */   
  4.     private Bitmap getBitmapFromCache(Stringurl) {   
  5.         // 先从mHardBitmapCache缓存中获取   
  6.         synchronized (mHardBitmapCache) {   
  7.             final Bitmap bitmap =mHardBitmapCache.get(url);   
  8.             if (bitmap != null) {   
  9.                 //如果找到的话,把元素移到linkedhashmap的最前面,从而保证在LRU算法中是最后被删除  
  10.                 mHardBitmapCache.remove(url);   
  11.                 mHardBitmapCache.put(url,bitmap);   
  12.                 return bitmap;   
  13.             }   
  14.         }   
  15.         //如果mHardBitmapCache中找不到,到mSoftBitmapCache中找  
  16.         SoftReference<Bitmap>bitmapReference = mSoftBitmapCache.get(url);   
  17.         if (bitmapReference !=null) {   
  18.             final Bitmap bitmap =bitmapReference.get();   
  19.             if (bitmap !=null) {   
  20.                 return bitmap;   
  21.             } else {   
  22.                 mSoftBitmapCache.remove(url);   
  23.             }   
  24.         }   
  25.         return null;   
  26.     }   


如果缓存中不存在,那么就只能去服务器端去下载:

[java] view plaincopyprint?
  1. /**
  2.   * 异步下载图片
  3.   */   
  4. class ImageDownloaderTask extendsAsyncTask<String, Void, Bitmap> {   
  5.      private staticfinal int IO_BUFFER_SIZE=4 * 1024;   
  6.      private String url;   
  7.      private finalWeakReference<ImageView> imageViewReference;   
  8.      public ImageDownloaderTask(ImageViewimageView) {   
  9.          imageViewReference = newWeakReference<ImageView>(imageView);   
  10.      }   
  11.    
  12.     @Override   
  13.      protected BitmapdoInBackground(String... params) {   
  14.          final AndroidHttpClient client =AndroidHttpClient.newInstance("Android");   
  15.          url = params[0];   
  16.          final HttpGet getRequest = newHttpGet(url);   
  17.          try {   
  18.              HttpResponse response =client.execute(getRequest);   
  19.              final int statusCode =response.getStatusLine().getStatusCode();   
  20.              if (statusCode !=HttpStatus.SC_OK) {   
  21.                  Log.w(TAG, "从" +url +"中下载图片时出错!,错误码:" + statusCode);   
  22.                  return null;   
  23.              }   
  24.              final HttpEntity entity =response.getEntity();   
  25.              if (entity !=null) {   
  26.                  InputStream inputStream =null;   
  27.                  OutputStream outputStream =null;   
  28.                  try {   
  29.                      inputStream =entity.getContent();   
  30.                      finalByteArrayOutputStream dataStream = new ByteArrayOutputStream();   
  31.                      outputStream = newBufferedOutputStream(dataStream, IO_BUFFER_SIZE);   
  32.                      copy(inputStream,outputStream);   
  33.                      outputStream.flush();   
  34.                      final byte[] data =dataStream.toByteArray();   
  35.                      final Bitmap bitmap =BitmapFactory.decodeByteArray(data,0, data.length);   
  36.                      return bitmap;   
  37.                  } finally {   
  38.                      if (inputStream !=null) {   
  39.                         inputStream.close();   
  40.                      }   
  41.                      if (outputStream !=null) {   
  42.                         outputStream.close();   
  43.                      }   
  44.                     entity.consumeContent();   
  45.                  }   
  46.              }   
  47.          } catch (IOException e) {   
  48.              getRequest.abort();   
  49.              Log.w(TAG, "I/O errorwhile retrieving bitmap from " + url, e);   
  50.          } catch (IllegalStateException e) {   
  51.              getRequest.abort();   
  52.              Log.w(TAG, "Incorrect URL:" + url);   
  53.          } catch (Exception e) {   
  54.              getRequest.abort();   
  55.              Log.w(TAG, "Error whileretrieving bitmap from " + url, e);   
  56.          } finally {   
  57.              if (client !=null) {   
  58.                  client.close();   
  59.              }   
  60.          }   
  61.          return null;   
  62.      }   


这是两种做法,还有一些应用在下载的时候使用了线程池和消息队列MQ,对于图片下载的效率要更好一些。有兴趣的同学可以看下。

总结:
对于远程图片等相对比较大的资源一定要在异步线程中去获取本地做缓存

 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 42天宝宝4天没有大便怎么办 42天的宝宝3天没大便怎么办 6个月宝宝前额头碰青了怎么办 三个月的婴儿喝牛奶拉绿大便怎么办 已经喂了猫一个多月的高钙奶怎么办 几个月宝宝胳膊起来红豆豆怎么办 一岁半宝宝只喝牛奶不吃饭怎么办 儿子一生下来脖子上有淋巴结怎么办 满月宝宝睡觉不踏实易惊醒怎么办 两个月宝宝从婴儿车上掉下来怎么办 兔子不吃兔粮不喝水不拉粑粑怎么办 兔子吃了带水的菜叶怎么办 七个月的宝宝晚上睡觉总醒怎么办 紫薯和番茄一起吃了怎么办 1当半宝宝喝温开水后打隔怎么办 婴儿屁股拉便便肛门有点烂了怎么办 婴儿便便之后肛门就红怎么办 50天的宝宝三天不拉大便怎么办 两个月宝宝不拉屎只放屁怎么办 两个月大宝宝两天没拉屎怎么办 两个月宝宝三天没拉大便怎么办 5个月宝宝3天没拉大便怎么办 4个月宝宝3天没拉大便怎么办 20个月的宝宝大便干燥怎么办 20个月宝宝大便间隔三天怎么办 两个多月的宝宝四天没大便怎么办 两个多月的宝宝几天没大便怎么办 2个月3天没大便怎么办 两个月大的宝宝发烧40度怎么办 两个月大的宝宝感冒了怎么办 四个月宝宝拉水样大便要怎么办 六个月的宝宝咳嗽有痰怎么办 未满月的宝宝大便脓状怎么办 五个月的宝宝总是吃手怎么办 小孩子学数字怎么也学不会怎么办 小孩学数字老是学不会该怎么办 只买了大人票忘买儿童的了怎么办 铝合金滑动门没轨道安纱门怎么办 移门衣柜门与柜体有冶缝隙怎么办 推拉门关门时撞门框声音大怎么办 两岁宝宝夏天不盖被子怎么办