BitMap高效显示策略(三):使用内存缓存技术和BitmapFactory.Options.inBitmap参数
来源:互联网 发布:linux aria2 多线程 编辑:程序博客网 时间:2024/06/05 18:03
接上篇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; // 内存缓存默认大小5MBprivate static final boolean DEFAULT_MEM_CACHE_ENABLED = true;//内存缓存private LruCache<String, BitmapDrawable> mMemoryCache;private ImageCacheParams mCacheParams;public static class ImageCacheParams {//mempublic 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) {@Overrideprotected void entryRemoved(boolean evicted, String key,BitmapDrawable oldValue, BitmapDrawable newValue) {if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {//减少缓存计数((RecyclingBitmapDrawable) oldValue).setIsCached(false);}}@Overrideprotected 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; // 内存缓存默认大小5MBprivate 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 {//mempublic 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版本,可以使用比自己大的bitmapint 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) {@Overrideprotected 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()));}}}@Overrideprotected 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.csdn.net/detail/ohehehou/8137871- BitMap高效显示策略(三):使用内存缓存技术和BitmapFactory.Options.inBitmap参数
- 【Android】【缓存】Andrid高效显示Bitmap( 使用缓存技术)
- Android(decode文件转成bitmap)使用BitmapFactory.Options解决加载大图片内存溢出
- BitMap高效显示策略(四):使用DiskLruCache作为硬盘缓存
- Bitmap——BitmapFactory.Options的使用
- Android 管理Bitmap内存 及 Bitmap.Config BitmapFactory.Options 说明
- Bitmap与BitmapFactory.Options
- Bitmap内存优化--使用BitmapFactory.options及SoftReference解决OutOfMemory问题
- Bitmap显示缩略图动态比例显示BitmapFactory.Options
- Android Training - 高效地显示Bitmap(两种缓存Bitmap的方式)与优化Bitmap的内存使用
- Bitmap和BitmapFactory对象使用
- bitmap处理BitmapFactory.Options.inSampleSize
- bitmap处理BitmapFactory.Options.inSampleSize
- Bitmap——BitmapFactory.Options
- 高效显示Bitmap之缓存Bitmap
- Android进阶练习 - 高效显示Bitmap(管理Bitmap内存)
- Android进阶练习 - 高效显示Bitmap(管理Bitmap内存)
- Android进阶练习 - 高效显示Bitmap(管理Bitmap内存)
- Deep Learning and Shallow Learning
- JNI设置C++与java的结合(2)
- [LeetCode]Search in Rotated Sorted Array
- 计算机视觉、机器学习相关领域论文和源代码大集合
- linux 线程切换效率与进程切换效率相差到底有多大?
- BitMap高效显示策略(三):使用内存缓存技术和BitmapFactory.Options.inBitmap参数
- lucene索引删除,恢复,更新
- android手机摄像:硬编码、软编码实验结果
- linux内核网络协议栈学习笔记:关于GRO/GSO/LRO/TSO等patch的分析和测
- poj1019 Number Sequence
- GDB调试指令
- C++Primer3.6练习题。
- POJ1664放苹果http://poj.org/problem?id=1664
- A fresh start