两种缓存Bitmap的方式

30 阅读5分钟
  • 图片被访问的频率如何?是其中一些比另外的访问更加频繁吗?如果是,也许你想要保存那些最常访问的到内存中,或者为不同组别的位图(按访问频率分组)设置多个[LruCache]( ) 对象。

  • 你可以平衡质量与数量吗? 某些时候保存大量低质量的位图会非常有用,在另外一个后台任务中加载更高质量的图片。

  • 没有指定的大小与公式能够适用与所有的程序,那取决于分析你的使用情况后提出一个合适的解决方案。一个太小的Cache会导致额外的花销却没有明显的好处,一个太大的Cache同样会导致java.lang.OutOfMemory的异常[Cache占用太多内存,其他活动则会因为内存不够而异常],并且使得你的程序只留下小部分的内存用来工作。

  • 下面是一个为bitmap建立[LruCache]( ) 的示例:

  1. private LruCache mMemoryCache;  

  2. @Override  

  3. protected void onCreate(Bundle savedInstanceState) {  

  4.     ...  

  5.     // Get memory class of this device, exceeding this amount will throw an  

  6.     // OutOfMemory exception.  

  7.     final int memClass = ((ActivityManager) context.getSystemService(  

  8.             Context.ACTIVITY_SERVICE)).getMemoryClass();  

  9.     // Use 1/8th of the available memory for this memory cache.  

  10.     final int cacheSize = 1024 * 1024 * memClass / 8;  

  11.     mMemoryCache = new LruCache(cacheSize) {  

  12.         @Override  

  13.         protected int sizeOf(String key, Bitmap bitmap) {  

  14.             // The cache size will be measured in bytes rather than number of items.  

  15.             return bitmap.getByteCount();  

  16.         }  

  17.     };  

  18.     ...  

  19. }  

  20. public void addBitmapToMemoryCache(String key, Bitmap bitmap) {  

  21.     if (getBitmapFromMemCache(key) == null) {  

  22.         mMemoryCache.put(key, bitmap);  

  23.     }  

  24. }  

  25. public Bitmap getBitmapFromMemCache(String key) {  

  26.     return mMemoryCache.get(key);  

  27. }  

    • Note:  在上面的例子中, 有1/8的程序内存被作为Cache. 在一个常见的设备上(hdpi),最小大概有4MB (32/8). 一个全屏的 [GridView]( ) 组件,如果被800x480像素的图片填满大概会花费1.5MB (800*480*4 bytes), 因此这大概最少可以缓存2.5张图片到内存中.
  • 当加载位图到 [ImageView]( ) 时,[LruCache]( ) 会先被检查是否存在这张图片。如果找到有,它会被用来立即更新 [ImageView]( ) 组件,否则一个后台线程则被触发去处理这张图片。

  1. public void loadBitmap(int resId, ImageView imageView) {  

  2.     final String imageKey = String.valueOf(resId);  

  3.     final Bitmap bitmap = getBitmapFromMemCache(imageKey);  

  4.     if (bitmap != null) {  

  5.         mImageView.setImageBitmap(bitmap);  

  6.     } else {  

  7.         mImageView.setImageResource(R.drawable.image_placeholder);  

  8.         BitmapWorkerTask task = new BitmapWorkerTask(mImageView);  

  9.         task.execute(resId);  

  10.     }  

  11. }  

  • 上面的程序中 [BitmapWorkerTask]( ) 也需要做添加到内存Cache中的动作:
  1. class BitmapWorkerTask extends AsyncTask {  

  2.     ...  

  3.     // Decode image in background.  

  4.     @Override  

  5.     protected Bitmap doInBackground(Integer... params) {  

  6.         final Bitmap bitmap = decodeSampledBitmapFromResource(  

  7.                 getResources(), params[0], 100, 100));  

  8.         addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);  

  9.         return bitmap;  

  10.     }  

  11.     ...  

  12. }  

Use a Disk Cache [使用磁盘缓存]


  • 内存缓存能够提高访问最近查看过的位图,但是你不能保证这个图片会在Cache中。像类似 [GridView]( ) 等带有大量数据的组件很容易就填满内存Cache。你的程序可能会被类似Phone call等任务而中断,这样后台程序可能会被杀死,那么内存缓存就会被销毁。一旦用户恢复前面的状态,你的程序就又需要为每个图片重新处理。

  • 磁盘缓存磁盘缓存可以用来保存那些已经处理好的位图,并且在那些图片在内存缓存中不可用时减少加载的次数。当然从磁盘读取图片会比从内存要慢,而且读取操作需要在后台线程中处理,因为磁盘读取操作是不可预期的。

  • Note:  如果图片被更频繁的访问到,也许使用 [ContentProvider]( ) 会更加的合适,比如在Gallery程序中。

  • 在下面的sample code中实现了一个基本的 DiskLruCache 。然而,Android 4.0 的源代码提供了一个更加robust并且推荐使用的DiskLruCache 方案。(libcore/luni/src/main/java/libcore/io/DiskLruCache.java). 因为向后兼容,所以在前面发布的Android版本中也可以直接使用。 (quick search 提供了一个实现这个解决方案的示例)。

  1. private DiskLruCache mDiskCache;  

  2. private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB  

  3. private static final String DISK_CACHE_SUBDIR = "thumbnails";  

  4. @Override  

  5. protected void onCreate(Bundle savedInstanceState) {  

  6.     ...  

  7.     // Initialize memory cache  

  8.     ...  

  9.     File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR);  

  10.     mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE);  

  11.     ...  

  12. }  

  13. class BitmapWorkerTask extends AsyncTask {  

  14.     ...  

  15.     // Decode image in background.  

  16.     @Override  

  17.     protected Bitmap doInBackground(Integer... params) {  

  18.         final String imageKey = String.valueOf(params[0]);  

  19.         // Check disk cache in background thread  

  20.         Bitmap bitmap = getBitmapFromDiskCache(imageKey);  

  21.         if (bitmap == null) { // Not found in disk cache  

  22.             // Process as normal  

  23.             final Bitmap bitmap = decodeSampledBitmapFromResource(  

  24.                     getResources(), params[0], 100, 100));  

  25.         }  

  26.         // Add final bitmap to caches  

  27.         addBitmapToCache(String.valueOf(imageKey, bitmap);  

  28.         return bitmap;  

  29.     }  

  30.     ...  

  31. }  

  32. public void addBitmapToCache(String key, Bitmap bitmap) {  

  33.     // Add to memory cache as before  

  34.     if (getBitmapFromMemCache(key) == null) {  

  35.         mMemoryCache.put(key, bitmap);  

  36.     }  

  37.     // Also add to disk cache  

  38.     if (!mDiskCache.containsKey(key)) {  

  39.         mDiskCache.put(key, bitmap);  

  40.     }  

  41. }  

  42. public Bitmap getBitmapFromDiskCache(String key) {  

  43.     return mDiskCache.get(key);  

  44. }  

  45. // Creates a unique subdirectory of the designated app cache directory. Tries to use external  

  46. // but if not mounted, falls back on internal storage.  

  47. public static File getCacheDir(Context context, String uniqueName) {  

  48.     // Check if media is mounted or storage is built-in, if so, try and use external cache dir  

  49.     // otherwise use internal cache dir  

  50.     final String cachePath = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED  

  51.             || !Environment.isExternalStorageRemovable() ?  

  52.                     context.getExternalCacheDir().getPath() : context.getCacheDir().getPath();  

  53.     return new File(cachePath + File.separator + uniqueName);  

  54. }  

  • 内存缓存的检查是可以在UI线程中进行的,磁盘缓存的检查需要在后台线程中处理。磁盘操作永远都不应该在UI线程中发生。当图片处理完成后,最后的位图需要添加到内存缓存与磁盘缓存中,方便之后的使用。

Handle Configuration Changes [处理配置改变]