android 图片缓

大家都知道,现在的手机屏幕分辨率是越来越大了,虽然之前我们介绍了异步加载图片的方法。但要知道,一个应用可用的内存是有限的。我们不可能将所有的内存都用来存储图片,也不可能为了内存而每次取图片时都上网下载(流量费是很贵滴,而且下载也很耗电啊)。
因此,对于已下载的图片,我们需要在本地维持一个缓存。
内存缓存
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 mMemoryCache;
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. ...
  5. // 获取可用内存的最大值,超过这个数,就会产生OutOfMemory.
  6. // 将其以KB为单位存储.
  7. final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
  8. // 使用八分之一的可用内存作为缓存.
  9. final int cacheSize = maxMemory / 8;
  10. mMemoryCache = new LruCache(cacheSize) {
  11. @Override
  12. protected int sizeOf(String key, Bitmap bitmap) {
  13. // 缓存大小以KB为单位进行计算.
  14. return bitmap.getByteCount() / 1024;
  15. }
  16. };
  17. ...
  18. }
  19. public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
  20. if (getBitmapFromMemCache(key) == null) {
  21. mMemoryCache.put(key, bitmap);
  22. }
  23. }
  24. public Bitmap getBitmapFromMemCache(String key) {
  25. return mMemoryCache.get(key);
  26. }
复制代码
在加载图片时,先去缓存里查一下。如果缓存里有,直接设上去就行了。
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 {
  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. }
复制代码
磁盘缓存
虽然内存缓存很有用,但光靠它还是不够的。像一些专门看图片的应用,里面的照片多了去了。很容易缓存就满了。或者当应用在后台被杀死时,内存缓存也会立刻清空。你还是得重新建立。
在这种情况下,就需要磁盘缓存来将图片保存到本地。这样,当内存缓存被清空时,可以通过磁盘缓存加快图片的加载。
以下的例子就是使用源码中提供的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. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8. ...
  9. // Initialize memory cache
  10. ...
  11. // Initialize disk cache on background thread
  12. File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
  13. new InitDiskCacheTask().execute(cacheDir);
  14. ...
  15. }
  16. class InitDiskCacheTask extends AsyncTask {
  17. @Override
  18. protected Void doInBackground(File... params) {
  19. synchronized (mDiskCacheLock) {
  20. File cacheDir = params[0];
  21. mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
  22. mDiskCacheStarting = false; // Finished initialization
  23. mDiskCacheLock.notifyAll(); // Wake any waiting threads
  24. }
  25. return null;
  26. }
  27. }
  28. class BitmapWorkerTask extends AsyncTask {
  29. ...
  30. // Decode image in background.
  31. @Override
  32. protected Bitmap doInBackground(Integer... params) {
  33. final String imageKey = String.valueOf(params[0]);
  34. // Check disk cache in background thread
  35. Bitmap bitmap = getBitmapFromDiskCache(imageKey);
  36. if (bitmap == null) {
  37. // Not found in disk cache
  38. // Process as normal
  39. final Bitmap bitmap = decodeSampledBitmapFromResource(
  40. getResources(), params[0], 100, 100);
  41. }
  42. // Add final bitmap to caches
  43. addBitmapToCache(imageKey, bitmap);
  44. return bitmap;
  45. }
  46. ...
  47. }
  48. public void addBitmapToCache(String key, Bitmap bitmap) {
  49. // Add to memory cache as before
  50. if (getBitmapFromMemCache(key) == null) {
  51. mMemoryCache.put(key, bitmap);
  52. }
  53. // Also add to disk cache
  54. synchronized (mDiskCacheLock) {
  55. if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
  56. mDiskLruCache.put(key, bitmap);
  57. }
  58. }
  59. }
  60. public Bitmap getBitmapFromDiskCache(String key) {
  61. synchronized (mDiskCacheLock) {
  62. // Wait while disk cache is started from background thread
  63. while (mDiskCacheStarting) {
  64. try {
  65. mDiskCacheLock.wait();
  66. } catch (InterruptedException e) {
  67. }
  68. }
  69. if (mDiskLruCache != null) {
  70. return mDiskLruCache.get(key);
  71. }
  72. }
  73. return null;
  74. }
  75. // Creates a unique subdirectory of the designated app cache directory.
  76. // Tries to use external
  77. // but if not mounted, falls back on internal storage.
  78. public static File getDiskCacheDir(Context context, String uniqueName) {
  79. // Check if media is mounted or storage is built-in, if so, try and use
  80. // external cache dir
  81. // otherwise use internal cache dir
  82. final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment
  83. .getExternalStorageState()) || !isExternalStorageRemovable() ? getExternalCacheDir(
  84. context).getPath() : context.getCacheDir().getPath();
  85. return new File(cachePath + File.separator + uniqueName);
  86. }
复制代码
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 mMemoryCache;
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. ...
  5. RetainFragment mRetainFragment =
  6. RetainFragment.findOrCreateRetainFragment(getFragmentManager());
  7. mMemoryCache = RetainFragment.mRetainedCache;
  8. if (mMemoryCache == null) {
  9. mMemoryCache = new LruCache(cacheSize);
  10. ... // Initialize cache here as usual
  11. mRetainFragment.mRetainedCache = mMemoryCache;
  12. }
  13. ...
  14. }
  15. class RetainFragment extends Fragment {
  16. private static final String TAG = "RetainFragment";
  17. public LruCache mRetainedCache;
  18. public RetainFragment() {
  19. }
  20. public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
  21. RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
  22. if (fragment == null) {
  23. fragment = new RetainFragment();
  24. }
  25. return fragment;
  26. }
  27. @Override
  28. public void onCreate(Bundle savedInstanceState) {
  29. super.onCreate(savedInstanceState);
  30. setRetainInstance(true);
  31. }
  32. }
复制代码


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值