Android远程图片获取和本地缓存

对于客户端——服务器端应用,从远程获取图片算是经常要用的一个功能,而图片资源往往会消耗比较大的流量,对 应用来说,如果处理不好这个问题,那会让用户很崩溃,不知不觉手机流量就用完了,等用户发现是你的应用消耗掉了他手机流量的话,那么可想而知你的应用将面 临什么样的命运。

AD:2014WOT全球软件技术峰会北京站 课程视频发布

另外一个问题就是加载速度,如果应用中图片加载速度很慢的话,那么用户同样会等到崩溃。

那么如何处理好图片资源的获取和管理呢?

异步下载

本地缓存

异步下载

大家都知道,在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卡上

  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上的空间:

  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. }

修改文件的最后修改时间

  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. }

本地缓存优化

  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. Arrays.sort(files, newFileLastModifSort());
  22. Log.i(TAG, "Clear some expiredcache files ");
  23. for (int i = 0; i <removeFactor; i++) {
  24. if(files[i].getName().contains(WHOLESALE_CONV)) {
  25. files[i].delete();
  26. }
  27. }
  28. }
  29. }
  30. /**
  31. * 删除过期文件
  32. * @param dirPath
  33. * @param filename
  34. */
  35. private void removeExpiredCache(StringdirPath, String filename) {
  36. File file = new File(dirPath,filename);
  37. if (System.currentTimeMillis() -file.lastModified() > mTimeDiff) {
  38. Log.i(TAG, "Clear some expiredcache files ");
  39. file.delete();
  40. }
  41. }

文件使用时间排序

  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缓存:

  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缓存:

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

从缓存中获取数据:

  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. }

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

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

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

总结

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

时间: 2024-10-11 21:23:27

Android远程图片获取和本地缓存的相关文章

ASP.NET下载远程图片保存到本地的方法、保存抓取远程图片

ASP.NET下载远程图片保存到本地的方法.保存抓取远程图片 2012-05-16 11:25:51     我来说两句      收藏    我要投稿 以下介绍两种方法:1.利用WebRequest,WebResponse 类WebRequest wreq=WebRequest.Create("http://up.2cto.com/2012/0516/20120516112717995.gif");    HttpWebResponse wresp=(HttpWebResponse)

获取远程图片保存到本地文件夹中

1 /** 2 * 抓取远程图片到本地,可以抓取不带有后缀的图片 3 * @author YanYing <[email protected]> 4 * @link bidianer.com 5 */ 6 class GrabImage{ 7 8 /** 9 * @var string 需要抓取的远程图片的地址 10 * 例如:http://www.bidianer.com/img/icon_mugs.jpg 11 * 有一些远程文件路径可能不带拓展名 12 * 形如:http://www.x

Android进阶图片处理之三级缓存方案

图片的三级缓存 一.概述 一開始在学习Android的时候.处理图片的时候,每次获取图片都是直接从网络上面载入图片. 可是在开发项目的过程中,每次点击进入app里面,图片都要慢慢的再一次从网络上面载入. 给用户的体验很不好,第一个等待的时间很令人dan 疼 第二个给用户的流量造成了不必要的浪费 因此提出图片的三级缓存策略, 所谓的三级缓存:就是在手机载入图片的时候, 1.首先从内存中载入, 2.假设内存中没有的话,从sd卡上获取.读取到之后将图片写入到内存中 3.假设sd卡上没有的话,从网络上获

Android 平滑图片加载和缓存库 Glide 使用详解

在图片加载库烂大街的今天,选择一个适合自己使用的图片加载库已经成为了每一个Android开发者的必经之路.现在市面上知名的图片加载库有UIL,Picasso,Volley ImageLoader,Fresco以及我们今天的主角Glide.它们各有千秋,不能评定谁一定比谁好,只能说哪一个更适合你. 我的理解 下面我来谈一下个人对这些图片加载库的理解,如有错误,还望指教. Universal Image Loader:一个强大的图片加载库,包含各种各样的配置,最老牌,使用也最广泛. Picasso:

PHP下载/采集远程图片到本地

/** * 下载远程图片到本地 * * @param string $url 远程文件地址 * @param string $filename 保存后的文件名(为空时则为随机生成的文件名,否则为原文件名) * @param array $fileType 允许的文件类型 * @param string $dirName 文件保存的路径(路径其余部分根据时间系统自动生成) * @param int $type 远程获取文件的方式 * @return json 返回文件名.文件的保存路径 * @au

php 保存编辑器内容中的远程图片为本地图片

有时,我们的编辑器复制过来的图片是远程的,需要下载到本地服务器上. 1.提取远程图片 /* *功能:php完美实现下载远程图片保存到本地 *参数:文件url,保存文件目录,保存文件名称,使用的下载方式 *当保存文件名称为空时则使用远程文件原来的名称 */ private function getImage($url,$filename='',$type=0){ if(trim($url)==''){ return array('file_name'=>'','save_path'=>'','e

【代码审计】EasySNS_V1.6远程图片本地化导致Getshell

0x00 环境准备 EasySNS官网:http://www.imzaker.com 网站源码版本:EasySNS极简社区V1.60 程序源码下载:http://es.imzaker.com/index.php/Topic/gview/id/92.html 默认后台地址:http://127.0.0.1/admin.php/Login/login.html 默认账号密码:admin/admin 测试网站首页: 0x01 代码分析 ? 1.漏洞文件位置:  /app/common.func.php

Android 获取并显示远程图片 Picasso框架的使用(一)

转载请注明出处:明桑Android 在Android开发中,常需要从远程获取图片并显示在客户端,当然我们可以使用原生HttpUrlConnection和AsyncTask等操作来完成,但并不推荐,因为这样不仅需要我们编写大量的代码,还需要处理缓存和下载管理等,最好自己封装成库或者采用第三方库: Picasso:A Powerful Image Downloading and Caching Library for Android 根据名字就知道它是跟什么相关了(Picasso:毕加索)它的基本操

内容和图片在从网络上获取到之后都会存入到本地缓存中

内容和图片在从网络上获取到之后都会存入到本地缓存中,因此即使手机在没有网络的情况下依然能够加载出以前浏览过的新闻.而使用的缓存技术不用多说,自然是DiskLruCache了,那么首先第一个问题,这些数据都被缓存在了手机的什么位置呢? 其实DiskLruCache并没有限制数据的缓存位置,可以自由地进行设定,但是通常情况下多数应用程序都会将缓存的位置选择为 /sdcard/Android/data/<application package>/cache 这个路径.选择在这个位置有两点好处:第一,