kmccormick | 7571542 | 2013-03-04 15:25:40 -0800 | [diff] [blame^] | 1 | page.title=Managing Bitmap Memory |
| 2 | parent.title=Displaying Bitmaps Efficiently |
| 3 | parent.link=index.html |
| 4 | |
| 5 | trainingnavtop=true |
| 6 | |
| 7 | @jd:body |
| 8 | |
| 9 | <div id="tb-wrapper"> |
| 10 | <div id="tb"> |
| 11 | |
| 12 | <h2>This lesson teaches you to</h2> |
| 13 | <ol> |
| 14 | <li><a href="#recycle">Manage Memory on Android 2.3.3 and Lower</a></li> |
| 15 | <li><a href="#inBitmap">Manage Memory on Android 3.0 and Higher</a></li> |
| 16 | </ol> |
| 17 | |
| 18 | <h2>You should also read</h2> |
| 19 | <ul> |
| 20 | <li><a href="http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html">Memory Analysis for Android Applications</a> blog post</li> |
| 21 | <li><a href="http://www.google.com/events/io/2011/sessions/memory-management-for-android-apps.html">Memory management for Android Apps</a> Google I/O presentation</li> |
| 22 | <li><a href="{@docRoot}design/patterns/swipe-views.html">Android Design: Swipe Views</a></li> |
| 23 | <li><a href="{@docRoot}design/building-blocks/grid-lists.html">Android Design: Grid Lists</a></li> |
| 24 | </ul> |
| 25 | |
| 26 | <h2>Try it out</h2> |
| 27 | |
| 28 | <div class="download-box"> |
| 29 | <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> |
| 30 | <p class="filename">BitmapFun.zip</p> |
| 31 | </div> |
| 32 | |
| 33 | </div> |
| 34 | </div> |
| 35 | |
| 36 | <p>In addition to the steps described in <a href="cache-bitmap.html">Caching Bitmaps</a>, |
| 37 | there are specific things you can do to facilitate garbage collection |
| 38 | and bitmap reuse. The recommended strategy depends on which version(s) |
| 39 | of Android you are targeting. The {@code BitmapFun} sample app included with |
| 40 | this class shows you how to design your app to work efficiently across |
| 41 | different versions of Android.</p> |
| 42 | |
| 43 | <p>To set the stage for this lesson, here is how Android's management of |
| 44 | bitmap memory has evolved:</p> |
| 45 | <ul> |
| 46 | <li> |
| 47 | On Android Android 2.2 (API level 8) and lower, when garbage |
| 48 | collection occurs, your app's threads get stopped. This causes a lag that |
| 49 | can degrade performance. |
| 50 | <strong>Android 2.3 adds concurrent garbage collection, which means that |
| 51 | the memory is reclaimed soon after a bitmap is no longer referenced.</strong> |
| 52 | </li> |
| 53 | |
| 54 | <li>On Android 2.3.3 (API level 10) and lower, the backing pixel data for a |
| 55 | bitmap is stored in native memory. It is separate from the bitmap itself, |
| 56 | which is stored in the Dalvik heap. The pixel data in native memory is |
| 57 | not released in a predictable manner, potentially causing an application |
| 58 | to briefly exceed its memory limits and crash. |
| 59 | <strong>As of Android 3.0 (API Level 11), the pixel data is stored on the |
| 60 | Dalvik heap along with the associated bitmap.</strong></li> |
| 61 | |
| 62 | </ul> |
| 63 | |
| 64 | <p>The following sections describe how to optimize bitmap memory |
| 65 | management for different Android versions.</p> |
| 66 | |
| 67 | <h2 id="recycle">Manage Memory on Android 2.3.3 and Lower</h2> |
| 68 | |
| 69 | <p>On Android 2.3.3 (API level 10) and lower, using |
| 70 | {@link android.graphics.Bitmap#recycle recycle()} |
| 71 | is recommended. If you're displaying large amounts of bitmap data in your app, |
| 72 | you're likely to run into |
| 73 | {@link java.lang.OutOfMemoryError} errors. The |
| 74 | {@link android.graphics.Bitmap#recycle recycle()} method allows an app |
| 75 | to reclaim memory as soon as possible.</p> |
| 76 | |
| 77 | <p class="note"><strong>Caution:</strong> You should use |
| 78 | {@link android.graphics.Bitmap#recycle recycle()} only when you are sure that the |
| 79 | bitmap is no longer being used. If you call {@link android.graphics.Bitmap#recycle recycle()} |
| 80 | and later attempt to draw the bitmap, you will get the error: |
| 81 | {@code "Canvas: trying to use a recycled bitmap"}.</p> |
| 82 | |
| 83 | <p>The following code snippet gives an example of calling |
| 84 | {@link android.graphics.Bitmap#recycle recycle()}. It uses reference counting |
| 85 | (in the variables {@code mDisplayRefCount} and {@code mCacheRefCount}) to track |
| 86 | whether a bitmap is currently being displayed or in the cache. The |
| 87 | code recycles the bitmap when these conditions are met:</p> |
| 88 | |
| 89 | <ul> |
| 90 | <li>The reference count for both {@code mDisplayRefCount} and |
| 91 | {@code mCacheRefCount} is 0.</li> |
| 92 | <li>The bitmap is not {@code null}, and it hasn't been recycled yet.</li> |
| 93 | </ul> |
| 94 | |
| 95 | <pre>private int mCacheRefCount = 0; |
| 96 | private int mDisplayRefCount = 0; |
| 97 | ... |
| 98 | // Notify the drawable that the displayed state has changed. |
| 99 | // Keep a count to determine when the drawable is no longer displayed. |
| 100 | public void setIsDisplayed(boolean isDisplayed) { |
| 101 | synchronized (this) { |
| 102 | if (isDisplayed) { |
| 103 | mDisplayRefCount++; |
| 104 | mHasBeenDisplayed = true; |
| 105 | } else { |
| 106 | mDisplayRefCount--; |
| 107 | } |
| 108 | } |
| 109 | // Check to see if recycle() can be called. |
| 110 | checkState(); |
| 111 | } |
| 112 | |
| 113 | // Notify the drawable that the cache state has changed. |
| 114 | // Keep a count to determine when the drawable is no longer being cached. |
| 115 | public void setIsCached(boolean isCached) { |
| 116 | synchronized (this) { |
| 117 | if (isCached) { |
| 118 | mCacheRefCount++; |
| 119 | } else { |
| 120 | mCacheRefCount--; |
| 121 | } |
| 122 | } |
| 123 | // Check to see if recycle() can be called. |
| 124 | checkState(); |
| 125 | } |
| 126 | |
| 127 | private synchronized void checkState() { |
| 128 | // If the drawable cache and display ref counts = 0, and this drawable |
| 129 | // has been displayed, then recycle. |
| 130 | if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed |
| 131 | && hasValidBitmap()) { |
| 132 | getBitmap().recycle(); |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | private synchronized boolean hasValidBitmap() { |
| 137 | Bitmap bitmap = getBitmap(); |
| 138 | return bitmap != null && !bitmap.isRecycled(); |
| 139 | }</pre> |
| 140 | |
| 141 | <h2 id="inBitmap">Manage Memory on Android 3.0 and Higher</h2> |
| 142 | |
| 143 | <p>Android 3.0 (API Level 11) introduces the |
| 144 | {@link android.graphics.BitmapFactory.Options#inBitmap BitmapFactory.Options.inBitmap} |
| 145 | field. If this option is set, decode methods that take the |
| 146 | {@link android.graphics.BitmapFactory.Options Options} object |
| 147 | will attempt to reuse an existing bitmap when loading content. This means |
| 148 | that the bitmap's memory is reused, resulting in improved performance, and |
| 149 | removing both memory allocation and de-allocation. There are some caveats in using |
| 150 | {@link android.graphics.BitmapFactory.Options#inBitmap}:</p> |
| 151 | <ul> |
| 152 | <li>The reused bitmap must be of the same size as the source content (to make |
| 153 | sure that the same amount of memory is used), and in JPEG or PNG format |
| 154 | (whether as a resource or as a stream).</li> |
| 155 | |
| 156 | |
| 157 | <li>The {@link android.graphics.Bitmap.Config configuration} of the reused bitmap |
| 158 | overrides the setting of |
| 159 | {@link android.graphics.BitmapFactory.Options#inPreferredConfig}, if set. </li> |
| 160 | |
| 161 | <li>You should always use the returned bitmap of the decode method, |
| 162 | because you can't assume that reusing the bitmap worked (for example, if there is |
| 163 | a size mismatch).</li> |
| 164 | |
| 165 | <h3>Save a bitmap for later use</h3> |
| 166 | |
| 167 | <p>The following snippet demonstrates how an existing bitmap is stored for possible |
| 168 | later use in the sample app. When an app is running on Android 3.0 or higher and |
| 169 | a bitmap is evicted from the {@link android.util.LruCache}, |
| 170 | a soft reference to the bitmap is placed |
| 171 | in a {@link java.util.HashSet}, for possible reuse later with |
| 172 | {@link android.graphics.BitmapFactory.Options#inBitmap}: |
| 173 | |
| 174 | <pre>HashSet<SoftReference<Bitmap>> mReusableBitmaps; |
| 175 | private LruCache<String, BitmapDrawable> mMemoryCache; |
| 176 | |
| 177 | // If you're running on Honeycomb or newer, create |
| 178 | // a HashSet of references to reusable bitmaps. |
| 179 | if (Utils.hasHoneycomb()) { |
| 180 | mReusableBitmaps = new HashSet<SoftReference<Bitmap>>(); |
| 181 | } |
| 182 | |
| 183 | mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) { |
| 184 | |
| 185 | // Notify the removed entry that is no longer being cached. |
| 186 | @Override |
| 187 | protected void entryRemoved(boolean evicted, String key, |
| 188 | BitmapDrawable oldValue, BitmapDrawable newValue) { |
| 189 | if (RecyclingBitmapDrawable.class.isInstance(oldValue)) { |
| 190 | // The removed entry is a recycling drawable, so notify it |
| 191 | // that it has been removed from the memory cache. |
| 192 | ((RecyclingBitmapDrawable) oldValue).setIsCached(false); |
| 193 | } else { |
| 194 | // The removed entry is a standard BitmapDrawable. |
| 195 | if (Utils.hasHoneycomb()) { |
| 196 | // We're running on Honeycomb or later, so add the bitmap |
| 197 | // to a SoftReference set for possible use with inBitmap later. |
| 198 | mReusableBitmaps.add |
| 199 | (new SoftReference<Bitmap>(oldValue.getBitmap())); |
| 200 | } |
| 201 | } |
| 202 | } |
| 203 | .... |
| 204 | }</pre> |
| 205 | |
| 206 | |
| 207 | <h3>Use an existing bitmap</h3> |
| 208 | <p>In the running app, decoder methods check to see if there is an existing |
| 209 | bitmap they can use. For example:</p> |
| 210 | |
| 211 | <pre>public static Bitmap decodeSampledBitmapFromFile(String filename, |
| 212 | int reqWidth, int reqHeight, ImageCache cache) { |
| 213 | |
| 214 | final BitmapFactory.Options options = new BitmapFactory.Options(); |
| 215 | ... |
| 216 | BitmapFactory.decodeFile(filename, options); |
| 217 | ... |
| 218 | |
| 219 | // If we're running on Honeycomb or newer, try to use inBitmap. |
| 220 | if (Utils.hasHoneycomb()) { |
| 221 | addInBitmapOptions(options, cache); |
| 222 | } |
| 223 | ... |
| 224 | return BitmapFactory.decodeFile(filename, options); |
| 225 | }</pre |
| 226 | |
| 227 | <p>The next snippet shows the {@code addInBitmapOptions()} method that is called in the |
| 228 | above snippet. It looks for an existing bitmap to set as the value for |
| 229 | {@link android.graphics.BitmapFactory.Options#inBitmap}. Note that this |
| 230 | method only sets a value for {@link android.graphics.BitmapFactory.Options#inBitmap} |
| 231 | if it finds a suitable match (your code should never assume that a match will be found):</p> |
| 232 | |
| 233 | <pre>private static void addInBitmapOptions(BitmapFactory.Options options, |
| 234 | ImageCache cache) { |
| 235 | // inBitmap only works with mutable bitmaps, so force the decoder to |
| 236 | // return mutable bitmaps. |
| 237 | options.inMutable = true; |
| 238 | |
| 239 | if (cache != null) { |
| 240 | // Try to find a bitmap to use for inBitmap. |
| 241 | Bitmap inBitmap = cache.getBitmapFromReusableSet(options); |
| 242 | |
| 243 | if (inBitmap != null) { |
| 244 | // If a suitable bitmap has been found, set it as the value of |
| 245 | // inBitmap. |
| 246 | options.inBitmap = inBitmap; |
| 247 | } |
| 248 | } |
| 249 | } |
| 250 | |
| 251 | // This method iterates through the reusable bitmaps, looking for one |
| 252 | // to use for inBitmap: |
| 253 | protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) { |
| 254 | Bitmap bitmap = null; |
| 255 | |
| 256 | if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) { |
| 257 | final Iterator<SoftReference<Bitmap>> iterator |
| 258 | = mReusableBitmaps.iterator(); |
| 259 | Bitmap item; |
| 260 | |
| 261 | while (iterator.hasNext()) { |
| 262 | item = iterator.next().get(); |
| 263 | |
| 264 | if (null != item && item.isMutable()) { |
| 265 | // Check to see it the item can be used for inBitmap. |
| 266 | if (canUseForInBitmap(item, options)) { |
| 267 | bitmap = item; |
| 268 | |
| 269 | // Remove from reusable set so it can't be used again. |
| 270 | iterator.remove(); |
| 271 | break; |
| 272 | } |
| 273 | } else { |
| 274 | // Remove from the set if the reference has been cleared. |
| 275 | iterator.remove(); |
| 276 | } |
| 277 | } |
| 278 | } |
| 279 | return bitmap; |
| 280 | }</pre> |
| 281 | |
| 282 | <p>Finally, this method determines whether a candidate bitmap |
| 283 | satisfies the size criteria to be used for |
| 284 | {@link android.graphics.BitmapFactory.Options#inBitmap}:</p> |
| 285 | |
| 286 | <pre>private static boolean canUseForInBitmap( |
| 287 | Bitmap candidate, BitmapFactory.Options targetOptions) { |
| 288 | int width = targetOptions.outWidth / targetOptions.inSampleSize; |
| 289 | int height = targetOptions.outHeight / targetOptions.inSampleSize; |
| 290 | |
| 291 | // Returns true if "candidate" can be used for inBitmap re-use with |
| 292 | // "targetOptions". |
| 293 | return candidate.getWidth() == width && candidate.getHeight() == height; |
| 294 | }</pre> |
| 295 | |
| 296 | </body> |
| 297 | </html> |