Android 图片缓存、加载器

来源:互联网 发布:淘宝哪家多肉土好 编辑:程序博客网 时间:2024/05/21 08:52

前言

关于缓存的文章网络上有很多,本文旨在提供一份优秀缓存机制的同时,使大家理解其运行原理,供各位交流、进一步提升。


一、代码结构及缓存基本原理


二、图片加载器ImageWorker

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.   * 根据url加载一个图片到ImageView上,未加载成功的时候显示加载图片<loadingImageId> 
  3.   *  
  4.   * @param url 图片网络地址. 
  5.   * @param imageView 图片View 
  6.   * @param loadingImageId 加载图片资源ID 
  7.   * @Description: 
  8.   * @Author Justlcw 
  9.   * @Date 2014-3-6 
  10.   */  
  11.  public void loadImage(String url, ImageView imageView, int loadingImageId)  
  12.  {  
  13.      //如果图片网络地址为空,直接返回.  
  14.      if(TextUtils.isEmpty(url))  
  15.      {  
  16.          return;  
  17.      }  
  18.      Bitmap bitmap = null;  
  19.      if (mImageCache != null)  
  20.      {  
  21.          //先从内存缓存中取.  
  22.          bitmap = mImageCache.getBitmapFromMemCache(url);  
  23.      }  
  24.      if (bitmap != null)  
  25.      {  
  26.          //如果取到了,直接显示.  
  27.          imageView.setImageBitmap(bitmap);  
  28.      }  
  29.      else if (cancelPotentialWork(url, imageView))  
  30.      {  
  31.          //如果没有取到,取消之前相同的加载线程,取消成功,就启动一个新的加载线程.  
  32.          final BitmapWorkerTask task = new BitmapWorkerTask(imageView);  
  33.          final AsyncDrawable asyncDrawable =   
  34.                  new AsyncDrawable(mContext.getResources(), getLoadingImage(loadingImageId), task);  
  35.          imageView.setImageDrawable(asyncDrawable);  
  36.          task.execute(url);  
  37.      }  
  38.  }  

如图,ImageWorker里面含有一个ImageCache(图片缓存),当开始加载一张网络图片的时候,首先从ImageCache(图片缓存)的内存缓存中获取,如果获取到了直接显示,否则启动一个后台线程加载图片。

1,看下BitmapWorkerTask在后台做了什么事

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. protected Bitmap doInBackground(String... params)  
  3. {  
  4.     url = params[0];  
  5.     Bitmap bitmap = null;  
  6.       
  7.     // 1.如果图片缓存不为null.  
  8.     // 2.如果当前线程没有被取消.  
  9.     // 3.如果被弱引用的ImageView没有被销毁.  
  10.     // 4.如果加载程序无须退出.  
  11.     if (mImageCache != null && !isCancelled() && getAttachedImageView() != null && !mExitTasksEarly)  
  12.     {  
  13.         //从硬盘缓存中去.  
  14.         bitmap = mImageCache.getBitmapFromDiskCache(url);  
  15.     }  
  16.   
  17.     // 1.如果从硬盘缓存中获取的图片为Null.  
  18.     // 2.如果当前线程没有被取消.  
  19.     // 3.如果被弱引用的ImageView没有被销毁.  
  20.     // 4.如果加载程序无须退出.  
  21.     if (bitmap == null && !isCancelled() && getAttachedImageView() != null && !mExitTasksEarly)  
  22.     {  
  23.         //后台获取图片<网络获取>  
  24.         bitmap = processBitmap(params[0]);  
  25.     }  
  26.   
  27.     // 1.如果加载过后的图片不为null.  
  28.     // 2.如果图片缓存不为null.  
  29.     if (bitmap != null && mImageCache != null)  
  30.     {  
  31.         //添加到缓存中.  
  32.         mImageCache.addBitmapToCache(url, bitmap);  
  33.     }  
  34.     return bitmap;  
  35. }  
先忽视if判断,执行语句表明:后台线程先是从ImageCache(图片缓存)的硬盘缓存中获取图片,如果获取不到再从网络获取;当获取成功后,又将图片放入ImageCache(图片缓存)中,这刚好验证了上面缓存的基本原理。

2,cancelPotentialWork(url, imageView)这个方法的作用

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.   * 取消指定url的图片加载线程. 
  3.   * @param url 图片网络地址 
  4.   * @param imageView 图片View 
  5.   * @return 如果有相同的线程在进行,返回false,否则返回 true. 
  6.   * @Description: 
  7.   * @Author Justlcw 
  8.   * @Date 2014-3-6 
  9.   */  
  10.  public static boolean cancelPotentialWork(String url, ImageView imageView)  
  11.  {  
  12.      //从ImageView中获取加载线程.  
  13.      final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);  
  14.      if (bitmapWorkerTask != null)  
  15.      {  
  16.          final String taskUrl = bitmapWorkerTask.url;  
  17.          if (TextUtils.isEmpty(taskUrl) || !taskUrl.equals(url))  
  18.          {  
  19.              //如果加载的url为空,获得加载的url不等指定的url,则取消当前的加载线程.  
  20.              bitmapWorkerTask.cancel(true);  
  21.              LogUtil.d(TAG, "cancel load : " + taskUrl);  
  22.          }  
  23.          else  
  24.          {  
  25.              // 如果加载的url等于指定的url,则说明不需要重新加载,返回false.  
  26.              return false;  
  27.          }  
  28.      }  
  29.      return true;  
  30.  }  
回过头看下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的构造

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.   * 打开一个硬盘缓存. 
  3.   * @Description: 
  4.   * @Author Justlcw 
  5.   * @Date 2014-3-5 
  6.   */  
  7.  public final static DiskLruCache openCache(Context context, String cacheName, long maxByteSize)  
  8.  {  
  9.      File cacheDir = CacheUtils.getEnabledCacheDir(context, cacheName);  
  10.      if (cacheDir.isDirectory() && cacheDir.canWrite() && CacheUtils.getUsableSpace(cacheDir) > maxByteSize)  
  11.      {  
  12.          return new DiskLruCache(cacheDir, maxByteSize);  
  13.      }  
  14.      return null;  
  15.  }  

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.   * 获得可使用的<缓存主目录>(先外部,后外部) 
  3.   * @Description: 
  4.   * @Author Justlcw 
  5.   * @Date 2014-3-6 
  6.   */  
  7.  public static File getEnabledCacheDir(Context context, String cacheName)  
  8.  {  
  9.      String cachePath;  
  10.      if(isExternalStorageRWable())  
  11.      {  
  12.          cachePath = Environment.getExternalStorageDirectory().getPath();  
  13.      }  
  14.      else  
  15.      {  
  16.          cachePath = context.getCacheDir().getPath();  
  17.      }  
  18.      File cacheFile = new File(cachePath + CacheConfig.DISK_CACHE_NAME + cacheName);  
  19.      //如果缓存目录不存在,创建缓存目录.  
  20.      if (!cacheFile.exists())  
  21.      {  
  22.          cacheFile.mkdirs();  
  23.      }  
  24.      return cacheFile;  
  25.  }  

如果外部存储(SD卡)可以使用,DiskLruCache则在外部存储中创建一个指定缓存名称的文件夹;如果不可用,则使用内存存储并创建指定文件夹。

同时还会对文件夹的可用空间进行判断,如果以上条件都允许,则返回一个DiskLruCache。

2,DiskLruCache的存入


[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.   * 把一个字节数组以文件存放在缓存中. 
  3.   * @param key 关键字 
  4.   * @param inputStream 字节流 
  5.   * @return 缓存文件路径. 
  6.   * @Description: 
  7.   * @Author Justlcw 
  8.   * @Date 2014-3-5 
  9.   */  
  10.  public final String put(String key, InputStream inputStream)  
  11.  {  
  12.      BufferedInputStream bufferedInputStream = null;  
  13.      OutputStream bufferOps= null;  
  14.      try  
  15.      {  
  16.          bufferedInputStream = new BufferedInputStream(inputStream);  
  17.          String filePath = createFilePath(key);  
  18.          bufferOps = new BufferedOutputStream(new FileOutputStream(filePath));  
  19.   
  20.          byte[] b = new byte[CacheConfig.IO_BUFFER_SIZE];  
  21.          int count;  
  22.          while ((count = bufferedInputStream.read(b)) > 0)  
  23.          {  
  24.              bufferOps.write(b, 0, count);  
  25.          }  
  26.          bufferOps.flush();  
  27.          LogUtil.d(TAG, "put success : " + key);  
  28.          onPutSuccess(key, filePath);  
  29.          flushCache();  
  30.          return filePath;  
  31.      }  
  32.      catch (IOException e)  
  33.      {  
  34.          LogUtil.d(TAG, "store failed to store: " + key, e);  
  35.      }  
  36.      finally  
  37.      {  
  38.          try  
  39.          {  
  40.              if(bufferOps != null)  
  41.              {  
  42.                  bufferOps.close();  
  43.              }  
  44.              if(bufferedInputStream != null)  
  45.              {  
  46.                  bufferedInputStream.close();  
  47.              }  
  48.              if(inputStream != null)  
  49.              {  
  50.                  inputStream.close();  
  51.              }  
  52.          }  
  53.          catch (IOException e)  
  54.          {  
  55.              LogUtil.d(TAG, "close stream error : "+e.getMessage());  
  56.          }  
  57.      }  
  58.      return "";  
  59.  }  

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.   * 放入一个文件. 
  3.   * @param key 关键字 
  4.   * @param filePath 文件路径 
  5.   */  
  6.  protected final void onPutSuccess(String key, String filePath)  
  7.  {  
  8.      mLinkedHashMap.put(key, filePath);  
  9.      cacheNumSize = mLinkedHashMap.size();  
  10.      cacheByteSize += new File(filePath).length();  
  11.  }  

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.   * 缓存大小溢出处理. 
  3.   * @Description: 
  4.   * @Author Justlcw 
  5.   * @Date 2014-3-5 
  6.   */  
  7.  protected final void flushCache()  
  8.  {  
  9.      Entry<String, String> eldestEntry;  
  10.      File eldestFile;  
  11.      long eldestFileSize;  
  12.      int count = 0;  
  13.      //超过最大缓存文件个数   or  超过最大空间大小    移除不常用的文件,并且一次最多只能移除4个.  
  14.      while (count < MAX_REMOVALS && (cacheNumSize > maxCacheNumSize || cacheByteSize > maxCacheByteSize))  
  15.      {  
  16.          eldestEntry = mLinkedHashMap.entrySet().iterator().next();  
  17.          eldestFile = new File(eldestEntry.getValue());  
  18.          eldestFileSize = eldestFile.length();  
  19.          mLinkedHashMap.remove(eldestEntry.getKey());  
  20.          eldestFile.delete();  
  21.          cacheNumSize = mLinkedHashMap.size();  
  22.          cacheByteSize -= eldestFileSize;  
  23.          count++;  
  24.          LogUtil.d(TAG, "flushCache - Removed :" + eldestFile.getAbsolutePath()+ ", " + eldestFileSize);  
  25.      }  
  26.  }  

当文件存入成功的时候,做了两步操作:

1.将文件的路径放入到当前使用的缓存文件列表中,并且更新缓存文件总共的数量及大小。

2.判断当前缓存文件有没有超出上限限制 (数量限制   和    大小限制)


3,DiskLruCache的取出



[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.   * 根据key返回缓存文件. 
  3.   * @param key key 
  4.   * @Description: 
  5.   * @Author Justlcw 
  6.   * @Date 2014-3-6 
  7.   */  
  8.  public final File get(String key)  
  9.  {  
  10.      if(containsKey(key))  
  11.      {  
  12.          final File file = new File(createFilePath(key));  
  13.          if(file.exists())  
  14.          {  
  15.              return file;  
  16.          }  
  17.      }  
  18.      return null;  
  19.  }  
  20.   
  21.  /** 
  22.   * 缓存中是否有key的数据保存 
  23.   * @param key 关键字 
  24.   * @Description: 
  25.   * @Author Justlcw 
  26.   * @Date 2014-3-5 
  27.   */  
  28.  public final boolean containsKey(String key)  
  29.  {  
  30.      //是否在map中.  
  31.      if (mLinkedHashMap.containsKey(key))  
  32.      {  
  33.          return true;  
  34.      }  
  35.      //如果不在map中,看是或否在文件夹中.  
  36.      final String existingFile = createFilePath(key);  
  37.      if (new File(existingFile).exists())  
  38.      {  
  39.          //如果存在,放入map后期直接提取.  
  40.          onPutSuccess(key, existingFile);  
  41.          return true;  
  42.      }  
  43.      return false;  
  44.  }  
根据key得到相应的文件路径,如果这个路径在当前使用的缓存中,直接返回这个文件;如果不在判断是否在原来的缓存文件夹中,如果在就是取出来放入当前使用的缓存列表中,否表示不在,则去下载。


四、图片加载器的使用

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. public View getView(int position, View convertView, ViewGroup parent)  
  3. {  
  4.     if(convertView == null)  
  5.     {  
  6.         convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_grid, parent, false);  
  7.     }  
  8.     ImageView imageView = (ImageView)convertView.findViewById(R.id.imageView);  
  9.     mImageLoader.loadImage(imgURLArray[position], imageView, R.drawable.bg_loading);  
  10.       
  11.     return convertView;  
  12. }  

在activity中放一个gridview,然后设置一个adapter,adapter使用图片加载的代码如上。

图片的内存缓存大小使用的是应用分得内存大小的1/8,所有的配置都在CacheConfig类里面配置。


实际效果及缓存文件夹如下:


                 


不足之处,望指点,望体谅。 附: 源码链接

转自:http://blog.csdn.net/justlcw/article/details/20708487

0 0
原创粉丝点击