Skip to content

Commit 6525f7b

Browse files
Adam KochAndroid (Google) Code Review
authored andcommitted
Merge "Updates to "Displaying Bitmaps Efficiently" class. Changes: -Updated code sample (see http://ag/214812) -Updated code snippets to match updated sample -Fixed <> in code snippets -Updated disk cache section -Some other minor updates" into jb-dev
2 parents ae050d5 + 9977ddd commit 6525f7b

File tree

5 files changed

+81
-49
lines changed

5 files changed

+81
-49
lines changed
80.4 KB
Binary file not shown.

docs/html/training/displaying-bitmaps/cache-bitmap.jd

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ and leave the rest of your app little memory to work with.</p>
9696
<p>Here’s an example of setting up a {@link android.util.LruCache} for bitmaps:</p>
9797

9898
<pre>
99-
private LruCache<String, Bitmap> mMemoryCache;
99+
private LruCache&lt;String, Bitmap&gt; mMemoryCache;
100100

101101
&#64;Override
102102
protected void onCreate(Bundle savedInstanceState) {
@@ -109,7 +109,7 @@ protected void onCreate(Bundle savedInstanceState) {
109109
// Use 1/8th of the available memory for this memory cache.
110110
final int cacheSize = 1024 * 1024 * memClass / 8;
111111

112-
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
112+
mMemoryCache = new LruCache&lt;String, Bitmap&gt;(cacheSize) {
113113
&#64;Override
114114
protected int sizeOf(String key, Bitmap bitmap) {
115115
// The cache size will be measured in bytes rather than number of items.
@@ -159,7 +159,7 @@ public void loadBitmap(int resId, ImageView imageView) {
159159
updated to add entries to the memory cache:</p>
160160

161161
<pre>
162-
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
162+
class BitmapWorkerTask extends AsyncTask&lt;Integer, Void, Bitmap&gt; {
163163
...
164164
// Decode image in background.
165165
&#64;Override
@@ -179,7 +179,7 @@ class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
179179
rely on images being available in this cache. Components like {@link android.widget.GridView} with
180180
larger datasets can easily fill up a memory cache. Your application could be interrupted by another
181181
task like a phone call, and while in the background it might be killed and the memory cache
182-
destroyed. Once the user resumes, your application it has to process each image again.</p>
182+
destroyed. Once the user resumes, your application has to process each image again.</p>
183183

184184
<p>A disk cache can be used in these cases to persist processed bitmaps and help decrease loading
185185
times where images are no longer available in a memory cache. Of course, fetching images from disk
@@ -190,18 +190,14 @@ be unpredictable.</p>
190190
appropriate place to store cached images if they are accessed more frequently, for example in an
191191
image gallery application.</p>
192192

193-
<p>Included in the sample code of this class is a basic {@code DiskLruCache} implementation.
194-
However, a more robust and recommended {@code DiskLruCache} solution is included in the Android 4.0
195-
source code ({@code libcore/luni/src/main/java/libcore/io/DiskLruCache.java}). Back-porting this
196-
class for use on previous Android releases should be fairly straightforward (a <a
197-
href="http://www.google.com/search?q=disklrucache">quick search</a> shows others who have already
198-
implemented this solution).</p>
199-
200-
<p>Here’s updated example code that uses the simple {@code DiskLruCache} included in the sample
201-
application of this class:</p>
193+
<p>The sample code of this class uses a {@code DiskLruCache} implementation that is pulled from the
194+
<a href="https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/io/DiskLruCache.java">Android source</a>. Here’s updated example code that adds a disk cache in addition
195+
to the existing memory cache:</p>
202196

203197
<pre>
204-
private DiskLruCache mDiskCache;
198+
private DiskLruCache mDiskLruCache;
199+
private final Object mDiskCacheLock = new Object();
200+
private boolean mDiskCacheStarting = true;
205201
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
206202
private static final String DISK_CACHE_SUBDIR = "thumbnails";
207203

@@ -210,12 +206,26 @@ protected void onCreate(Bundle savedInstanceState) {
210206
...
211207
// Initialize memory cache
212208
...
213-
File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR);
214-
mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE);
209+
// Initialize disk cache on background thread
210+
File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
211+
new InitDiskCacheTask().execute(cacheDir);
215212
...
216213
}
217214

218-
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
215+
class InitDiskCacheTask extends AsyncTask&lt;File, Void, Void&gt; {
216+
&#64;Override
217+
protected Void doInBackground(File... params) {
218+
synchronized (mDiskCacheLock) {
219+
File cacheDir = params[0];
220+
mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
221+
mDiskCacheStarting = false; // Finished initialization
222+
mDiskCacheLock.notifyAll(); // Wake any waiting threads
223+
}
224+
return null;
225+
}
226+
}
227+
228+
class BitmapWorkerTask extends AsyncTask&lt;Integer, Void, Bitmap&gt; {
219229
...
220230
// Decode image in background.
221231
&#64;Override
@@ -232,7 +242,7 @@ class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
232242
}
233243

234244
// Add final bitmap to caches
235-
addBitmapToCache(String.valueOf(imageKey, bitmap);
245+
addBitmapToCache(imageKey, bitmap);
236246

237247
return bitmap;
238248
}
@@ -246,28 +256,48 @@ public void addBitmapToCache(String key, Bitmap bitmap) {
246256
}
247257

248258
// Also add to disk cache
249-
if (!mDiskCache.containsKey(key)) {
250-
mDiskCache.put(key, bitmap);
259+
synchronized (mDiskCacheLock) {
260+
if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
261+
mDiskLruCache.put(key, bitmap);
262+
}
251263
}
252264
}
253265

254266
public Bitmap getBitmapFromDiskCache(String key) {
255-
return mDiskCache.get(key);
267+
synchronized (mDiskCacheLock) {
268+
// Wait while disk cache is started from background thread
269+
while (mDiskCacheStarting) {
270+
try {
271+
mDiskCacheLock.wait();
272+
} catch (InterruptedException e) {}
273+
}
274+
if (mDiskLruCache != null) {
275+
return mDiskLruCache.get(key);
276+
}
277+
}
278+
return null;
256279
}
257280

258281
// Creates a unique subdirectory of the designated app cache directory. Tries to use external
259282
// but if not mounted, falls back on internal storage.
260-
public static File getCacheDir(Context context, String uniqueName) {
283+
public static File getDiskCacheDir(Context context, String uniqueName) {
261284
// Check if media is mounted or storage is built-in, if so, try and use external cache dir
262285
// otherwise use internal cache dir
263-
final String cachePath = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
264-
|| !Environment.isExternalStorageRemovable() ?
265-
context.getExternalCacheDir().getPath() : context.getCacheDir().getPath();
286+
final String cachePath =
287+
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
288+
!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
289+
context.getCacheDir().getPath();
266290

267291
return new File(cachePath + File.separator + uniqueName);
268292
}
269293
</pre>
270294

295+
<p class="note"><strong>Note:</strong> Even initializing the disk cache requires disk operations
296+
and therefore should not take place on the main thread. However, this does mean there's a chance
297+
the cache is accessed before initialization. To address this, in the above implementation, a lock
298+
object ensures that the app does not read from the disk cache until the cache has been
299+
initialized.</p>
300+
271301
<p>While the memory cache is checked in the UI thread, the disk cache is checked in the background
272302
thread. Disk operations should never take place on the UI thread. When image processing is
273303
complete, the final bitmap is added to both the memory and disk cache for future use.</p>
@@ -292,7 +322,7 @@ android.widget.ImageView} objects.</p>
292322
changes using a {@link android.app.Fragment}:</p>
293323

294324
<pre>
295-
private LruCache<String, Bitmap> mMemoryCache;
325+
private LruCache&lt;String, Bitmap&gt; mMemoryCache;
296326

297327
&#64;Override
298328
protected void onCreate(Bundle savedInstanceState) {
@@ -301,7 +331,7 @@ protected void onCreate(Bundle savedInstanceState) {
301331
RetainFragment.findOrCreateRetainFragment(getFragmentManager());
302332
mMemoryCache = RetainFragment.mRetainedCache;
303333
if (mMemoryCache == null) {
304-
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
334+
mMemoryCache = new LruCache&lt;String, Bitmap&gt;(cacheSize) {
305335
... // Initialize cache here as usual
306336
}
307337
mRetainFragment.mRetainedCache = mMemoryCache;
@@ -311,7 +341,7 @@ protected void onCreate(Bundle savedInstanceState) {
311341

312342
class RetainFragment extends Fragment {
313343
private static final String TAG = "RetainFragment";
314-
public LruCache<String, Bitmap> mRetainedCache;
344+
public LruCache&lt;String, Bitmap&gt; mRetainedCache;
315345

316346
public RetainFragment() {}
317347

docs/html/training/displaying-bitmaps/display-bitmap.jd

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ public class ImageDetailActivity extends FragmentActivity {
103103
}
104104
</pre>
105105

106-
<p>The details {@link android.app.Fragment} holds the {@link android.widget.ImageView} children:</p>
106+
<p>Here is an implementation of the details {@link android.app.Fragment} which holds the {@link android.widget.ImageView} children. This might seem like a perfectly reasonable approach, but can
107+
you see the drawbacks of this implementation? How could it be improved?</p>
107108

108109
<pre>
109110
public class ImageDetailFragment extends Fragment {
@@ -146,11 +147,11 @@ public class ImageDetailFragment extends Fragment {
146147
}
147148
</pre>
148149

149-
<p>Hopefully you noticed the issue with this implementation; The images are being read from
150-
resources on the UI thread which can lead to an application hanging and being force closed. Using an
151-
{@link android.os.AsyncTask} as described in the <a href="process-bitmap.html">Processing Bitmaps Off
152-
the UI Thread</a> lesson, it’s straightforward to move image loading and processing to a background
153-
thread:</p>
150+
<p>Hopefully you noticed the issue: the images are being read from resources on the UI thread,
151+
which can lead to an application hanging and being force closed. Using an
152+
{@link android.os.AsyncTask} as described in the <a href="process-bitmap.html">Processing Bitmaps
153+
Off the UI Thread</a> lesson, it’s straightforward to move image loading and processing to a
154+
background thread:</p>
154155

155156
<pre>
156157
public class ImageDetailActivity extends FragmentActivity {
@@ -190,7 +191,7 @@ modifications for a memory cache:</p>
190191
<pre>
191192
public class ImageDetailActivity extends FragmentActivity {
192193
...
193-
private LruCache<String, Bitmap> mMemoryCache;
194+
private LruCache&lt;String, Bitmap&gt; mMemoryCache;
194195

195196
&#64;Override
196197
public void onCreate(Bundle savedInstanceState) {
@@ -229,7 +230,8 @@ UI remains fluid, memory usage remains under control and concurrency is handled
229230
the way {@link android.widget.GridView} recycles its children views).</p>
230231

231232
<p>To start with, here is a standard {@link android.widget.GridView} implementation with {@link
232-
android.widget.ImageView} children placed inside a {@link android.app.Fragment}:</p>
233+
android.widget.ImageView} children placed inside a {@link android.app.Fragment}. Again, this might
234+
seem like a perfectly reasonable approach, but what would make it better?</p>
233235

234236
<pre>
235237
public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
@@ -261,7 +263,7 @@ public class ImageGridFragment extends Fragment implements AdapterView.OnItemCli
261263
}
262264

263265
&#64;Override
264-
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
266+
public void onItemClick(AdapterView&lt;?&gt; parent, View v, int position, long id) {
265267
final Intent i = new Intent(getActivity(), ImageDetailActivity.class);
266268
i.putExtra(ImageDetailActivity.EXTRA_IMAGE, position);
267269
startActivity(i);
@@ -345,13 +347,13 @@ public class ImageGridFragment extends Fragment implements AdapterView.OnItemCli
345347
}
346348

347349
static class AsyncDrawable extends BitmapDrawable {
348-
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
350+
private final WeakReference&lt;BitmapWorkerTask&gt; bitmapWorkerTaskReference;
349351

350352
public AsyncDrawable(Resources res, Bitmap bitmap,
351353
BitmapWorkerTask bitmapWorkerTask) {
352354
super(res, bitmap);
353355
bitmapWorkerTaskReference =
354-
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
356+
new WeakReference&lt;BitmapWorkerTask&gt;(bitmapWorkerTask);
355357
}
356358

357359
public BitmapWorkerTask getBitmapWorkerTask() {

docs/html/training/displaying-bitmaps/index.jd

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ exception:<br />{@code java.lang.OutofMemoryError: bitmap size exceeds VM budget
4343
perform under this minimum memory limit. However, keep in mind many devices are configured with
4444
higher limits.</li>
4545
<li>Bitmaps take up a lot of memory, especially for rich images like photographs. For example, the
46-
camera on the <a href="http://www.google.com/nexus/">Galaxy Nexus</a> takes photos up to 2592x1936
47-
pixels (5 megapixels). If the bitmap configuration used is {@link
46+
camera on the <a href="http://www.android.com/devices/detail/galaxy-nexus">Galaxy Nexus</a> takes
47+
photos up to 2592x1936 pixels (5 megapixels). If the bitmap configuration used is {@link
4848
android.graphics.Bitmap.Config ARGB_8888} (the default from the Android 2.3 onward) then loading
4949
this image into memory takes about 19MB of memory (2592*1936*4 bytes), immediately exhausting the
5050
per-app limit on some devices.</li>
@@ -75,4 +75,4 @@ exception:<br />{@code java.lang.OutofMemoryError: bitmap size exceeds VM budget
7575
components like {@link android.support.v4.view.ViewPager} and {@link android.widget.GridView}
7676
using a background thread and bitmap cache.</dd>
7777

78-
</dl>
78+
</dl>

docs/html/training/displaying-bitmaps/process-bitmap.jd

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ decodeSampledBitmapFromResource()}</a>: </p>
6262

6363
<a name="BitmapWorkerTask"></a>
6464
<pre>
65-
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
66-
private final WeakReference<ImageView> imageViewReference;
65+
class BitmapWorkerTask extends AsyncTask&lt;Integer, Void, Bitmap&gt; {
66+
private final WeakReference&lt;ImageView&gt; imageViewReference;
6767
private int data = 0;
6868

6969
public BitmapWorkerTask(ImageView imageView) {
7070
// Use a WeakReference to ensure the ImageView can be garbage collected
71-
imageViewReference = new WeakReference<ImageView>(imageView);
71+
imageViewReference = new WeakReference&lt;ImageView&gt;(imageView);
7272
}
7373

7474
// Decode image in background.
@@ -133,13 +133,13 @@ completes:</p>
133133
<a name="AsyncDrawable"></a>
134134
<pre>
135135
static class AsyncDrawable extends BitmapDrawable {
136-
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
136+
private final WeakReference&lt;BitmapWorkerTask&gt; bitmapWorkerTaskReference;
137137

138138
public AsyncDrawable(Resources res, Bitmap bitmap,
139139
BitmapWorkerTask bitmapWorkerTask) {
140140
super(res, bitmap);
141141
bitmapWorkerTaskReference =
142-
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
142+
new WeakReference&lt;BitmapWorkerTask&gt;(bitmapWorkerTask);
143143
}
144144

145145
public BitmapWorkerTask getBitmapWorkerTask() {
@@ -211,7 +211,7 @@ one associated with the {@link android.widget.ImageView}:</p>
211211

212212
<a name="BitmapWorkerTaskUpdated"></a>
213213
<pre>
214-
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
214+
class BitmapWorkerTask extends AsyncTask&lt;Integer, Void, Bitmap&gt; {
215215
...
216216

217217
&#64;Override
@@ -236,4 +236,4 @@ class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
236236
android.widget.GridView} components as well as any other components that recycle their child
237237
views. Simply call {@code loadBitmap} where you normally set an image to your {@link
238238
android.widget.ImageView}. For example, in a {@link android.widget.GridView} implementation this
239-
would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p>
239+
would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p>

0 commit comments

Comments
 (0)