图片缓存(源于SDK文档)

来源:互联网 发布:java高性能并发框架 编辑:程序博客网 时间:2024/05/29 13:04

From: http://johnsonxu.iteye.com/blog/1930787

接着之前的《一种异步加载资源的方法(源于SDK文档)》,SDK文档在《Caching Bitmaps》中介绍了内存缓存与磁盘缓存的使用。

大家都知道,现在的手机屏幕分辨率是越来越大了,虽然之前我们介绍了异步加载图片的方法。但要知道,一个应用可用的内存是有限的。我们不可能将所有的内存都用来存储图片,也不可能为了内存而每次取图片时都上网下载(流量费是很贵滴,而且下载也很耗电啊)。

因此,对于已下载的图片,我们需要在本地维持一个缓存。

内存缓存

LurCache是一个内存缓存类(Android3.1引入,通过v4的支援包,可以在API Level 4以上使用)。它使用一个强连接的LinkedHashMap,将使用频率最高的图片缓存在内存中。

PS:在这之前,最流行的缓存方式是使用SoftReference与WeakReference。但从Android2.3开始,垃圾回收对于软引用与弱引用来说,变得越来越积极了。这也就造成了软引用与弱引用的效率变得很低(没几下就被回收了,然后又得再创建,和没缓存没太大区别)。同时,在Android3.0之前,Bitmap的数据是储存在所谓的native内存区中(可以想象成是用C语言申请的内存,很难被垃圾回收自动释放)。这也造成了应用非常容易发生内存溢出。

当然,要想使用LurCache,我们需要给定一个缓存大小。而要想确定缓存占多少内存,需要考虑以下条件:

  • 应用的其他地方对内存的占用有多紧张?
  • 有多少图片会同时在屏幕上显示?在它们显示时,要确保多少的其余图片可以马上显示而无明显延迟?
  • 屏幕大小与密度如何?xhdpi的机子明显要比hdpi的机子需要更多的缓存。
  • 每张图片大概有多大?
  • 图片访问的频率是多少?有没有部分图片的访问频率远高于其他图片?如果是的话,你可能需要将这些图片进行缓存,甚至需要多个LurCache对象来缓存不同等级的图片。
  • 你可以平衡质量与数量吗?有的时候,存储大量低分辨率的图片更有用。你可以在后台任务中加载高分辨率的。

没有绝对合适的大小或计算方法,你必须根据自身应用的特点来确定相应的解决方案。缓存太小,反而会由于频繁的重新创建而降低效率。缓存太大,自然也同样会造成内存溢出了。

以下是一个使用LurCache的例子,使用八分之一的可用内存作为缓存。在一个普通的hdpi的机子上,大概是4MB以上。

在一个800x480的屏幕上的全屏GridView显示的图片,大概需要1.5MB (800*480*4 bytes)的内存。也就是说,这个例子里的缓存,可以保存至少2.5屏的图片。

Java代码  收藏代码
  1. private LruCache<String, Bitmap> mMemoryCache;  
  2.   
  3. @Override  
  4. protected void onCreate(Bundle savedInstanceState) {  
  5.     ...  
  6.     // 获取可用内存的最大值,超过这个数,就会产生OutOfMemory.  
  7.     // 将其以KB为单位存储.  
  8.     final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);  
  9.     // 使用八分之一的可用内存作为缓存.  
  10.     final int cacheSize = maxMemory / 8;  
  11.     mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {  
  12.         @Override  
  13.         protected int sizeOf(String key, Bitmap bitmap) {  
  14.             // 缓存大小以KB为单位进行计算.  
  15.             return bitmap.getByteCount() / 1024;  
  16.         }  
  17.     };  
  18.     ...  
  19. }  
  20.   
  21. public void addBitmapToMemoryCache(String key, Bitmap bitmap) {  
  22.     if (getBitmapFromMemCache(key) == null) {  
  23.         mMemoryCache.put(key, bitmap);  
  24.     }  
  25. }  
  26.   
  27. public Bitmap getBitmapFromMemCache(String key) {  
  28.     return mMemoryCache.get(key);  
  29. }  

接着《一种异步加载资源的方法(源于SDK文档)》的例子,在加载图片时,先去缓存里查一下。如果缓存里有,直接设上去就行了。

Java代码  收藏代码
  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也需要更新一下,当图片获取到后,将其加入缓存。

Java代码  收藏代码
  1. class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {     
  2.     ...     
  3.     // Decode image in background.  
  4.     @Override  
  5.     protected Bitmap doInBackground(Integer... params) {  
  6.         final Bitmap bitmap = decodeSampledBitmapFromResource(  
  7.                 getResources(), params[0], 100100);  
  8.         addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);  
  9.         return bitmap;  
  10.     }  
  11.     ...  
  12. }  

 磁盘缓存

虽然内存缓存很有用,但光靠它还是不够的。像一些专门看图片的应用,里面的照片多了去了。很容易缓存就满了。或者当应用在后台被杀死时,内存缓存也会立刻清空。你还是得重新建立。

在这种情况下,就需要磁盘缓存来将图片保存到本地。这样,当内存缓存被清空时,可以通过磁盘缓存加快图片的加载。

以下的例子就是使用源码中提供的DiskLurCache来完成磁盘缓存的功能。

它并不是替代内存缓存,而是在内存缓存之外再额外备份了一次。

Java代码  收藏代码
  1. private DiskLruCache mDiskLruCache;  
  2. private final Object mDiskCacheLock = new Object();  
  3. private boolean mDiskCacheStarting = true;  
  4. private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10// 10MB  
  5. private static final String DISK_CACHE_SUBDIR = "thumbnails";  
  6.   
  7. @Override  
  8. protected void onCreate(Bundle savedInstanceState) {  
  9.     ...  
  10.     // Initialize memory cache  
  11.   
  12.     ...  
  13.     // Initialize disk cache on background thread  
  14.     File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);  
  15.     new InitDiskCacheTask().execute(cacheDir);  
  16.     ...  
  17. }  
  18.   
  19. class InitDiskCacheTask extends AsyncTask<File, Void, Void> {  
  20.     @Override  
  21.     protected Void doInBackground(File... params) {  
  22.         synchronized (mDiskCacheLock) {  
  23.             File cacheDir = params[0];  
  24.             mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);  
  25.             mDiskCacheStarting = false// Finished initialization  
  26.             mDiskCacheLock.notifyAll(); // Wake any waiting threads  
  27.         }  
  28.         return null;  
  29.     }  
  30. }  
  31.   
  32. class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {  
  33.     ...  
  34.     // Decode image in background.  
  35.     @Override  
  36.     protected Bitmap doInBackground(Integer... params) {  
  37.         final String imageKey = String.valueOf(params[0]);  
  38.         // Check disk cache in background thread  
  39.         Bitmap bitmap = getBitmapFromDiskCache(imageKey);  
  40.         if (bitmap == null) {  
  41.             // Not found in disk cache  
  42.             // Process as normal  
  43.             final Bitmap bitmap = decodeSampledBitmapFromResource(  
  44.                     getResources(), params[0], 100100);  
  45.         }  
  46.         // Add final bitmap to caches  
  47.         addBitmapToCache(imageKey, bitmap);  
  48.         return bitmap;  
  49.     }  
  50.     ...  
  51. }  
  52.   
  53. public void addBitmapToCache(String key, Bitmap bitmap) {  
  54.     // Add to memory cache as before  
  55.     if (getBitmapFromMemCache(key) == null) {  
  56.         mMemoryCache.put(key, bitmap);  
  57.     }  
  58.     // Also add to disk cache  
  59.     synchronized (mDiskCacheLock) {  
  60.         if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {  
  61.             mDiskLruCache.put(key, bitmap);  
  62.         }  
  63.     }  
  64. }  
  65.   
  66. public Bitmap getBitmapFromDiskCache(String key) {  
  67.     synchronized (mDiskCacheLock) {  
  68.         // Wait while disk cache is started from background thread  
  69.         while (mDiskCacheStarting) {  
  70.             try {  
  71.                 mDiskCacheLock.wait();  
  72.             } catch (InterruptedException e) {  
  73.             }  
  74.         }  
  75.         if (mDiskLruCache != null) {  
  76.             return mDiskLruCache.get(key);  
  77.         }  
  78.     }  
  79.     return null;  
  80. }  
  81.   
  82. // Creates a unique subdirectory of the designated app cache directory.  
  83. // Tries to use external  
  84. // but if not mounted, falls back on internal storage.  
  85. public static File getDiskCacheDir(Context context, String uniqueName) {  
  86.     // Check if media is mounted or storage is built-in, if so, try and use  
  87.     // external cache dir  
  88.     // otherwise use internal cache dir  
  89.   
  90.     final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment  
  91.             .getExternalStorageState()) || !isExternalStorageRemovable() ? getExternalCacheDir(  
  92.             context).getPath() : context.getCacheDir().getPath();  
  93.     return new File(cachePath + File.separator + uniqueName);  
  94. }  

 

PS:即使是初始化磁盘缓存,也需要相应的磁盘操作。因此,使用了一个异步任务InitDiskCacheTask来进行。另外,为了防止在磁盘缓存建立成功前就去访问,在getBitmapFromDiskCache方法中,进行了wait操作。当磁盘缓存初始化后,如果提前访问了,相应的线程将被notifyAll唤醒。

处理Configuration Change

在程序运行时,我们经常会遇到Configuration Change,像横竖屏切换、语言改变等等。

而这些情况,往往会使得当前运行的Activity被销毁并重新创建。

为了防止在销毁与创建过程中重新建立缓存(耗时太久且影响效率,要是能直接保存就好了),我们可以通过Fragment。只要setRetainInstance(true)就行了。

如下图所示,在Activity的onCreate里,先判断相应的Fragment在不在FragmentManager里,要是在的话,直接获取相应的缓存对象。并且在Fragment的onCreate中setRetainInstance(true)。

Java代码  收藏代码
  1. private LruCache<String, Bitmap> mMemoryCache;  
  2.   
  3. @Override  
  4. protected void onCreate(Bundle savedInstanceState) {  
  5.     ...  
  6.     RetainFragment mRetainFragment =  
  7.             RetainFragment.findOrCreateRetainFragment(getFragmentManager());  
  8.     mMemoryCache = RetainFragment.mRetainedCache;  
  9.     if (mMemoryCache == null) {  
  10.         mMemoryCache = new LruCache<String, Bitmap>(cacheSize);  
  11.         ... // Initialize cache here as usual  
  12.   
  13.         mRetainFragment.mRetainedCache = mMemoryCache;  
  14.     }  
  15.     ...  
  16. }  
  17.   
  18. class RetainFragment extends Fragment {  
  19.     private static final String TAG = "RetainFragment";  
  20.     public LruCache<String, Bitmap> mRetainedCache;  
  21.   
  22.     public RetainFragment() {  
  23.     }  
  24.   
  25.     public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {  
  26.         RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);  
  27.         if (fragment == null) {  
  28.             fragment = new RetainFragment();  
  29.         }  
  30.         return fragment;  
  31.     }  
  32.   
  33.     @Override  
  34.     public void onCreate(Bundle savedInstanceState) {  
  35.         super.onCreate(savedInstanceState);  
  36.         setRetainInstance(true);  
  37.     }  
  38. }  

 


0 0
原创粉丝点击