在用户界面(UI)加载一张图片时很简单,然而,如果你需要加载多张较大的图像,事情就会变得更加复杂,。在许多情况下(如与像的ListView GridView或ViewPager的组件),屏幕上的图片的总数伴随屏幕上滚动的骤然增加,且基本上是无限的。为使内存使用保持在稳定范围内,这些组件会在子view在屏幕中消失后,对其进行资源回收,垃圾回收机制会释放掉已加载的图片内存空间,所以建议你不要保持图片的常引用,这样做很好,为了保证页面的流畅性和响应速度,你可能不愿意在页面返回时频繁处理加载过图片。通过内存、磁盘的缓存就可以帮助你快速加载已加载的图片。

今天与大家分享一下图片的缓存技术,利用它可以提高UI的流畅性、响应速度,给用户好的体验。

如何在内存中做缓存?

通过内存缓存可以快速加载缓存图片,但会消耗应用的内存空间。LruCache类(通过兼容包可以支持到sdk4)很适合做图片缓存,它通过LinkedHashMap保持图片的强引用方式存储图片,当缓存空间超过设置定的限值时会释放掉早期的缓存。

注:在过去,常用的内存缓存实现是通过SoftReference或WeakReference,但不建议这样做。从Android2.3(API等级9)垃圾收集器开始更积极收集软/弱引用,这使得它们相当无效。此外,在Android 3.0(API等级11)之前,存储在native内存中的可见的bitmap不会被释放,可能会导致应用程序暂时地超过其内存限制并崩溃。

为了给LruCache设置合适的大小,需要考虑以下几点因素:

  • 你的应用中空闲内存是多大?

  • 你要在屏幕中一次显示多少图片? 你准备多少张图片用于显示?

  • 设备的屏幕大小与density 是多少?超高屏幕density的设备(xhdpi)像Galaxy Nexus 比 Nexus S (hdpi)这样的设备在缓存相同的图片时需要更大的Cache空间。

  • 图片的大小和属性及其需要占用多少内存空间?

  • 图片的访问频率是多少? 是否比其他的图片使用的频率高?如果这样你可能需要考虑将图片长期存放在内存中或者针对不同类型的图片使用不同的缓存策略。

  • 如何平衡质量与数量,有事你可能会存储一些常用的低质量的图片用户显示,然后通过异步线程加载高质量的图片。

图片缓存方案没有固定的模式使用所有的的应用,你需要根据应用的具体应用场景进行分析,选择合适的方案来做,缓存太小不能发挥缓存的优势,太大可能占用过多的内存,降低应用性能,或者发生内存溢出异常,

下面是一个使用LruCache的例子:

private LruCache mMemoryCache; 

@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Get memory class of this device, exceeding this amount will throw an
// OutOfMemory exception.
final int memClass = ((ActivityManager) context.getSystemService(
Context.ACTIVITY_SERVICE)).getMemoryClass(); // Use 1/8th of the available memory for this memory cache.
final int cacheSize = 1024 * 1024 * memClass / 8; mMemoryCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in bytes rather than number of items.
return bitmap.getByteCount();
}
};
...
} public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
} public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}

注意:在这个例子中,应用八分子一的内存分配给图片缓存,在普通/hdpi设备中大约为4MB(32/8)。GirdView全屏时在800x480分辨率的设备中需要1.5M图片空间(800*480*4 bytes),这样就可以在内存中缓存2.5屏的图片。

运用LruCache向ImageView添加图片时首先先检查图片是否存在,如果在直接更行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);
}
}

BitmapWorkerTask需要将将加载的图片添加到缓存中:

class BitmapWorkerTask extends AsyncTask {
...
// 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;
}
...
}

如何使用磁盘缓存?

内存缓存对访问最近使用的图片时很高效,但是你不能保证它一直会在缓存中。像GirdView这样大数据量的组件很容易充满内存缓存。你的应用可能会被“来电”打断,在后台时可能会被杀掉,内存缓存就会失效,一旦用户重新回到应用中时,你需要重新处理每个图片。

在这种情况下我们可以运用磁盘缓存存储已处理的图片,当图片不再内存中时,减少重新加载的时间,当然从磁盘加载图片时要比内存中慢,需要在后台线程中做,因为磁盘的读取时间是未知的。

注意:如果你经常访问图片,ContentProvider应该是存储图片的好地方,如:Gallery图片管理应用。

下面是一个简单的DiskLruCache实现。然而推荐的实现DiskLruCache方案请参考Android4.0中(libcore/luni/src/main/java/libcore/io/DiskLruCache.java)源码。本文使用的是之前版本中的简单实现(Quick Search中是另外的实现).

显示是简单实现DiskLruCache更新后的例子:

private DiskLruCache mDiskCache;
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
...
File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR);
mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE);
...
} class BitmapWorkerTask extends AsyncTask {
...
// 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(String.valueOf(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
if (!mDiskCache.containsKey(key)) {
mDiskCache.put(key, bitmap);
}
} public Bitmap getBitmapFromDiskCache(String key) {
return mDiskCache.get(key);
} // 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 getCacheDir(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.getExternalStorageState() == Environment.MEDIA_MOUNTED
|| !Environment.isExternalStorageRemovable() ?
context.getExternalCacheDir().getPath() : context.getCacheDir().getPath(); return new File(cachePath + File.separator + uniqueName);
}

内存缓存检查在UI线程中做,磁盘缓存的检查在后台线程中。硬盘操作不应在UI线程中。图片处理完成后应将其加入正在使用的内存、磁盘缓存中。

LruCache & DiskLruCache的更多相关文章

  1. 安卓开发笔记——关于照片墙的实现(完美缓存策略LruCache+DiskLruCache)

    这几天一直研究在安卓开发中图片应该如何处理,在网上翻了好多资料,这里做点小总结,如果朋友们有更好的解决方案,可以留言一起交流下. 内存缓存技术 在我们开发程序中要在界面上加载一张图片是件非常容易的事情 ...

  2. LruCache DiskLruCache 缓存 简介 案例 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  3. 打造完美的ImageLoader——LruCache+DiskLruCache

    做android应用少不了要和网络打交道,在我刚开始学android的时候总是处理不好网络图片的加载,尤其是图片乱跳的问题,后来发现了各种缓存图片的方法:本地缓存.软引用.LruCache.... 我 ...

  4. Android Volley框架的使用(四)图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)

    在开发安卓应用中避免不了要使用到网络图片,获取网络图片很简单,但是需要付出一定的代价——流量.对于少数的图片而言问题不大,但如果手机应用中包含大量的图片,这势必会耗费用户的一定流量,如果我们不加以处理 ...

  5. DiskLruCache硬盘缓存技术详解

    上次讲了使用内存缓存LruCache去加载很多图片而不造成OOM,而这种缓存的特点是在应用程序运行时管理内存中的资源(图片)的存储和释放,如果LruCache中有一张图片被释放了,再次加载该图片时需要 ...

  6. 让App中加入LruCache缓存,轻松解决图片过多造成的OOM

    上次有过电话面试中问到Android中的缓存策略,当时模糊不清的回答,现在好好理一下吧. Android中一般情况下采取的缓存策略是使用二级缓存,即内存缓存+硬盘缓存->LruCache+Dis ...

  7. 让App中增加LruCache缓存,轻松解决图片过多造成的OOM

    上次有过电话面试中问到Android中的缓存策略,当时模糊不清的回答,如今好好理一下吧. Android中普通情况下採取的缓存策略是使用二级缓存.即内存缓存+硬盘缓存->LruCache+Dis ...

  8. 【第五篇】Volley代码修改之图片二级缓存以及相关源码阅读(重写ImageLoader.ImageCache)

    前面http://www.cnblogs.com/androidsuperman/p/8a157b18ede85caa61ca5bc04bba43d0.html 有讲到使用LRU来处理缓存的,但是只是 ...

  9. ImageLoader 笔记

    BitmapFactory 我们不能够通过构造函数创建Bitmap对象.如果需要将图片转成Bitmap对象加载到内存中,就需要使用BitmapFactory类.BitmapFactory跟据图片数据源 ...

随机推荐

  1. 使用window.open打开新窗口被浏览器拦截的解决方案

    问题描述: 代码中直接使用window.open('//www.baidu.com', '_blank');会被浏览器窗口拦截 原因浏览器为了维护用户安全和体验,在JS中直接使用window.open ...

  2. java io-----转

    https://blog.csdn.net/zch19960629/article/details/77917739 输入输出的重要性:     输入和输出功能是Java对程序处理数据能力的提高,Ja ...

  3. MySQL账户管理和主从同步

    账户管理 在生产环境下操作数据库时,绝对不可以使用root账户连接,而是创建特定的账户,授予这个账户特定 的操作权限,然后连接进行操作,主要的操作就是数据的CRUD(增删改查) MySQL账户体系:根 ...

  4. 算法导论 第八章 线性时间排序(python)

    比较排序:各元素的次序依赖于它们之间的比较{插入排序O(n**2) 归并排序O(nlgn) 堆排序O(nlgn)快速排序O(n**2)平均O(nlgn)} 本章主要介绍几个线性时间排序:(运算排序非比 ...

  5. Java关于条件判断练习--统计一个src文件下的所有.java文件内的代码行数(注释行、空白行不统计在内)

    要求:统计一个src文件下的所有.java文件内的代码行数(注释行.空白行不统计在内) 分析:先封装一个静态方法用于统计确定的.java文件的有效代码行数.使用字符缓冲流读取文件,首先判断是否是块注释 ...

  6. Python 和 Flask实现RESTful services

    使用Flask建立web services超级简单. 当然,也有很多Flask extensions可以帮助建立RESTful services,但是这个例实在太简单了,不需要使用任何扩展. 这个we ...

  7. python 快排,堆排,归并

    #归并排序 def mergeSort(a,L,R) :     if(L>=R) :         return     mid=((L+R)>>1)     mergeSort ...

  8. ORACLE金额转换成英文大写的函数

    用法如下:get_capital_money(Currency, Money) Currency: 货币或货币描述,将放在英文大写的前面: Money:金额.支持两位小数点.如果需要更多的小数点,请自 ...

  9. 【BZOJ2560】串珠子(状压DP,容斥原理)

    题意: 铭铭有n个十分漂亮的珠子和若干根颜色不同的绳子.现在铭铭想用绳子把所有的珠子连接成一个整体.现在已知所有珠子互不相同,用整数1到n编号.对于第i个珠子和第j个珠子,可以选择不用绳子连接,或者在 ...

  10. Codeforces889C. Maximum Element

    $n \leq 2000000$的排列,问有多少满足:存在个$i$,使得$p_i \neq n$,且$p_j<p_i,j \in [i+1,i+K]$,$K \leq 2000000$是给定常数 ...