Asynchronous download of Bitmaps in an Adapter, with emphasis on Bitmap.recycle()
Asked Answered
J

2

7

Could someone tell me how to make a good mechanism for async. download of images for use in a ListView/GridView? There are many suggestions, but each only considers a small subset of the typical requirements.

Below I've listed some reasonable factors (requirements or things to take into account) that I, and my collegues, are unable to satisfy at once.
I am not asking for code (though it would be welcome), just an approach that manages the Bitmaps as described.

  1. No duplication of downloaders or Bitmaps
  2. Canceling downloads/assigning of images that would no longer be needed, or are likely to be automatically removed (SoftReference, etc)
  3. Note: an adapter can have multiple Views for the same ID (calls to getView(0) are very frequent)
  4. Note: there is no guarantee that a view will not be lost instead of recycled (consider List/GridView resizing or filtering by text)
  5. A separation of views and data/logic (as much as possible)
  6. Not starting a separate Thread for each download (visible slowdown of UI). Use a queue/stack (BlockingQueue?) and thread pool, or somesuch.... but need to end that if the Activity is stopped.
  7. Purging Bitmaps sufficiently distant from the current position in the list/grid, preferably only when memory is needed
  8. Calling recycle() on every Bitmap that is to be discarded.
  9. Note: External memory may not be available (at all or all the time), and, if used, should be cleared (of only the images downloaded here) asap (consider Activity destruction/recreation by Android)
  10. Note: Data can be changed: entries removed (multi-selection & delete) and added (in a background Thread). Already downloaded Bitmaps should be kept, as long as the entries they're linked to still exist.
  11. setTextFilterEnabled(true) (if based on ArrayAdapter's mechanism, will affect array indexes)
  12. Usable in ExpandableList (affects the order the thumbnails are shown in)
  13. (optional) when a Bitmap is downloaded, refresh ONLY the relevant ImageView (the list items may be very complex)

Please do not post answers for individual points. My problem is that that the more we focus on some aspects, the fuzzier others become, Heisenberg-like.
Each adds a dimension of difficulty, especially Bitmap.recycle, which needs to be called during operation and on Activity destruction (note that onDestroy, even onStop might not be called).
This also precludes relying on SoftReferences.
It is necessary, or I get OutOfMemoryError even after any number of gc, sleep (20s, even), yield and huge array allocations in a try-catch (to force a controlled OutOfMemory) after nulling a Bitmap.
I am resampling the Bitmaps already.

Jorgensen answered 23/11, 2011 at 10:19 Comment(2)
https://mcmap.net/q/1045355/-bitmap-recycle-with-largeheap-enabledSejm
@LalitPoptani That answer deals almost exclusively with resampling, which, as I wrote, I was doing already. Because of that, I could have missed the passing mention of LruCache, which could be good, with its entryRemoved method, where you could call recycle() if you overrode sizeOf to return 4*width*height... except, while it helps, it does not SOLVE the problem. I can guess there might be 10MB non-heap (API<=10) memory for bitmaps available, but, even if there had been, there might not, by the time I fill the cache. OOM.Jorgensen
J
0

In the end, I chose to disregard the recycling bug entirely. it just adds a layer of impossible difficulty on top of a manageable process.
Without that burden (just making adapters, etc stop showing images), I made a manager using Map<String, SoftReference<Bitmap>> to store the downloaded Bitmaps under URLs.
Also, 2-4 AsyncTasks (making use of both doInBackground and onProgressUpdate; stopped by adding special jobs that throw InterruptedException) taking jobs from a LinkedBlockingDeque<WeakReference<DownloadingJob>> supported by a WeakHashMap<Object, Set<DownloadingJob>>.
The deque (LinkedBlockingDeque code copied for use on earlier API) is a queue where jobs can leave if they're no longer needed. The map has job creators as keys, so, if an Adapter demands downloads and then is removed, it is removed from the map, and, as a consequence, all its jobs disappear from the queue.

A job will, if the image is already present, return synchronously. it can also contain a Bundle of data that can identify which position in an AdapterView it concerns.

Caching is also done on an SD card, if available, under URLEncoded names. (cleaned partially, starting with oldest, on app start, and/or using deleteOnExit()
requests include "If-Modified-Since" if we have a cached version, to check for updates.

The same thing can also be used for XML parsing, and most other data acquisition.
If I ever clean that class up, I'll post the code.

Jorgensen answered 5/2, 2013 at 8:51 Comment(0)
C
2

Check this example. As Its is used by Google and I am also using the same logic to avoid OutOfMemory Error.

http://developer.android.com/resources/samples/XmlAdapters/index.html

Basically this ImageDownlaoder is your answer ( As It cover most of your requirements) some you can also implement in that.

http://developer.android.com/resources/samples/XmlAdapters/src/com/example/android/xmladapters/ImageDownloader.html

Cupid answered 14/12, 2011 at 10:55 Comment(1)
Apologies for waiting so long... the links are no longer valid, but I remember this had potential, though I wasn't sure how it applied to Activity lifecycles...Jorgensen
J
0

In the end, I chose to disregard the recycling bug entirely. it just adds a layer of impossible difficulty on top of a manageable process.
Without that burden (just making adapters, etc stop showing images), I made a manager using Map<String, SoftReference<Bitmap>> to store the downloaded Bitmaps under URLs.
Also, 2-4 AsyncTasks (making use of both doInBackground and onProgressUpdate; stopped by adding special jobs that throw InterruptedException) taking jobs from a LinkedBlockingDeque<WeakReference<DownloadingJob>> supported by a WeakHashMap<Object, Set<DownloadingJob>>.
The deque (LinkedBlockingDeque code copied for use on earlier API) is a queue where jobs can leave if they're no longer needed. The map has job creators as keys, so, if an Adapter demands downloads and then is removed, it is removed from the map, and, as a consequence, all its jobs disappear from the queue.

A job will, if the image is already present, return synchronously. it can also contain a Bundle of data that can identify which position in an AdapterView it concerns.

Caching is also done on an SD card, if available, under URLEncoded names. (cleaned partially, starting with oldest, on app start, and/or using deleteOnExit()
requests include "If-Modified-Since" if we have a cached version, to check for updates.

The same thing can also be used for XML parsing, and most other data acquisition.
If I ever clean that class up, I'll post the code.

Jorgensen answered 5/2, 2013 at 8:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.