page.title=Managing Bitmap Memory parent.title=Displaying Bitmaps Efficiently parent.link=index.html trainingnavtop=true @jd:body <div id="tb-wrapper"> <div id="tb"> <h2>This lesson teaches you to</h2> <ol> <li><a href="#recycle">Manage Memory on Android 2.3.3 and Lower</a></li> <li><a href="#inBitmap">Manage Memory on Android 3.0 and Higher</a></li> </ol> <h2>You should also read</h2> <ul> <li><a href="http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html">Memory Analysis for Android Applications</a> blog post</li> <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> <li><a href="{@docRoot}design/patterns/swipe-views.html">Android Design: Swipe Views</a></li> <li><a href="{@docRoot}design/building-blocks/grid-lists.html">Android Design: Grid Lists</a></li> </ul> <h2>Try it out</h2> <div class="download-box"> <a href="{@docRoot}downloads/samples/DisplayingBitmaps.zip" class="button">Download the sample</a> <p class="filename">DisplayingBitmaps.zip</p> </div> </div> </div> <p>In addition to the steps described in <a href="cache-bitmap.html">Caching Bitmaps</a>, there are specific things you can do to facilitate garbage collection and bitmap reuse. The recommended strategy depends on which version(s) of Android you are targeting. The {@code BitmapFun} sample app included with this class shows you how to design your app to work efficiently across different versions of Android.</p> <p>To set the stage for this lesson, here is how Android's management of bitmap memory has evolved:</p> <ul> <li> On Android Android 2.2 (API level 8) and lower, when garbage collection occurs, your app's threads get stopped. This causes a lag that can degrade performance. <strong>Android 2.3 adds concurrent garbage collection, which means that the memory is reclaimed soon after a bitmap is no longer referenced.</strong> </li> <li>On Android 2.3.3 (API level 10) and lower, the backing pixel data for a bitmap is stored in native memory. It is separate from the bitmap itself, which is stored in the Dalvik heap. The pixel data in native memory is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash. <strong>As of Android 3.0 (API level 11), the pixel data is stored on the Dalvik heap along with the associated bitmap.</strong></li> </ul> <p>The following sections describe how to optimize bitmap memory management for different Android versions.</p> <h2 id="recycle">Manage Memory on Android 2.3.3 and Lower</h2> <p>On Android 2.3.3 (API level 10) and lower, using {@link android.graphics.Bitmap#recycle recycle()} is recommended. If you're displaying large amounts of bitmap data in your app, you're likely to run into {@link java.lang.OutOfMemoryError} errors. The {@link android.graphics.Bitmap#recycle recycle()} method allows an app to reclaim memory as soon as possible.</p> <p class="note"><strong>Caution:</strong> You should use {@link android.graphics.Bitmap#recycle recycle()} only when you are sure that the bitmap is no longer being used. If you call {@link android.graphics.Bitmap#recycle recycle()} and later attempt to draw the bitmap, you will get the error: <code>"Canvas: trying to use a recycled bitmap"</code>.</p> <p>The following code snippet gives an example of calling {@link android.graphics.Bitmap#recycle recycle()}. It uses reference counting (in the variables {@code mDisplayRefCount} and {@code mCacheRefCount}) to track whether a bitmap is currently being displayed or in the cache. The code recycles the bitmap when these conditions are met:</p> <ul> <li>The reference count for both {@code mDisplayRefCount} and {@code mCacheRefCount} is 0.</li> <li>The bitmap is not {@code null}, and it hasn't been recycled yet.</li> </ul> <pre>private int mCacheRefCount = 0; private int mDisplayRefCount = 0; ... // Notify the drawable that the displayed state has changed. // Keep a count to determine when the drawable is no longer displayed. public void setIsDisplayed(boolean isDisplayed) { synchronized (this) { if (isDisplayed) { mDisplayRefCount++; mHasBeenDisplayed = true; } else { mDisplayRefCount--; } } // Check to see if recycle() can be called. checkState(); } // Notify the drawable that the cache state has changed. // Keep a count to determine when the drawable is no longer being cached. public void setIsCached(boolean isCached) { synchronized (this) { if (isCached) { mCacheRefCount++; } else { mCacheRefCount--; } } // Check to see if recycle() can be called. checkState(); } private synchronized void checkState() { // If the drawable cache and display ref counts = 0, and this drawable // has been displayed, then recycle. if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed && hasValidBitmap()) { getBitmap().recycle(); } } private synchronized boolean hasValidBitmap() { Bitmap bitmap = getBitmap(); return bitmap != null && !bitmap.isRecycled(); }</pre> <h2 id="inBitmap">Manage Memory on Android 3.0 and Higher</h2> <p>Android 3.0 (API level 11) introduces the {@link android.graphics.BitmapFactory.Options#inBitmap BitmapFactory.Options.inBitmap} field. If this option is set, decode methods that take the {@link android.graphics.BitmapFactory.Options Options} object will attempt to reuse an existing bitmap when loading content. This means that the bitmap's memory is reused, resulting in improved performance, and removing both memory allocation and de-allocation. However, there are certain restrictions with how {@link android.graphics.BitmapFactory.Options#inBitmap} can be used. In particular, before Android 4.4 (API level 19), only equal sized bitmaps are supported. For details, please see the {@link android.graphics.BitmapFactory.Options#inBitmap} documentation. <h3>Save a bitmap for later use</h3> <p>The following snippet demonstrates how an existing bitmap is stored for possible later use in the sample app. When an app is running on Android 3.0 or higher and a bitmap is evicted from the {@link android.util.LruCache}, a soft reference to the bitmap is placed in a {@link java.util.HashSet}, for possible reuse later with {@link android.graphics.BitmapFactory.Options#inBitmap}: <pre>Set<SoftReference<Bitmap>> mReusableBitmaps; private LruCache<String, BitmapDrawable> mMemoryCache; // If you're running on Honeycomb or newer, create a // synchronized HashSet of references to reusable bitmaps. 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)) { // The removed entry is a recycling drawable, so notify it // that it has been removed from the memory cache. ((RecyclingBitmapDrawable) oldValue).setIsCached(false); } else { // The removed entry is a standard BitmapDrawable. if (Utils.hasHoneycomb()) { // We're running on Honeycomb or later, so add the bitmap // to a SoftReference set for possible use with inBitmap later. mReusableBitmaps.add (new SoftReference<Bitmap>(oldValue.getBitmap())); } } } .... }</pre> <h3>Use an existing bitmap</h3> <p>In the running app, decoder methods check to see if there is an existing bitmap they can use. For example:</p> <pre>public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth, int reqHeight, ImageCache cache) { final BitmapFactory.Options options = new BitmapFactory.Options(); ... BitmapFactory.decodeFile(filename, options); ... // If we're running on Honeycomb or newer, try to use inBitmap. if (Utils.hasHoneycomb()) { addInBitmapOptions(options, cache); } ... return BitmapFactory.decodeFile(filename, options); }</pre <p>The next snippet shows the {@code addInBitmapOptions()} method that is called in the above snippet. It looks for an existing bitmap to set as the value for {@link android.graphics.BitmapFactory.Options#inBitmap}. Note that this method only sets a value for {@link android.graphics.BitmapFactory.Options#inBitmap} if it finds a suitable match (your code should never assume that a match will be found):</p> <pre>private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) { // inBitmap only works with mutable bitmaps, so force the decoder to // return mutable bitmaps. options.inMutable = true; if (cache != null) { // Try to find a bitmap to use for inBitmap. Bitmap inBitmap = cache.getBitmapFromReusableSet(options); if (inBitmap != null) { // If a suitable bitmap has been found, set it as the value of // inBitmap. options.inBitmap = inBitmap; } } } // This method iterates through the reusable bitmaps, looking for one // to use for inBitmap: 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()) { // Check to see it the item can be used for inBitmap. 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; }</pre> <p>Finally, this method determines whether a candidate bitmap satisfies the size criteria to be used for {@link android.graphics.BitmapFactory.Options#inBitmap}:</p> <pre>static boolean canUseForInBitmap( Bitmap candidate, BitmapFactory.Options targetOptions) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // From Android 4.4 (KitKat) onward we can re-use if the byte size of // the new bitmap is smaller than the reusable bitmap candidate // allocation byte count. int width = targetOptions.outWidth / targetOptions.inSampleSize; int height = targetOptions.outHeight / targetOptions.inSampleSize; int byteCount = width * height * getBytesPerPixel(candidate.getConfig()); return byteCount <= candidate.getAllocationByteCount(); } // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1 return candidate.getWidth() == targetOptions.outWidth && candidate.getHeight() == targetOptions.outHeight && targetOptions.inSampleSize == 1; } /** * A helper function to return the byte usage per pixel of a bitmap based on its configuration. */ 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; }</pre> </body> </html>