这篇文章其实也可以起另外一个标题:android 如何高效的展示图片


学习和使用常见的技术去处理和加载图片,能让你的用户界面快速响应,并且能避免动不动就超出了受限内存。可能你一不小心,就会导致内存消耗完毕从而crash

,抛出java.lang.OutofMemoryError: bitmap size exceeds VM budget,在应用程序中加载图片是个棘手的问题,主要包括以下几个原因:


1.移动设备通常有约束的系统资源,android 设备中可能在一个应用里只有16M的可用内存。Android兼容定义文档(CDD),3.7节,指出了在

不同屏幕大小和密度所需的最小应用程序内存。应用程序应该优化过并且在这个最小的内存限制之下执行。当然,请记住许多设备配置更高的限制。

2.图片需要消耗大量的内存,尤其是丰富的图像比如照片。例如,摄像头拍照的Galaxy Nexus 2592 x1936像素(像素)。
如果位图配置使用ARGB 8888(默认从Android 2.3起)然后加载这个图像到内存需要大约19 mb的内存(2592 * 1936 * 4字节),在一些设备会立即消耗完每个应用拥有的内存。


3.android App 的UI 可能需要频繁的将多个图片一次性加载


内容分为五个部分,分别是:

1,高效的加载大图片:在不会超过单个应用限制内存这一条例下进行decode bitmap.

2, 异步加载图片:通过AsyncTask 异步处理图片,同时也提一下如何处理并发的情况。

3. 缓存图片:通过内存和磁盘缓存,来提高UI的响应速度和流程性。

4. 内存管理:如何管理位图内存来最大化你的应用程序的性能。

4. 在你的UI上展示图片:用例子说明如何在ViewPager和GridView中 使用后台线程来和图片缓存来加载图片。


一、高效的加载大图片

在decode一张Bitmap之前,先检查这张图片的尺寸(除非你明确的知道这张图片的尺寸,并且确定一定不会产生内存溢出),可以设置BitmapFactory.Options对象options的属性inJustDecodeBounds为true,因为他不会去开辟内存生成一张图片,却能够知道图片的宽度和高度


[java] view plain copy


    1. BitmapFactory.Options options = new BitmapFactory.Options();  
    2. options.inJustDecodeBounds = true;  
    3. BitmapFactory.decodeResource(getResources(), R.id.myimage, options);  
    4. int imageHeight = options.outHeight;  
    5. int imageWidth = options.outWidth;  
    6. String imageType = options.outMimeType;

    在知道宽度和高度后,可根据需要生成采样率(说明一点:如果生成的采样率是2的幂,如2,4,8,16...那么解码一张图片将会更快更高效)

    [java] view plain copy


      1. public static int calculateInSampleSize(  
      2. int reqWidth, int reqHeight) {  
      3. // Raw height and width of image  
      4. final int height = options.outHeight;  
      5. final int width = options.outWidth;  
      6. int inSampleSize = 1;  
      7.   
      8. if (height > reqHeight || width > reqWidth) {  
      9.   
      10. // Calculate ratios of height and width to requested height and width  
      11. final int heightRatio = Math.round((float) height / (float) reqHeight);  
      12. final int widthRatio = Math.round((float) width / (float) reqWidth);  
      13.   
      14. // Choose the smallest ratio as inSampleSize value, this will guarantee  
      15. // a final image with both dimensions larger than or equal to the  
      16. // requested height and width.  
      17.         inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;  
      18.     }  
      19.   
      20. return inSampleSize;  
      21. }


      在知道采样率后,就可以生成图片了(这时要设置inSampleSize=获取的采样率,inJustDecodeBounds=false)


      [java] view plain copy


      1. public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,  
      2. int reqWidth, int reqHeight) {  
      3.   
      4. // First decode with inJustDecodeBounds=true to check dimensions  
      5. final BitmapFactory.Options options = new BitmapFactory.Options();  
      6. true;  
      7.     BitmapFactory.decodeResource(res, resId, options);  
      8.   
      9. // Calculate inSampleSize  
      10.     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
      11.   
      12. // Decode bitmap with inSampleSize set  
      13. false;  
      14. return BitmapFactory.decodeResource(res, resId, options);  
      15. }


      在获取图片后,就可以设置UI组件的图片相关属性(这里是举例,通常在UI中需要异步回调进行设置,而不是直接在UI线程中设置,如果需要在SD卡中读取,或者网络读取的话,会因为耗时导致阻塞UI线程,从而产出ANR):

      [java] view plain copy


        1. mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));


        二、异步加载图片


        [java] view plain copy

        1. class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {  
        2. private final WeakReference<ImageView> imageViewReference;  
        3. private int data = 0;  
        4.   
        5. public BitmapWorkerTask(ImageView imageView) {  
        6. // Use a WeakReference to ensure the ImageView can be garbage collected  
        7. new WeakReference<ImageView>(imageView);  
        8.     }  
        9.   
        10. // Decode image in background.  
        11. @Override  
        12. protected Bitmap doInBackground(Integer... params) {  
        13. 0];  
        14. return decodeSampledBitmapFromResource(getResources(), data, 100, 100));  
        15.     }  
        16.   
        17. // Once complete, see if ImageView is still around and set bitmap.  
        18. @Override  
        19. protected void onPostExecute(Bitmap bitmap) {  
        20. if (imageViewReference != null && bitmap != null) {  
        21. final ImageView imageView = imageViewReference.get();  
        22. if (imageView != null) {  
        23.                 imageView.setImageBitmap(bitmap);  
        24.             }  
        25.         }  
        26.     }  
        27. }

        注意:这里的ImageView 使用弱引用的目的 是为了确保AsyncTask不会阻止ImageView或者它引用的资源被系统回收


        所以要异步加载一张图片很简单了,调用如下即可:

        [java] view plain copy


          1. public void loadBitmap(int resId, ImageView imageView) {  
          2. new BitmapWorkerTask(imageView);  
          3.     task.execute(resId);  
          4. }

          上面主要是针对普通的View ,但是ListView,GridView,ViewPage等这些滚动时有重用自己的child view又该怎么办呢?因为如果每一个child View 都触发一个AsyncTask的话,就无法

          保证一种情况:AsyncTask已经完成,但与之相关连的child View却没有被回收,转而被重用了,你这样设置的话,显示的图片不是会错了么。。。就是顺序无法保证一致。

          这种情况下的解决方案是:ImageView存储一个最近的AsyncTask的引用,并且在完成的时候再次判断一下,不就可以了吗!


          仿照AsyncTask存储一个ImageView软应用的方法,我们可以自定义一个Drawable,也存储一个AsyncTask的软应用,使用了相同的思想



          [java] view plain copy


          1. static class AsyncDrawable extends BitmapDrawable {  
          2. private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;  
          3.   
          4. public AsyncDrawable(Resources res, Bitmap bitmap,  
          5.             BitmapWorkerTask bitmapWorkerTask) {  
          6. super(res, bitmap);  
          7.         bitmapWorkerTaskReference =  
          8. new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);  
          9.     }  
          10.   
          11. public BitmapWorkerTask getBitmapWorkerTask() {  
          12. return bitmapWorkerTaskReference.get();  
          13.     }  
          14. }

          在执行AsyncTask之前,我们new AsyncDrawable 绑定到ImageView


          [java] view plain copy


          1. public void loadBitmap(int resId, ImageView imageView) {  
          2. if (cancelPotentialWork(resId, imageView)) {  
          3. final BitmapWorkerTask task = new BitmapWorkerTask(imageView);  
          4. final AsyncDrawable asyncDrawable =  
          5. new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);  
          6.         imageView.setImageDrawable(asyncDrawable);  
          7.         task.execute(resId);  
          8.     }  
          9. }
          1.  

          绑定之前,先清掉以前的Task即可:


          [java] view plain copy


            1. public static boolean cancelPotentialWork(int data, ImageView imageView) {  
            2. final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);  
            3.   
            4. if (bitmapWorkerTask != null) {  
            5. final int bitmapData = bitmapWorkerTask.data;  
            6. if (bitmapData != data) {  
            7. // Cancel previous task  
            8. true);  
            9. else {  
            10. // The same work is already in progress  
            11. return false;  
            12.         }  
            13.     }  
            14. // No task associated with the ImageView, or an existing task was cancelled  
            15. return true;  
            16. }


            [java] view plain copy

            1. private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {  
            2. if (imageView != null) {  
            3. final Drawable drawable = imageView.getDrawable();  
            4. if (drawable instanceof AsyncDrawable) {  
            5. final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;  
            6. return asyncDrawable.getBitmapWorkerTask();  
            7.        }  
            8.     }  
            9. return null;  
            10. }

            所以在你的BitmapWorkerTask 中onPostExecute() 需要再次检查当前的task是不是ImageView相匹配的Task



            [java] view plain copy


              1. class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {  
              2.     ...  
              3.   
              4. @Override  
              5. protected void onPostExecute(Bitmap bitmap) {  
              6. if (isCancelled()) {  
              7. null;  
              8.         }  
              9.   
              10. if (imageViewReference != null && bitmap != null) {  
              11. final ImageView imageView = imageViewReference.get();  
              12. final BitmapWorkerTask bitmapWorkerTask =  
              13.                     getBitmapWorkerTask(imageView);  
              14. if (this == bitmapWorkerTask && imageView != null) {  
              15.                 imageView.setImageBitmap(bitmap);  
              16.             }  
              17.         }  
              18.     }  
              19. }

              这个时候你就可以在ListView,GridView的getView()方法中执行loadBitmap方法即可了。


              三、缓存图片


              a.内存缓存。就是使用我们常说的LRU进行缓存了。

              这里注意一点:在过去,很流行使用软应用和若应用来缓存图片,但是现在已经不推荐使用了,因为从Android 2.3(API级别9)以后,垃圾收集器是更积极收集软/弱引用,这使得它们相当无效,

              Android 3.0(API级别11) 之前,支持数据的位图存储在本地内存(而不是虚拟机内存),并且不是显示方式释放,可能导致应用程序超过其内存限制和崩溃。


              如何为LruCahce选定一个合适的大小,需要考虑一系列的因素,如:

              1.内存是如何加强你的activity/application.

              2.有多少图片需要马上显示,有多少图片准备显示。

              3.设备的尺寸和密度是什么,高密度设备在缓存相同数量的图片需要的更大的缓存

              4.图片的尺寸和配置是什么,需要消耗多少的内存。

              5.你是否访问频繁?如果频繁访问,你可能需要将某些图片内存常驻,或者使用多个LruCache(不同场合不同大小不同定义)

              6.需要平衡数量与质量。

              所以没有一个特定的大小或者适合所有的解决方案,得自己去分析APP,综合想出一个解决方案,缓存太小,导致额外的开销,缓存太大,容易再次使内存溢出或者留下很少的内存供给其它的用途。


              这里举个例子:


              [java] view plain copy


                1. private LruCache<String, Bitmap> mMemoryCache;  
                2.   
                3. @Override  
                4. protected void onCreate(Bundle savedInstanceState) {  
                5.     ...  
                6. // Get max available VM memory, exceeding this amount will throw an  
                7. // OutOfMemory exception. Stored in kilobytes as LruCache takes an  
                8. // int in its constructor.  
                9. final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);  
                10.   
                11. // Use 1/8th of the available memory for this memory cache.  
                12. final int cacheSize = maxMemory / 8;  
                13.   
                14. new LruCache<String, Bitmap>(cacheSize) {  
                15. @Override  
                16. protected int sizeOf(String key, Bitmap bitmap) {  
                17. // The cache size will be measured in kilobytes rather than  
                18. // number of items.  
                19. return bitmap.getByteCount() / 1024;  
                20.         }  
                21.     };  
                22.     ...  
                23. }  
                24.   
                25. public void addBitmapToMemoryCache(String key, Bitmap bitmap) {  
                26. if (getBitmapFromMemCache(key) == null) {  
                27.         mMemoryCache.put(key, bitmap);  
                28.     }  
                29. }  
                30.   
                31. public Bitmap getBitmapFromMemCache(String key) {  
                32. return mMemoryCache.get(key);  
                33. }


                所以之前的loadBitmap方法,使用LRUCache,就可以先check一下:


                [java] view plain copy


                1. public void loadBitmap(int resId, ImageView imageView) {  
                2. final String imageKey = String.valueOf(resId);  
                3.   
                4. final Bitmap bitmap = getBitmapFromMemCache(imageKey);  
                5. if (bitmap != null) {  
                6.         mImageView.setImageBitmap(bitmap);  
                7. else {  
                8.         mImageView.setImageResource(R.drawable.image_placeholder);  
                9. new BitmapWorkerTask(mImageView);  
                10.         task.execute(resId);  
                11.     }  
                12. }


                同时BitmapWorkerTask也需要更新LRUCache:


                [java] view plain copy


                1. class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {  
                2.     ...  
                3. // Decode image in background.  
                4. @Override  
                5. protected Bitmap doInBackground(Integer... params) {  
                6. final Bitmap bitmap = decodeSampledBitmapFromResource(  
                7. 0], 100, 100));  
                8. 0]), bitmap);  
                9. return bitmap;  
                10.     }  
                11.     ...  
                12. }

                b.DISK卡缓存

                注意:如果访问特别的频繁,ContentProvider可能是一个合适的地方存储缓存的图片。

                DiskLruCache的例子:


                [java] view plain copy

                内存缓存检查在UI线程中,Disk卡缓存检查在非UI线程中,但是图片完成后,内存缓存和Disk缓存都需要添加。

                1. private DiskLruCache mDiskLruCache;  
                2. private final Object mDiskCacheLock = new Object();  
                3. private boolean mDiskCacheStarting = true;  
                4. private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB  
                5. private static final String DISK_CACHE_SUBDIR = "thumbnails";  
                6.   
                7. @Override  
                8. protected void onCreate(Bundle savedInstanceState) {  
                9.     ...  
                10. // Initialize memory cache  
                11.     ...  
                12. // Initialize disk cache on background thread  
                13. this, DISK_CACHE_SUBDIR);  
                14. new InitDiskCacheTask().execute(cacheDir);  
                15.     ...  
                16. }  
                17.   
                18. class InitDiskCacheTask extends AsyncTask<File, Void, Void> {  
                19. @Override  
                20. protected Void doInBackground(File... params) {  
                21. synchronized (mDiskCacheLock) {  
                22. 0];  
                23.             mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);  
                24. false; // Finished initialization  
                25. // Wake any waiting threads  
                26.         }  
                27. return null;  
                28.     }  
                29. }  
                30.   
                31. class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {  
                32.     ...  
                33. // Decode image in background.  
                34. @Override  
                35. protected Bitmap doInBackground(Integer... params) {  
                36. final String imageKey = String.valueOf(params[0]);  
                37.   
                38. // Check disk cache in background thread  
                39.         Bitmap bitmap = getBitmapFromDiskCache(imageKey);  
                40.   
                41. if (bitmap == null) { // Not found in disk cache  
                42. // Process as normal  
                43. final Bitmap bitmap = decodeSampledBitmapFromResource(  
                44. 0], 100, 100));  
                45.         }  
                46.   
                47. // Add final bitmap to caches  
                48.         addBitmapToCache(imageKey, bitmap);  
                49.   
                50. return bitmap;  
                51.     }  
                52.     ...  
                53. }  
                54.   
                55. public void addBitmapToCache(String key, Bitmap bitmap) {  
                56. // Add to memory cache as before  
                57. if (getBitmapFromMemCache(key) == null) {  
                58.         mMemoryCache.put(key, bitmap);  
                59.     }  
                60.   
                61. // Also add to disk cache  
                62. synchronized (mDiskCacheLock) {  
                63. if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {  
                64.             mDiskLruCache.put(key, bitmap);  
                65.         }  
                66.     }  
                67. }  
                68.   
                69. public Bitmap getBitmapFromDiskCache(String key) {  
                70. synchronized (mDiskCacheLock) {  
                71. // Wait while disk cache is started from background thread  
                72. while (mDiskCacheStarting) {  
                73. try {  
                74.                 mDiskCacheLock.wait();  
                75. catch (InterruptedException e) {}  
                76.         }  
                77. if (mDiskLruCache != null) {  
                78. return mDiskLruCache.get(key);  
                79.         }  
                80.     }  
                81. return null;  
                82. }  
                83.   
                84. // Creates a unique subdirectory of the designated app cache directory. Tries to use external  
                85. // but if not mounted, falls back on internal storage.  
                86. public static File getDiskCacheDir(Context context, String uniqueName) {  
                87. // Check if media is mounted or storage is built-in, if so, try and use external cache dir  
                88. // otherwise use internal cache dir  
                89. final String cachePath =  
                90.             Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||  
                91.                     !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :  
                92.                             context.getCacheDir().getPath();  
                93.   
                94. return new File(cachePath + File.separator + uniqueName);  
                95. }


                举个例子,当Configuration 改变时候,如横竖屏切换。


                [java] view plain copy

                1. private LruCache<String, Bitmap> mMemoryCache;  
                2.   
                3. @Override  
                4. protected void onCreate(Bundle savedInstanceState) {  
                5.     ...  
                6.     RetainFragment retainFragment =  
                7.             RetainFragment.findOrCreateRetainFragment(getFragmentManager());  
                8.     mMemoryCache = retainFragment.mRetainedCache;  
                9. if (mMemoryCache == null) {  
                10. new LruCache<String, Bitmap>(cacheSize) {  
                11. // Initialize cache here as usual  
                12.         }  
                13.         retainFragment.mRetainedCache = mMemoryCache;  
                14.     }  
                15.     ...  
                16. }  
                17.   
                18. class RetainFragment extends Fragment {  
                19. private static final String TAG = "RetainFragment";  
                20. public LruCache<String, Bitmap> mRetainedCache;  
                21.   
                22. public RetainFragment() {}  
                23.   
                24. public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {  
                25.         RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);  
                26. if (fragment == null) {  
                27. new RetainFragment();  
                28.         }  
                29. return fragment;  
                30.     }  
                31.   
                32. @Override  
                33. public void onCreate(Bundle savedInstanceState) {  
                34. super.onCreate(savedInstanceState);  
                35. true);  
                36.     }  
                37. }

                四、内存管理



                主要讲的是促进垃圾回收期回收,和使图片重用。

                先讲下关于版本的一些变化知识:

                1.在Android上Android 2.2(API级别8)和低版本时,当垃圾收集发生时,您的应用程序的线程被暂停,这导致滞后,降低性能。Android 2.3增加了并发垃圾收集,这意味着内存位图不再被引用不久后就会被回收。

                2.在安卓2.3.3(API级别10)和低版本,位图像素数据存储在Native内存。它是独立于位图本身(存储在Dalvik堆)。像素数据在Native内存不是显式的方式释放,可能导致应用程序超过其内存限制和崩溃。在Android 3.0(API级别11),像素数据连同相关的位图存储在Dalvik堆。


                2.3.3以及以前的优化:

                通过引用计数的方式来判断图片是否可以回收了。


                [java] view plain copy

                3.0以及以后版本的优化:

                1. private int mCacheRefCount = 0;  
                2. private int mDisplayRefCount = 0;  
                3. ...  
                4. // Notify the drawable that the displayed state has changed.  
                5. // Keep a count to determine when the drawable is no longer displayed.  
                6. public void setIsDisplayed(boolean isDisplayed) {  
                7. synchronized (this) {  
                8. if (isDisplayed) {  
                9.             mDisplayRefCount++;  
                10. true;  
                11. else {  
                12.             mDisplayRefCount--;  
                13.         }  
                14.     }  
                15. // Check to see if recycle() can be called.  
                16.     checkState();  
                17. }  
                18.   
                19. // Notify the drawable that the cache state has changed.  
                20. // Keep a count to determine when the drawable is no longer being cached.  
                21. public void setIsCached(boolean isCached) {  
                22. synchronized (this) {  
                23. if (isCached) {  
                24.             mCacheRefCount++;  
                25. else {  
                26.             mCacheRefCount--;  
                27.         }  
                28.     }  
                29. // Check to see if recycle() can be called.  
                30.     checkState();  
                31. }  
                32.   
                33. private synchronized void checkState() {  
                34. // If the drawable cache and display ref counts = 0, and this drawable  
                35. // has been displayed, then recycle.  
                36. if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed  
                37.             && hasValidBitmap()) {  
                38.         getBitmap().recycle();  
                39.     }  
                40. }  
                41.   
                42. private synchronized boolean hasValidBitmap() {  
                43.     Bitmap bitmap = getBitmap();  
                44. return bitmap != null && !bitmap.isRecycled();  
                45. }

                3.0以后引进了BitmapFactory.Options.inBitmap 这个属性,如果option设置了这个属性的话,当load一张图片的时候,它将尝试去复用一张已经存在的图片:

                就是复用之前那种图片的内存,而不用频繁的去开辟/回收内存,从而提高了效率。

                当然是有条件的:复用图片的大小必须和新生成的图片大小一致(确保所占用的内存一致)


                [java] view plain copy


                1. HashSet<SoftReference<Bitmap>> mReusableBitmaps;  
                2. private LruCache<String, BitmapDrawable> mMemoryCache;  
                3.   
                4. // If you're running on Honeycomb or newer, create  
                5. // a HashSet of references to reusable bitmaps.  
                6. if (Utils.hasHoneycomb()) {  
                7. new HashSet<SoftReference<Bitmap>>();  
                8. }  
                9.   
                10. mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {  
                11.   
                12. // Notify the removed entry that is no longer being cached.  
                13. @Override  
                14. protected void entryRemoved(boolean evicted, String key,  
                15.             BitmapDrawable oldValue, BitmapDrawable newValue) {  
                16. if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {  
                17. // The removed entry is a recycling drawable, so notify it  
                18. // that it has been removed from the memory cache.  
                19. false);  
                20. else {  
                21. // The removed entry is a standard BitmapDrawable.  
                22. if (Utils.hasHoneycomb()) {  
                23. // We're running on Honeycomb or later, so add the bitmap  
                24. // to a SoftReference set for possible use with inBitmap later.  
                25.                 mReusableBitmaps.add  
                26. new SoftReference<Bitmap>(oldValue.getBitmap()));  
                27.             }  
                28.         }  
                29.     }  
                30. ....  
                31. }


                [java] view plain copy


                  1. public static Bitmap decodeSampledBitmapFromFile(String filename,  
                  2. int reqWidth, int reqHeight, ImageCache cache) {  
                  3.   
                  4. final BitmapFactory.Options options = new BitmapFactory.Options();  
                  5.     ...  
                  6.     BitmapFactory.decodeFile(filename, options);  
                  7.     ...  
                  8.   
                  9. // If we're running on Honeycomb or newer, try to use inBitmap.  
                  10. if (Utils.hasHoneycomb()) {  
                  11.         addInBitmapOptions(options, cache);  
                  12.     }  
                  13.     ...  
                  14. return BitmapFactory.decodeFile(filename, options);  
                  15. }

                  [java] view plain copy


                  1. private static void addInBitmapOptions(BitmapFactory.Options options,  
                  2.         ImageCache cache) {  
                  3. // inBitmap only works with mutable bitmaps, so force the decoder to  
                  4. // return mutable bitmaps.  
                  5. true;  
                  6.   
                  7. if (cache != null) {  
                  8. // Try to find a bitmap to use for inBitmap.  
                  9.         Bitmap inBitmap = cache.getBitmapFromReusableSet(options);  
                  10.   
                  11. if (inBitmap != null) {  
                  12. // If a suitable bitmap has been found, set it as the value of  
                  13. // inBitmap.  
                  14.             options.inBitmap = inBitmap;  
                  15.         }  
                  16.     }  
                  17. }  
                  18.   
                  19. // This method iterates through the reusable bitmaps, looking for one   
                  20. // to use for inBitmap:  
                  21. protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {  
                  22. null;  
                  23.   
                  24. if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {  
                  25. final Iterator<SoftReference<Bitmap>> iterator  
                  26.                 = mReusableBitmaps.iterator();  
                  27.         Bitmap item;  
                  28.   
                  29. while (iterator.hasNext()) {  
                  30.             item = iterator.next().get();  
                  31.   
                  32. if (null != item && item.isMutable()) {  
                  33. // Check to see it the item can be used for inBitmap.  
                  34. if (canUseForInBitmap(item, options)) {  
                  35.                     bitmap = item;  
                  36.   
                  37. // Remove from reusable set so it can't be used again.  
                  38.                     iterator.remove();  
                  39. break;  
                  40.                 }  
                  41. else {  
                  42. // Remove from the set if the reference has been cleared.  
                  43.                 iterator.remove();  
                  44.             }  
                  45.         }  
                  46.     }  
                  47. return bitmap;  
                  48. }


                  [java] view plain copy

                  1. private static boolean canUseForInBitmap(  
                  2.         Bitmap candidate, BitmapFactory.Options targetOptions) {  
                  3. int width = targetOptions.outWidth / targetOptions.inSampleSize;  
                  4. int height = targetOptions.outHeight / targetOptions.inSampleSize;  
                  5.   
                  6. // Returns true if "candidate" can be used for inBitmap re-use with  
                  7. // "targetOptions".  
                  8. return candidate.getWidth() == width && candidate.getHeight() == height;  
                  9. }