Android 图片缓存机制的深入理解

Android 图片缓存机制的深入理解

Android加载一张图片到用户界面是很简单的,但是当一次加载多张图片时,情况就变得复杂起来。很多情况下(像ListView、GridView或ViewPager等组件),屏幕上已显示的图片和即将滑动到当前屏幕上的图片数量基本上是没有限制的。

这些组件通过重用已经移除屏幕的子视图来将降低内存的使用,垃圾回收器也会及时释放那些已经不再使用的已下载的图片,这些都是很好的方法,但是为了保持一个流畅的、快速加载的用户界面,就应该避免当再次回到某个页面时而重新处理图片。内存缓存和磁盘缓存可以帮我们做到这些,它们允许组件快速地重新加载已处理好的图片。

使用内存缓存

内存缓存允许快速地访问图片,但它以占用App宝贵的内存为代价。LruCache类(API Level 4的Support Library也支持)特别适合来做图片缓存,它使用一个强引用的LinkedHashMap来保存最近使用的对象,并且会在缓存数量超出预设的大小之前移除最近最少使用的对象。

说明:以前流行的内存缓存方案是使用软引用或弱引用来缓存图片,然而现在不推荐这样做了,因为从android 2.3(API Level 9)起,垃圾收集器更倾向于先回收软引用或弱引用,这样就使它们变得低效。另外在Android 3.0(API Level 11)之前,图片的像素数据是存储在本地内存(native memory)中的,它以一种不可预测的方式释放,因此可能会导致App超过内存限制甚至崩溃。

为了给LruCache设置一个合适的大小,以下是应该考虑的一些因素:

1.你的Activity或App的可用内存是多少?

2.一次展示到屏幕上的图片是多少?有多少图片需要预先准备好以便随时加载到屏幕?

3.设备的屏幕尺寸和密度是多少?像Galaxy Nexus这样的高分辨率(xhdpi)设备比Nexus S这样分辨率(hdpi)的设备在缓存相同数量的图片时需要更大的缓存空间。

4.图片的尺寸和配置是怎样的?每张图片会占用多少内存?

5.图片的访问频率如何?是否有一些图片比另一些访问更加频繁?如果这样的话,或许可以将某些图片一直保存在内存里或者针对不同的图片分组设置不同的LruCache对象。

6.你能否平衡图片质量和数量之间的关系?有时候存储更多低质量的图片更加有用,当在需要的时候,再通过后台任务下载高质量的图片。

这里没有一个具体的大小和计算公式适用于所有的App,你需要分析你的使用情况并得到一个合适的方案。当一个缓存太小时会导致无益的额外的开销,而缓存太大时也可能会引起Java.lang.OutOfMemory异常,另外缓存越大,留给App其他部分的内存相应就越小。

这里是一个为图片设置LruCache的示例:

private LruCache<String, Bitmap> mMemoryCache; 

@Override
protected void onCreate(Bundle savedInstanceState) {
  ...
  // Get max available VM memory, exceeding this amount will throw an
  // OutOfMemory exception. Stored in kilobytes as LruCache takes an
  // int in its constructor.
  final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 

  // Use 1/8th of the available memory for this memory cache.
  final int cacheSize = maxMemory / 8; 

  mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
      // The cache size will be measured in kilobytes rather than
      // number of items.
      return bitmap.getByteCount() / 1024;
    }
  };
  ...
} 

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
  if (getBitmapFromMemCache(key) == null) {
    mMemoryCache.put(key, bitmap);
  }
} 

public Bitmap getBitmapFromMemCache(String key) {
  return mMemoryCache.get(key);
}

说明:在上述例子中,我们分配了应用内存的1/8作为缓存大小,在一个normal/hdpi的设备上最少也有4MB(32/8)的大小。一个800*480分辨率的屏幕上的一个填满图片的GridView大概占用1.5MB(800*480*4byte)的内存,因此该Cache至少可以缓存2.5页这样的图片。

当加载一张图片到ImageView时,首先检查LruCache,如果找到图片,就直接用来更新ImageView,如果没找到就开启一个后台线程来处理:

public void loadBitmap(int resId, ImageView imageView) {
  final String imageKey = String.valueOf(resId); 

  final Bitmap bitmap = getBitmapFromMemCache(imageKey);
  if (bitmap != null) {
    mImageView.setImageBitmap(bitmap);
  } else {
    mImageView.setImageResource(R.drawable.image_placeholder);
    BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
    task.execute(resId);
  }
}

上述线程中,在解码图片之后,也需要把它添加到内存缓存中:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
  ...
  // Decode image in background.
  @Override
  protected Bitmap doInBackground(Integer... params) {
    final Bitmap bitmap = decodeSampledBitmapFromResource(
        getResources(), params[0], 100, 100));
    addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
    return bitmap;
  }
  ...
}

使用磁盘缓存

虽然内存缓存在快速访问最近使用的图片时是很有用的,但是你无法保证你所需要的图片就在缓存中,类似GridView这样展示大量数据的组件可以很轻易地就占满内存缓存。你的App也可能被类似电话这样的任务打断,当App被切换到后台后也可能被杀死,内存缓存也可能被销毁,一旦用户回到之前的界面,你的App依然要重新处理每个图片。

磁盘缓存可以用来辅助存储处理过的图片,当内存缓存中图片不可用时,可以从磁盘缓存中查找,从而减少加载次数。当然,从磁盘读取图片要比从内存读取慢并且读取时间是不可预期的,因此需要使用后台线程来读取。

说明:ContentProvider 可能是一个合适的存储频繁访问的图片的地方,比如在Image Gallery应用中。

这里的示例代码是从Android源代码中剥离出来的DiskLruCache,以下是更新后的实例代码,在内存缓存的基础上增加了磁盘缓存:

private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails"; 

@Override
protected void onCreate(Bundle savedInstanceState) {
  ...
  // Initialize memory cache
  ...
  // Initialize disk cache on background thread
  File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
  new InitDiskCacheTask().execute(cacheDir);
  ...
} 

class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
  @Override
  protected Void doInBackground(File... params) {
    synchronized (mDiskCacheLock) {
      File cacheDir = params[0];
      mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
      mDiskCacheStarting = false; // Finished initialization
      mDiskCacheLock.notifyAll(); // Wake any waiting threads
    }
    return null;
  }
} 

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
  ...
  // Decode image in background.
  @Override
  protected Bitmap doInBackground(Integer... params) {
    final String imageKey = String.valueOf(params[0]); 

    // Check disk cache in background thread
    Bitmap bitmap = getBitmapFromDiskCache(imageKey); 

    if (bitmap == null) { // Not found in disk cache
      // Process as normal
      final Bitmap bitmap = decodeSampledBitmapFromResource(
          getResources(), params[0], 100, 100));
    } 

    // Add final bitmap to caches
    addBitmapToCache(imageKey, bitmap); 

    return bitmap;
  }
  ...
} 

public void addBitmapToCache(String key, Bitmap bitmap) {
  // Add to memory cache as before
  if (getBitmapFromMemCache(key) == null) {
    mMemoryCache.put(key, bitmap);
  } 

  // Also add to disk cache
  synchronized (mDiskCacheLock) {
    if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
      mDiskLruCache.put(key, bitmap);
    }
  }
} 

public Bitmap getBitmapFromDiskCache(String key) {
  synchronized (mDiskCacheLock) {
    // Wait while disk cache is started from background thread
    while (mDiskCacheStarting) {
      try {
        mDiskCacheLock.wait();
      } catch (InterruptedException e) {}
    }
    if (mDiskLruCache != null) {
      return mDiskLruCache.get(key);
    }
  }
  return null;
} 

// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public static File getDiskCacheDir(Context context, String uniqueName) {
  // Check if media is mounted or storage is built-in, if so, try and use external cache dir
  // otherwise use internal cache dir
  final String cachePath =
      Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
          !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
              context.getCacheDir().getPath(); 

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

说明:初始化磁盘缓存需要磁盘操作因此它不应在主线程进行,然而这意味着有可能在磁盘缓存尚未初始化之前就有访问操作发生,为了解决这个问题,在上面的实现中,使用一个锁对象来确保只有在磁盘缓存初始化之后才会从磁盘缓存读取数据。

内存缓存可以直接在UI线程读取,然而磁盘缓存必须在后台线程检查,磁盘操作不应该在UI线程发生。当图片处理完毕后,务必将最终的图片添加到内存缓存和磁盘缓存以备后续使用。

以上就是对Android 图片缓存机制的详解,如有疑问请留言或者到本站社区交流讨论,感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

  • Android中图片的三级缓存机制

    我们不能每次加载图片的时候都让用户从网络上下载,这样不仅浪费流量又会影响用户体验,所以Android中引入了图片的缓存这一操作机制. 原理: 首先根据图片的网络地址在网络上下载图片,将图片先缓存到内存缓存中,缓存到强引用中 也就是LruCache中.如果强引用中空间不足,就会将较早存储的图片对象驱逐到软引用(softReference)中存储,然后将图片缓存到文件(内部存储外部存储)中:读取图片的时候,先读取内存缓存,判断强引用中是否存在图片,如果强引用中存在,则直接读取,如果强引用中不存在,则

  • Android 图片的三级缓存机制实例分析

    Android 图片的三级缓存机制实例分析 当我们获取图片的时候,如果不加以协调好图片的缓存,就会造成大流量,费流量应用,用户体验不好,影响后期发展.为此,我特地分享Android图片的三级缓存机制之从网络中获取图片,来优化应用,具体分三步进行: (1)从缓存中获取图片 (2)从本地的缓存目录中获取图片,并且获取到之后,放到缓存中 (3)从网络去下载图片,下载完成之后,保存到本地和放到缓存中 很好的协调这三层图片缓存就可以大幅度提升应用的性能和用户体验. 快速实现三级缓存的工具类ImageCac

  • Android使用缓存机制实现文件下载及异步请求图片加三级缓存

    首先给大家介绍Android使用缓存机制实现文件下载 在下载文件或者在线浏览文件时,或者为了保证文件下载的正确性,需要使用缓存机制,常使用SoftReference来实现. SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收.也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用.另外

  • Android 图片缓存机制的深入理解

    Android 图片缓存机制的深入理解 Android加载一张图片到用户界面是很简单的,但是当一次加载多张图片时,情况就变得复杂起来.很多情况下(像ListView.GridView或ViewPager等组件),屏幕上已显示的图片和即将滑动到当前屏幕上的图片数量基本上是没有限制的. 这些组件通过重用已经移除屏幕的子视图来将降低内存的使用,垃圾回收器也会及时释放那些已经不再使用的已下载的图片,这些都是很好的方法,但是为了保持一个流畅的.快速加载的用户界面,就应该避免当再次回到某个页面时而重新处理图

  • Android图片缓存原理、特性对比

    这是我在 MDCC 上分享的内容(略微改动),也是源码解析第一期发布时介绍的源码解析后续会慢慢做的事. 从总体设计和原理上对几个图片缓存进行对比,没用到他们的朋友也可以了解他们在某些特性上的实现. 一. 四大图片缓存基本信息 Universal ImageLoader 是很早开源的图片缓存,在早期被很多应用使用. Picasso 是 Square 开源的项目,且他的主导者是 JakeWharton,所以广为人知. Glide 是 Google 员工的开源项目,被一些 Google App 使用,

  • 直接应用项目中的Android图片缓存技术

    前不久搞的Android图片缓存,刚开始引入开源的框架,用着还行,但是在开发中遇到问题,就比如universal-image-loader-1.9.5.jar这个框架吧,在加载图片的时候自定义imageview无法加载,可能是存在以下问题吧,况且导入框架导致开发的项目包越来越大,基于上面的这几种情况,于是我就想自己写一个图片三级缓存的工具. 简要分析:刚开始想,图片的加载显示无非是先检查内存里面有没有,没就去文件里面找,若是文件里面没有的话就去开启网络下载,这样也符合开发中的大部分需求,而且效率

  • Android图片缓存之Lru算法(二)

    前言: 上篇我们总结了Bitmap的处理,同时对比了各种处理的效率以及对内存占用大小,点击查看.我们得知一个应用如果使用大量图片就会导致OOM(out of memory),那该如何处理才能近可能的降低oom发生的概率呢?之前我们一直在使用SoftReference软引用,SoftReference是一种现在已经不再推荐使用的方式,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用变得不再可靠,所以今天我们来认识一种新的缓

  • Android图片缓存之Bitmap详解(一)

    前言: 最近准备研究一下图片缓存框架,基于这个想法觉得还是先了解有关图片缓存的基础知识,今天重点学习一下Bitmap.BitmapFactory这两个类.  Bitmap: Bitmap是Android系统中的图像处理的最重要类之一.用它可以获取图像文件信息,进行图像剪切.旋转.缩放等操作,并可以指定格式保存图像文件.  重要函数  •public void recycle() // 回收位图占用的内存空间,把位图标记为Dead  •public final boolean isRecycled

  • Android图片缓存之初识Glide(三)

    前言: 前面总结学习了图片的使用以及Lru算法,今天来学习一下比较优秀的图片缓存开源框架.技术本身就要不断的更迭,从最初的自己使用SoftReference实现自己的图片缓存,到后来做电商项目自己的实现方案不能满足项目的需求改用Afinal,由于Afinal不再维护而选择了师出同门的Xutils,中间也接触过别的开源框架比如Picasso,对Picasso的第一次印象就不太好,初次接触是拿到了公司刚从外包公司接手过来的图片社交类app,对内存占用太大,直接感受就是导致ListView滑动有那么一

  • Android图片三级缓存策略(网络、本地、内存缓存)

    一.简介 现在的Android应用程序中,不可避免的都会使用到图片,如果每次加载图片的时候都要从网络重新拉取,这样不但很耗费用户的流量,而且图片加载的也会很慢,用户体验很不好.所以一个应用的图片缓存策略是很重要的.通常情况下,Android应用程序中图片的缓存策略采用"内存-本地-网络"三级缓存策略,首先应用程序访问网络拉取图片,分别将加载的图片保存在本地SD卡中和内存中,当程序再一次需要加载图片的时候,先判断内存中是否有缓存,有则直接从内存中拉取,否则查看本地SD卡中是否有缓存,SD

随机推荐