高效地加载图片(四) 管理缓存

除了缓存图片意外,还有一些其他的方式来促进GC的效率和图片的复用.不同的Android系统版本有不同的处理策略.BitmapFun中就包含了这个类,能够使我们高效地构建我们的项目.

为了开始以下教程,我们需要先介绍一下Android系统对Bitmap管理的进化史.

  • 在Android2.2(API level 8)以及更低的版本中,当垃圾被回收时,应用的线程会被停止,这会造成一定程度的延时.在Android
    2.3中,加入了并发回收机制,这意味着当Bitmap不再被使用的时候,内存会被很快地回收.
  • 在Android 2.3.3(API level
    10)以及更低版本中,像素数据是被存储在本地内存中的,并且和Bitmap本身是分离的,Bitmap存储在Dalvik堆中.本地内存中的像素数据并不以一种可预知的方式释放,可能会造成应用内存溢出和崩溃.而在Android
    3.0(API level 11)中,像素数据和Bitmap一起被保存在Dalvik堆中.

以下内容描述了如何为不同的Android版本选择最佳的图片缓存管理方式.

在Android
2.3.3以及更低版本中管理缓存

在Android 2.3.3(API level
10)以及更低版本中,建议使用Bitmap.recycle()方法.如果你正在展示大量的图片,应用可能会内存溢出.这时候recycle()方法可以使内存被尽可能快地回收.

注意:你只能在确定了Bitmap确实不再被使用了之后才能调用recycle()方法.如果你调用recycle()方法释放了Bitmap,而稍后却又使用这个Bitmap,这时就会出现"Canvas:
trying to use a recycled bitmap"错误.

以下代码片段是recycle()方法调用的示例.这里使用了引用计数器的方式(通过变量mDisplayRefCount和mCacheRefCount)来追踪一个Bitmap是否还在被使用或者存在于缓存中.当满足如下条件时,此处就会回收Bitmap:

  • 引用计数器变量mDisplayRefCount和mCacheRefCount的值都为0时.
  • Bitmap不为null,而且没有被回收.

view plaincopy to clipboardprint?

  1. private int mCacheRefCount = 0;

  2. private int mDisplayRefCount = 0;

  3. ...

  4. // 通知当前Drawable,当前被使用的状态改变了

  5. // 保持一个计数器,用来决定当前Drawable何时被回收

  6. public void setIsDisplayed(boolean isDisplayed) {

  7. synchronized (this) {

  8. if (isDisplayed) {

  9. // 当被显示时,则计数器mDisplayRefCount的值+1

  10. mDisplayRefCount++;

  11. // 标志当前Drawable被显示了

  12. mHasBeenDisplayed = true;

  13. } else {

  14. // 当一个引用被释放时,计数器mDisplayRefCount的值-1

  15. mDisplayRefCount--;

  16. }

  17. }

  18. // 确认recycle()方法是否已经被调用了

  19. checkState();

  20. }
  21. // 通知当前Drawable缓存状态改变了

  22. // 保持一个计数器用于决定何时这个Drawable不再被缓存

  23. public void setIsCached(boolean isCached) {

  24. synchronized (this) {

  25. if (isCached) {

  26. mCacheRefCount++;

  27. } else {

  28. mCacheRefCount--;

  29. }

  30. }

  31. // 确认recycle()方法是否已经被调用了

  32. checkState();

  33. }
  34. private synchronized void checkState() {

  35. // 如果当前Drawable的内存和展示计数器都为0,而且当前Drawable还可用

  36. // 则释放掉它

  37. if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed

  38. && hasValidBitmap()) {

  39. getBitmap().recycle();

  40. }

  41. }
  42. // 判断Drawable对应的Bitmap是否可用

  43. private synchronized boolean hasValidBitmap() {

  44. Bitmap bitmap = getBitmap();

  45. return bitmap != null && !bitmap.isRecycled();

  46. }

private int mCacheRefCount = 0;
private int mDisplayRefCount = 0;
...
// 通知当前Drawable,当前被使用的状态改变了
// 保持一个计数器,用来决定当前Drawable何时被回收
public void setIsDisplayed(boolean isDisplayed) {
synchronized (this) {
if (isDisplayed) {
// 当被显示时,则计数器mDisplayRefCount的值+1
mDisplayRefCount++;
// 标志当前Drawable被显示了
mHasBeenDisplayed = true;
} else {
// 当一个引用被释放时,计数器mDisplayRefCount的值-1
mDisplayRefCount--;
}
}
// 确认recycle()方法是否已经被调用了
checkState();
}

// 通知当前Drawable缓存状态改变了
// 保持一个计数器用于决定何时这个Drawable不再被缓存
public void setIsCached(boolean isCached) {
synchronized (this) {
if (isCached) {
mCacheRefCount++;
} else {
mCacheRefCount--;
}
}
// 确认recycle()方法是否已经被调用了
checkState();
}

private synchronized void checkState() {
// 如果当前Drawable的内存和展示计数器都为0,而且当前Drawable还可用
// 则释放掉它
if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
&& hasValidBitmap()) {
getBitmap().recycle();
}
}

// 判断Drawable对应的Bitmap是否可用
private synchronized boolean hasValidBitmap() {
Bitmap bitmap = getBitmap();
return bitmap != null && !bitmap.isRecycled();
}

在Android
3.0以及更高版本中管理缓存

从Android 3.0(API level 11)开始,引入了BitmapFactory.Options.inBitmap字段,如果这个属性被设置了,拥有这个Options对象的方法在解析图片的时候会尝试复用一张已存在的图片.这意味着图片缓存被服用了,这意味着更流畅的用户体验以及更好的内存分配和回收.然而,要使用inBitmap有这一定的限制.尤其需要注意的是,在Android
4.4(API level 19)之前,只有相同大小的Btiamp才会被复用.更详细的用法,请参见inBitmap的文档.

保存一个Bitmap以备后来使用

下面的代码片段示范了如何保存一个Bitmap以备后来使用.当在Android
3.0以及更高版本平台中时,一个Bitmap被从LrcCache中移除后,它的软引用会被保存到一个HashSet中,以备inBitmap后来使用:

view plaincopy to clipboardprint?

  1. Set<SoftReference<Bitmap>> mReusableBitmaps;

  2. private LruCache<String, BitmapDrawable> mMemoryCache;
  3. // 当程序运行在Honeycomb(Android 3.1)及以上版本的系统中时,创建一个

  4. // 同步的HashSet用于存放可复用的Bitmap的引用

  5. if (Utils.hasHoneycomb()) {

  6. mReusableBitmaps =

  7. Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());

  8. }
  9. mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
  10. // Notify the removed entry that is no longer being cached.

  11. @Override

  12. protected void entryRemoved(boolean evicted, String key,

  13. BitmapDrawable oldValue, BitmapDrawable newValue) {

  14. if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {

  15. // 如果被移除的Drawable是RecyclingBitmapDrawable,则通知它

  16. // 已经被从内存缓存中移除

  17. ((RecyclingBitmapDrawable) oldValue).setIsCached(false);

  18. } else {

  19. // 如果被移除的Drawable是一个普通的BitmapDrawable

  20. if (Utils.hasHoneycomb()) {

  21. // 如果运行在Honeycomb(Android 3.1)及以上系统中,则为Bitmap添加

  22. // 到一个保存软引用的集合中,以备后来被inBitmap使用

  23. mReusableBitmaps.add

  24. (new SoftReference<Bitmap>(oldValue.getBitmap()));

  25. }

  26. }

  27. }

  28. ....

  29. }

Set<SoftReference<Bitmap>> mReusableBitmaps;
private LruCache<String, BitmapDrawable> mMemoryCache;

// 当程序运行在Honeycomb(Android 3.1)及以上版本的系统中时,创建一个
// 同步的HashSet用于存放可复用的Bitmap的引用
if (Utils.hasHoneycomb()) {
mReusableBitmaps =
Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
}

mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {

// Notify the removed entry that is no longer being cached.
@Override
protected void entryRemoved(boolean evicted, String key,
BitmapDrawable oldValue, BitmapDrawable newValue) {
if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
// 如果被移除的Drawable是RecyclingBitmapDrawable,则通知它
// 已经被从内存缓存中移除
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
} else {
// 如果被移除的Drawable是一个普通的BitmapDrawable
if (Utils.hasHoneycomb()) {
// 如果运行在Honeycomb(Android 3.1)及以上系统中,则为Bitmap添加
// 到一个保存软引用的集合中,以备后来被inBitmap使用
mReusableBitmaps.add
(new SoftReference<Bitmap>(oldValue.getBitmap()));
}
}
}
....
}

复用一个已经存在的Bitmap

在解析图片时,会检查是否已经存在一个可用的Bitmap,如下:

view plaincopy to clipboardprint?

  1. public static Bitmap decodeSampledBitmapFromFile(String filename,

  2. int reqWidth, int reqHeight, ImageCache cache) {
  3. final BitmapFactory.Options options = new BitmapFactory.Options();

  4. ...

  5. BitmapFactory.decodeFile(filename, options);

  6. ...
  7. // 如果应用运行在Honeycomb及以上系统中,则尝试使用inBitmap复用图片

  8. if (Utils.hasHoneycomb()) {

  9. addInBitmapOptions(options, cache);

  10. }

  11. ...

  12. return BitmapFactory.decodeFile(filename, options);

  13. }

public static Bitmap decodeSampledBitmapFromFile(String filename,
int reqWidth, int reqHeight, ImageCache cache) {

final BitmapFactory.Options options = new BitmapFactory.Options();
...
BitmapFactory.decodeFile(filename, options);
...

// 如果应用运行在Honeycomb及以上系统中,则尝试使用inBitmap复用图片
if (Utils.hasHoneycomb()) {
addInBitmapOptions(options, cache);
}
...
return BitmapFactory.decodeFile(filename, options);
}

以下代码片段描述了如何查找一个已经存在的Bitmap并将它设置为inBitmap的值.注意,这个方法只有在找到了符合条件的Bitmap才会为inBitmap赋值(我们不能乐观地假定图片会被找到):

view plaincopy to clipboardprint?

  1. private static void addInBitmapOptions(BitmapFactory.Options options,

  2. ImageCache cache) {

  3. // inBitmap仅能接受可编辑的Bitmap,所以此处需要强制解码器

  4. // 返回一个可编辑的Bitmap

  5. options.inMutable = true;
  6. if (cache != null) {

  7. // 尝试查找满足条件的Bitmap给inBitmap赋值

  8. Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
  9. if (inBitmap != null) {

  10. // 如果查找到了符合条件的Bitmap,则赋值给inBitmap

  11. options.inBitmap = inBitmap;

  12. }

  13. }

  14. }
  15. // 这个方法遍历了保存弱引用的集合,用于查找一个合适的Bitmap

  16. protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {

  17. Bitmap bitmap = null;
  18. if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {

  19. synchronized (mReusableBitmaps) {

  20. final Iterator<SoftReference<Bitmap>> iterator

  21. = mReusableBitmaps.iterator();

  22. Bitmap item;
  23. while (iterator.hasNext()) {

  24. item = iterator.next().get();
  25. if (null != item && item.isMutable()) {

  26. // 检查当前的Bitmap是否满足inBitmap的复用条件

  27. // Check to see it the item can be used for inBitmap.

  28. if (canUseForInBitmap(item, options)) {

  29. // 如果满足复用条件,则给Bitmap赋值,并在方法结束时返回

  30. bitmap = item;

  31. // 在给返回值赋值后,将当前Bitmap的引用从集合中移除

  32. iterator.remove();

  33. break;

  34. }

  35. } else {

  36. // 如果读取到的是空引用,则将该引用移除

  37. iterator.remove();

  38. }

  39. }

  40. }

  41. }

  42. return bitmap;

  43. }

private static void addInBitmapOptions(BitmapFactory.Options options,
ImageCache cache) {
// inBitmap仅能接受可编辑的Bitmap,所以此处需要强制解码器
// 返回一个可编辑的Bitmap
options.inMutable = true;

if (cache != null) {
// 尝试查找满足条件的Bitmap给inBitmap赋值
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);

if (inBitmap != null) {
// 如果查找到了符合条件的Bitmap,则赋值给inBitmap
options.inBitmap = inBitmap;
}
}
}

// 这个方法遍历了保存弱引用的集合,用于查找一个合适的Bitmap
protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
Bitmap bitmap = null;

if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
synchronized (mReusableBitmaps) {
final Iterator<SoftReference<Bitmap>> iterator
= mReusableBitmaps.iterator();
Bitmap item;

while (iterator.hasNext()) {
item = iterator.next().get();

if (null != item && item.isMutable()) {
// 检查当前的Bitmap是否满足inBitmap的复用条件
// Check to see it the item can be used for inBitmap.
if (canUseForInBitmap(item, options)) {
// 如果满足复用条件,则给Bitmap赋值,并在方法结束时返回
bitmap = item;
// 在给返回值赋值后,将当前Bitmap的引用从集合中移除
iterator.remove();
break;
}
} else {
// 如果读取到的是空引用,则将该引用移除
iterator.remove();
}
}
}
}
return bitmap;
}

最后,下面的这个方法能够检查哪个Bitmap满足inBitmap的复用条件:

view plaincopy to clipboardprint?

  1. static boolean canUseForInBitmap(

  2. Bitmap candidate, BitmapFactory.Options targetOptions) {
  3. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

  4. // 从Android 4.4(KitKat)开始,如果当前需要的Bitmap尺寸(内存中占用的字节数)比缓存中的Bitmap尺寸小

  5. // 并且同一张图片的Bitmap存在,我们就可以复用它

  6. int width = targetOptions.outWidth / targetOptions.inSampleSize;

  7. int height = targetOptions.outHeight / targetOptions.inSampleSize;

  8. // 当前需要的Bitmap占用字节数

  9. int byteCount = width * height * getBytesPerPixel(candidate.getConfig());

  10. // 如果需要的Bitmap尺寸小于原Bitmap,则返回true

  11. return byteCount <= candidate.getAllocationByteCount();

  12. }
  13. // 在Android 4.4以前,尺寸必须完全一致,并且inSampleSize为1时

  14. // Bitmap才能被复用

  15. return candidate.getWidth() == targetOptions.outWidth

  16. && candidate.getHeight() == targetOptions.outHeight

  17. && targetOptions.inSampleSize == 1;

  18. }
  19. /**

  20. * 一个用于判断在不同的参数下,每个像素占用字节数的方法

  21. */

  22. static int getBytesPerPixel(Config config) {

  23. if (config == Config.ARGB_8888) {

  24. return 4;

  25. } else if (config == Config.RGB_565) {

  26. return 2;

  27. } else if (config == Config.ARGB_4444) {

  28. return 2;

  29. } else if (config == Config.ALPHA_8) {

  30. return 1;

  31. }

  32. return 1;

  33. }

static boolean canUseForInBitmap(
Bitmap candidate, BitmapFactory.Options targetOptions) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 从Android 4.4(KitKat)开始,如果当前需要的Bitmap尺寸(内存中占用的字节数)比缓存中的Bitmap尺寸小
// 并且同一张图片的Bitmap存在,我们就可以复用它
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
// 当前需要的Bitmap占用字节数
int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
// 如果需要的Bitmap尺寸小于原Bitmap,则返回true
return byteCount <= candidate.getAllocationByteCount();
}

// 在Android 4.4以前,尺寸必须完全一致,并且inSampleSize为1时
// Bitmap才能被复用
return candidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1;
}

/**
* 一个用于判断在不同的参数下,每个像素占用字节数的方法
*/
static int getBytesPerPixel(Config config) {
if (config == Config.ARGB_8888) {
return 4;
} else if (config == Config.RGB_565) {
return 2;
} else if (config == Config.ARGB_4444) {
return 2;
} else if (config == Config.ALPHA_8) {
return 1;
}
return 1;
}


高效地加载图片(四) 管理缓存

时间: 2024-09-29 08:12:23

高效地加载图片(四) 管理缓存的相关文章

高效地加载图片(三) 缓存图片

如果只需要加载一张图片,那么直接加载就可以.但是,如果要在类似ListView,GridView或者ViewPager的控件中加载大量的图片时,问题就会变得复杂.在使用这类控件时,在短时间内可能会显示在屏幕上的图片数量是不固定的. 这类控件会通过子View的复用来保持较低的内存占用.而Garbage Collector也会在View被复用时释放对应的Bitmap,保证这些没用用到的Bitmap不会长期存在于内存中.但是为了保证控件的流畅滑动,在一个View再次滑动出现在屏幕上时,我们需要避免图片

Android开源框架ImageLoader:加载图片的三级缓存机制

前言:可从  https://github.com/nostra13/Android-Universal-Image-Loader 下载三级缓存机制的开源框架.下文简单介绍该框架中主要的常用方法,掌握这些方法,基本就可应对多数图片下载的需求. 注意:以下代码为示意代码片断,仔细读一下应能知道怎么用.蓝色表示为开源框架中的类. 1.初始化ImageLoader类对象: ImageLoader imageLoader = ImageLoader.getInstance(); imageLoader.

高效地加载图片(五) 将图片展示在UI中

这篇文章将前几篇使用的方法进行了整合,让我们能够在后台线程中加载以及缓存图片并在ViewPager和GridView中展示出来,并在这些过程中处理并发以及参数的设置. 将图片加载到ViewPager中 使用滑动视图来对图片详情进行导航是一种不错的方式.我们可以使用ViewPager和PagerAdapter来实现.但是,使用FragmentStatePagerAdapter可能会更好,它能够自动地保存ViewPager中Fragment的状态并控制它的创建和销毁,能够有效地利用内存. 注意:如果

Picasso图片框架加载图片 使用及缓存问题

项目中用的Picasso 框架 ,加载图片.使用很方便 而且缓存机制非常强大. 正常使用我们可以这样直接调用,我把方法写到一个util里面了. 调用代码如下: PicassoUtil.displayImage(context, Constants.U_IMG_URL, R.drawable.default, iv_icon); Util工具类 import java.io.File; import android.content.Context; import android.text.Text

android异步加载图片并缓存到本地实现方法

图片过多造成内存溢出,这个是最不容易解决的,要想一些好的缓存策略,比如大图片使用LRU缓存策略或懒加载缓存策略.今天首先介绍一下本地缓存图片 在android项目中访问网络图片是非常普遍性的事情,如果我们每次请求都要访问网络来获取图片,会非常耗费流量,而且图片占用内存空间也比较大,图片过多且不释放的话很容易造成内存溢出.针对上面遇到的两个问题,首先耗费流量我们可以将图片第一次加载上面缓存到本地,以后如果本地有就直接从本地加载.图片过多造成内存溢出,这个是最不容易解决的,要想一些好的缓存策略,比如

LruCache缓存处理及异步加载图片类的封装

Android中的缓存处理及异步加载图片类的封装 一.缓存介绍: (一).Android中缓存的必要性: 智能手机的缓存管理应用非常的普遍和需要,是提高用户体验的有效手段之一. 1.没有缓存的弊端: 流量开销:对于客户端——服务器端应用,从远程获取图片算是经常要用的一个功能,而图片资源往往会消耗比较大的流量. 加载速度:如果应用中图片加载速度很慢的话,那么用户体验会非常糟糕. 那么如何处理好图片资源的获取和管理呢?异步下载+本地缓存 2.缓存带来的好处: 1. 服务器的压力大大减小: 2. 客户

Android 异步加载图片,使用LruCache和SD卡或手机缓存,效果非常的流畅

转载请注明出处http://blog.csdn.net/xiaanming/article/details/9825113 异步加载图片的例子,网上也比较多,大部分用了HashMap<String, SoftReference<Drawable>> imageCache ,但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠.另外,Android 3.0

android 网络加载图片,对图片资源进行优化,并且实现内存双缓存 + 磁盘缓存

经常会用到 网络文件 比如查看大图片数据 资源优化的问题,当然用开源的项目  Android-Universal-Image-Loader  或者 ignition 都是个很好的选择. 在这里把原来 写过的优化的代码直接拿出来,经过测试千张图片效果还是不错的. 免费培训课:http://www.jinhusns.com/Products/Curriculum/?type=xcj 工程目录 至于 Activity 就是加载了 1个网格布局 01./** 02.*   实现 异步加载 和   2级缓

Android:ViewPager扩展详解——带有导航的ViewPagerIndicator(附带图片缓存,异步加载图片)

大家都用过viewpager了, github上有对viewpager进行扩展,导航风格更加丰富,这个开源项目是ViewPagerIndicator,很好用,但是例子比较简单,实际用起来要进行很多扩展,比如在fragment里进行图片缓存和图片异步加载. 下面是ViewPagerIndicator源码运行后的效果,大家也都看过了,我多此一举截几张图: 下载源码请点击这里 ===========================================华丽的分割线==============