7571542c9d
Change-Id: If780ad8bfabd66de21842bdb465d86ab24b2940c
298 lines
11 KiB
Plaintext
298 lines
11 KiB
Plaintext
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}shareables/training/BitmapFun.zip" class="button">Download the sample</a>
|
|
<p class="filename">BitmapFun.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"}.</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. There are some caveats in using
|
|
{@link android.graphics.BitmapFactory.Options#inBitmap}:</p>
|
|
<ul>
|
|
<li>The reused bitmap must be of the same size as the source content (to make
|
|
sure that the same amount of memory is used), and in JPEG or PNG format
|
|
(whether as a resource or as a stream).</li>
|
|
|
|
|
|
<li>The {@link android.graphics.Bitmap.Config configuration} of the reused bitmap
|
|
overrides the setting of
|
|
{@link android.graphics.BitmapFactory.Options#inPreferredConfig}, if set. </li>
|
|
|
|
<li>You should always use the returned bitmap of the decode method,
|
|
because you can't assume that reusing the bitmap worked (for example, if there is
|
|
a size mismatch).</li>
|
|
|
|
<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>HashSet<SoftReference<Bitmap>> mReusableBitmaps;
|
|
private LruCache<String, BitmapDrawable> mMemoryCache;
|
|
|
|
// If you're running on Honeycomb or newer, create
|
|
// a HashSet of references to reusable bitmaps.
|
|
if (Utils.hasHoneycomb()) {
|
|
mReusableBitmaps = 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()) {
|
|
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>private static boolean canUseForInBitmap(
|
|
Bitmap candidate, BitmapFactory.Options targetOptions) {
|
|
int width = targetOptions.outWidth / targetOptions.inSampleSize;
|
|
int height = targetOptions.outHeight / targetOptions.inSampleSize;
|
|
|
|
// Returns true if "candidate" can be used for inBitmap re-use with
|
|
// "targetOptions".
|
|
return candidate.getWidth() == width && candidate.getHeight() == height;
|
|
}</pre>
|
|
|
|
</body>
|
|
</html>
|