接上篇BitMap高效显示策略(二):在ListView上异步加载网络图片,ListView在屏幕上来回划动时,重新进入屏幕范围的Item会重新从网络上加载一次图片,这样做会降低效率,并且浪费流量,更好的方法是使用缓存,缓存可以分为2级:内存缓存和文件缓存,这篇只讨论内存缓存:当ListView需要在指定Item上加载图片时,先根据下载URL检查缓存中是否存在这个BitmapDrawable,如果存在,直接显示在ImageView上,不存在,则从网络上下载。当一个Bitmap下载完毕后,加入内存缓存。
按照以上思路,接上篇代码,改写loadImage方法,伪代码为:
- public void loadImage(String url, ImageView imageView) {
- if (url == null) {
- return;
- }
- BitmapDrawable bitmapDrawable = null;
- //先从内存缓存中读取
- if (缓存 != null) {
- bitmapDrawable = 缓存中获取bitmapDrawable方法(url);
- }
- if (bitmapDrawable != null) {
- imageView.setImageDrawable(bitmapDrawable);
- }
- //否则下载
- else if (cancelPotentialWork(url, imageView)) {
- final BitmapWorkerTask task = new BitmapWorkerTask(url, imageView);
- final AsyncDrawable asyncDrawable = new AsyncDrawable(mResources,
- task);
- imageView.setImageDrawable(asyncDrawable);
- task.executeOnExecutor(ImageAsyncTask.DUAL_THREAD_EXECUTOR);
- }
- }
- if (bitmap == null && !isCancelled()
- && getAttachedImageView() != null && !mExitTasksEarly) {
- bitmap = processBitmap(mUrl);
- }
- if (bitmap != null) {
- drawable = new BitmapDrawable(mResources, bitmap);
- if (缓存 != null) {
- 加入缓存
- }
- }
接下来,实现以上伪码
首先创建ImageCache这个类,ImageCache用于封装缓存对象,ImageCache内使用LruCache类作为内存缓存,LruCache内部实现中,使用了LinkedHashMap,具体实现分析可以参照android.util.LruCache主要代码分析 。
ImageCache的实现:
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class ImageCache {
private static final String TAG = "TEST";
//缓存基本设置
private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 5; // 内存缓存默认大小5MB
private static final boolean DEFAULT_MEM_CACHE_ENABLED = true;
//内存缓存
private LruCache<String, BitmapDrawable> mMemoryCache;
private ImageCacheParams mCacheParams;
public static class ImageCacheParams {
//mem
public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;
public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;
/***
* 手动设置内存缓存大小
* @param percent 缓存大小占最大可用内存的比例
*/
public void setMemCacheSizePercent(float percent) {
if (percent < 0.01f || percent > 0.8f) {
throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
+ "between 0.01 and 0.8 (inclusive)");
}
memCacheSize = Math.round(percent * Runtime.getRuntime().maxMemory() / 1024);
}
}
/**
*
* 放在no UI的fragment中,保证屏幕旋转时不被回收
* @param fragmentManager
* @param cacheParams
* @return
*/
public static ImageCache getInstance(
FragmentManager fragmentManager, ImageCacheParams cacheParams) {
final RetainFragment mRetainFragment = findOrCreateRetainFragment(fragmentManager);
ImageCache imageCache = (ImageCache) mRetainFragment.getObject();
if (imageCache == null) {
imageCache = new ImageCache(cacheParams);
mRetainFragment.setObject(imageCache);
}
return imageCache;
}
private ImageCache(ImageCacheParams cacheParams) {
init(cacheParams);
}
private void init(ImageCacheParams cacheParams) {
mCacheParams = cacheParams;
if (mCacheParams.memoryCacheEnabled) {
mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
@Override
protected void entryRemoved(boolean evicted, String key,
BitmapDrawable oldValue, BitmapDrawable newValue) {
if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
//减少缓存计数
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
}
}
@Override
protected int sizeOf(String key, BitmapDrawable bitmapDrawable) {
final int bitmapSize = getBitmapSize(bitmapDrawable) / 1024;
return bitmapSize == 0 ? 1 : bitmapSize;
}
};
}
}
/**
* 清空缓存
*/
public void clearCache() {
if (mMemoryCache != null) {
mMemoryCache.evictAll();
}
}
/**
* 获取bitmap大小
* @param bitmapDrawable
* @return
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
public static int getBitmapSize(BitmapDrawable bitmapDrawable) {
Bitmap bitmap = bitmapDrawable.getBitmap();
//4.4
if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
return bitmap.getAllocationByteCount();
}
//3.1及以上
if (Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
return bitmap.getByteCount();
}
//3.1之前的版本
return bitmap.getRowBytes() * bitmap.getHeight();
}
/**获取内存缓存中的图片
* @param url
* @return
*/
public BitmapDrawable getBitmapFromMemCache(String url) {
BitmapDrawable bitmapDrawable = null;
if (mMemoryCache != null) {
bitmapDrawable = mMemoryCache.get(url);
}
if (bitmapDrawable != null) {
Log.d("TEST", "Memory cache hit");
}
return bitmapDrawable;
}
/**
* 图片加入内存缓存
* @param data
* @param value
*/
public void addBitmapToCache(String url, BitmapDrawable bitmapDrawable) {
if (url == null || bitmapDrawable == null) {
return;
}
if (mMemoryCache != null) {
//如果是RecyclingBitmapDrawable,增加缓存计数
if (RecyclingBitmapDrawable.class.isInstance(bitmapDrawable)) {
((RecyclingBitmapDrawable) bitmapDrawable).setIsCached(true);
}
mMemoryCache.put(url, bitmapDrawable);
}
}
private static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
RetainFragment mRetainFragment = (RetainFragment) fm.findFragmentByTag(ImageCache.class.getName());
if (mRetainFragment == null) {
mRetainFragment = new RetainFragment();
fm.beginTransaction().add(mRetainFragment,
ImageCache.class.getName()).commitAllowingStateLoss();;
}
return mRetainFragment;
}
/**
* 后台Fragment用于在横竖屏切换时保存ImageCache对象。
*
*/
public static class RetainFragment extends Fragment {
private Object object;
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
}
}
ImageCacheParams封装了缓存的一些设定,setMemCacheSizePercent方法设置内存缓存大小占应用程序最大可用内存的比例,如果不设置,默认大小是5MB。
ImageCache实例通过静态方法getInstance获取,ImageCache实例存储在RetainFragment中,RetainFragment是一个没有UI的Fragment,在onCreate方法中, 设置了setRetainInstance(true);所以这个Fragment在屏幕旋转时,不会重新创建,这样得以在配置变化时保存ImageCache对象并且Activity重新创建后快速加载缓存。
构造方法中调用了init方法,init方法中,初始化mMemoryCache对象, 重写了其entryRemoved 和sizeof方法,在entryRemoved中,减少RecyclingBitmapDrawable的缓存计数。sizeOf是用来计算单个缓存的BitmapDrawable对象的大小的,getBitmapSize中根据不同安卓版本,调用不同的计算方法。
- mImageWorker = new ImageFetcher(getActivity(), 50);
- ImageCache.ImageCacheParams cacheParams = new ImageCache.ImageCacheParams();
- //内存缓存大小为应用可用内存的1/4
- cacheParams.setMemCacheSizePercent(0.25f);
- mImageWorker.initImageCache(getActivity().getSupportFragmentManager(), cacheParams);
修改 loadImage代码:
public void loadImage(String url, ImageView imageView) {
if (url == null) {
return;
}
BitmapDrawable bitmapDrawable = null;
//先从内存缓存中读取
if (mImageCache != null) {
bitmapDrawable = mImageCache.getBitmapFromMemCache(url);
}
if (bitmapDrawable != null) {
imageView.setImageDrawable(bitmapDrawable);
}
//否则下载
else if (cancelPotentialWork(url, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(url, imageView);
final AsyncDrawable asyncDrawable = new AsyncDrawable(mResources,
task);
imageView.setImageDrawable(asyncDrawable);
task.executeOnExecutor(ImageAsyncTask.DUAL_THREAD_EXECUTOR);
}
}
修改BitmapWorkerTask的doInBackground:
- @Override
- protected BitmapDrawable doInBackground(Void... params) {
- Bitmap bitmap = null;
- BitmapDrawable drawable = null;
- synchronized (mPauseWorkLock) {
- while (mPauseWork && !isCancelled()) {
- try {
- mPauseWorkLock.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- if (bitmap == null && !isCancelled()
- && getAttachedImageView() != null && !mExitTasksEarly) {
- bitmap = processBitmap(mUrl);
- }
- if (bitmap != null) {
- if (Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
- drawable = new BitmapDrawable(mResources, bitmap);
- } else {
- // 3.0以下版本,创建自动回收的BitmapDrawable
- drawable = new RecyclingBitmapDrawable(mResources, bitmap);
- }
- //加入缓存
- if (mImageCache != null) {
- mImageCache.addBitmapToCache(mUrl, drawable);
- }
- }
- return drawable;
- }
BitmapFactory.Options.inBitmap:
在Android 3.0 引进了BitmapFactory.Options.inBitmap. 如果这个值被设置了,decode方法会在加载内容的时候去重用已经存在的bitmap. 这意味着bitmap的内存是被重新利用的,这样可以提升性能, 并且减少了内存的分配与回收。然而,使用inBitmap有一些限制。特别是在Android 4.4 之前,只支持同等大小的位图。
在ImageCache中加入全局变量:
private Set<SoftReference<Bitmap>> mReusableBitmaps;
这是个存放可重用Bitmap的软引用的集合
修改init方法:
- private void init(ImageCacheParams cacheParams) {
- mCacheParams = cacheParams;
- if (mCacheParams.memoryCacheEnabled) {
- //Android3.0以上可以重用bitmap内存,将内存缓存中的回收项重用。
- if (Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
- mReusableBitmaps =
- Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
- }
- mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
- @Override
- protected void entryRemoved(boolean evicted, String key,
- BitmapDrawable oldValue, BitmapDrawable newValue) {
- if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
- //减少缓存计数
- ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
- } else {
- //加入待重用集合
- if (Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
- mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));
- }
- }
- }
- @Override
- protected int sizeOf(String key, BitmapDrawable bitmapDrawable) {
- final int bitmapSize = getBitmapSize(bitmapDrawable) / 1024;
- return bitmapSize == 0 ? 1 : bitmapSize;
- }
- };
- }
- }
在方法中,decoder去检查是否有可用的bitmap。
- public static Bitmap decodeSampledBitmapFromStream(InputStream is,
- BitmapFactory.Options options, ImageCache cache) {
- if (Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
- addInBitmapOptions(options, cache);
- }
- return BitmapFactory.decodeStream(is, null, options);
- }
addInBitmapOptions方法会去可重用的集合中查找一个合适的bitmap赋值给inBitmap,这个方法不保证一定返回非空的bitmap
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- private static void addInBitmapOptions(BitmapFactory.Options options,
- ImageCache cache) {
- options.inMutable = true;
- if (cache != null) {
- Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
- if (inBitmap != null) {
- options.inBitmap = inBitmap;
- }
- }
- }
getBitmapFromReusableSet方法中,遍历集合中的每个Bitmap,如果有合适的就返回。
- protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
- Bitmap bitmap = null;
- if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
- final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps
- .iterator();
- Bitmap item;
- while (iterator.hasNext()) {
- item = iterator.next().get();
- if (null != item && item.isMutable()) {
- if (canUseForInBitmap(item, options)) {
- bitmap = item;
- // Remove from reusable set so it can't be used again
- iterator.remove();
- break;
- }
- } else {
- // Remove from the set if the reference has been cleared.
- iterator.remove();
- }
- }
- }
- return bitmap;
- }
- @TargetApi(VERSION_CODES.KITKAT)
- private static boolean canUseForInBitmap(Bitmap candidate,
- BitmapFactory.Options targetOptions) {
- // 4.4之前的版本,尺寸必须完全吻合
- if (Build.VERSION.SDK_INT < VERSION_CODES.KITKAT) {
- return candidate.getWidth() == targetOptions.outWidth
- && candidate.getHeight() == targetOptions.outHeight
- && targetOptions.inSampleSize == 1;
- }
- // 4.4版本,可以使用比自己大的bitmap
- int width = targetOptions.outWidth / targetOptions.inSampleSize;
- int height = targetOptions.outHeight / targetOptions.inSampleSize;
- // 根据图片格式,计算具体的bitmap大小
- int byteCount = width * height
- * getBytesPerPixel(candidate.getConfig());
- return byteCount <= candidate.getAllocationByteCount();
- }
- /**
- * Return the byte usage per pixel of a bitmap based on its configuration.
- *
- * @param config
- * The bitmap configuration.
- * @return The byte usage per pixel.
- */
- private 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;
- }
完整的ImageCache代码:
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class ImageCache {
private static final String TAG = "TEST";
//缓存基本设置
private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 5; // 内存缓存默认大小5MB
private static final boolean DEFAULT_MEM_CACHE_ENABLED = true;
//内存缓存
private LruCache<String, BitmapDrawable> mMemoryCache;
private ImageCacheParams mCacheParams;
//3.0后的bitmap重用机制
private Set<SoftReference<Bitmap>> mReusableBitmaps;
public static class ImageCacheParams {
//mem
public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;
public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;
/***
* 手动设置内存缓存大小
* @param percent 缓存大小占最大可用内存的比例
*/
public void setMemCacheSizePercent(float percent) {
if (percent < 0.01f || percent > 0.8f) {
throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
+ "between 0.01 and 0.8 (inclusive)");
}
memCacheSize = Math.round(percent * Runtime.getRuntime().maxMemory() / 1024);
}
}
protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
Bitmap bitmap = null;
if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator();
Bitmap item;
while (iterator.hasNext()) {
item = iterator.next().get();
if (null != item && item.isMutable()) {
if (canUseForInBitmap(item, options)) {
Log.v("TEST", "canUseForInBitmap!!!!");
bitmap = item;
// Remove from reusable set so it can't be used again
iterator.remove();
break;
}
} else {
// Remove from the set if the reference has been cleared.
iterator.remove();
}
}
}
return bitmap;
}
@TargetApi(VERSION_CODES.KITKAT)
private static boolean canUseForInBitmap(
Bitmap candidate, BitmapFactory.Options targetOptions) {
//4.4之前的版本,尺寸必须完全吻合
if (Build.VERSION.SDK_INT < VERSION_CODES.KITKAT) {
return candidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1;
}
//4.4版本,可以使用比自己大的bitmap
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
//根据图片格式,计算具体的bitmap大小
int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
return byteCount <= candidate.getAllocationByteCount();
}
/**
* Return the byte usage per pixel of a bitmap based on its configuration.
* @param config The bitmap configuration.
* @return The byte usage per pixel.
*/
private 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;
}
/**
*
* 放在no UI的fragment中,保证屏幕旋转时不被回收
* @param fragmentManager
* @param cacheParams
* @return
*/
public static ImageCache getInstance(
FragmentManager fragmentManager, ImageCacheParams cacheParams) {
final RetainFragment mRetainFragment = findOrCreateRetainFragment(fragmentManager);
ImageCache imageCache = (ImageCache) mRetainFragment.getObject();
if (imageCache == null) {
imageCache = new ImageCache(cacheParams);
mRetainFragment.setObject(imageCache);
}
return imageCache;
}
private ImageCache(ImageCacheParams cacheParams) {
init(cacheParams);
}
private void init(ImageCacheParams cacheParams) {
mCacheParams = cacheParams;
if (mCacheParams.memoryCacheEnabled) {
//Android3.0以上可以重用bitmap内存,将内存缓存中的回收项重用。
if (Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
mReusableBitmaps =
Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
}
mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
@Override
protected void entryRemoved(boolean evicted, String key,
BitmapDrawable oldValue, BitmapDrawable newValue) {
if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
//减少缓存计数
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
} else {
//加入待重用集合
if (Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));
}
}
}
@Override
protected int sizeOf(String key, BitmapDrawable bitmapDrawable) {
final int bitmapSize = getBitmapSize(bitmapDrawable) / 1024;
return bitmapSize == 0 ? 1 : bitmapSize;
}
};
}
}
/**
* 清空缓存
*/
public void clearCache() {
if (mMemoryCache != null) {
mMemoryCache.evictAll();
}
}
/**
* 获取bitmap大小
* @param bitmapDrawable
* @return
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
public static int getBitmapSize(BitmapDrawable bitmapDrawable) {
Bitmap bitmap = bitmapDrawable.getBitmap();
//4.4
if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
return bitmap.getAllocationByteCount();
}
//3.1及以上
if (Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
return bitmap.getByteCount();
}
//3.1之前的版本
return bitmap.getRowBytes() * bitmap.getHeight();
}
/**获取内存缓存中的图片
* @param url
* @return
*/
public BitmapDrawable getBitmapFromMemCache(String url) {
BitmapDrawable bitmapDrawable = null;
if (mMemoryCache != null) {
bitmapDrawable = mMemoryCache.get(url);
}
if (bitmapDrawable != null) {
Log.d("TEST", "Memory cache hit");
}
return bitmapDrawable;
}
/**
* 图片加入内存缓存
* @param data
* @param value
*/
public void addBitmapToCache(String url, BitmapDrawable bitmapDrawable) {
if (url == null || bitmapDrawable == null) {
return;
}
//先加入内存缓存
if (mMemoryCache != null) {
//如果是RecyclingBitmapDrawable,增加缓存计数
if (RecyclingBitmapDrawable.class.isInstance(bitmapDrawable)) {
((RecyclingBitmapDrawable) bitmapDrawable).setIsCached(true);
}
mMemoryCache.put(url, bitmapDrawable);
}
}
private static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
RetainFragment mRetainFragment = (RetainFragment) fm.findFragmentByTag(ImageCache.class.getName());
if (mRetainFragment == null) {
mRetainFragment = new RetainFragment();
fm.beginTransaction().add(mRetainFragment,
ImageCache.class.getName()).commitAllowingStateLoss();;
}
return mRetainFragment;
}
/**
* 后台Fragment用于在横竖屏切换时保存ImageCache对象。
*
*/
public static class RetainFragment extends Fragment {
private Object object;
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
}
}
Demo下载地址:
http://download.youkuaiyun.com/detail/ohehehou/8137871