I currently have a ListView with a custom adapter that gets information describing the content of the rows asynchronously. Part of each row is an image URL, that I'm planning to download asynchronously and then display.
My current plan for a strategy to download these images is:
Keep a cache of soft references to downloaded Bitmap objects.
When a getView() is called and the bitmap is in the cache, set the bitmap for the ImageView directly.
If the bitmap isn't in the cache, start loading it in a separate thread, after the download is complete add it to the cache and call notifyDataSetChanged() on the adapter.
I am also planning to kill pending downloads when the Activity object owning the ListView's onDestroy()-method (Or possibly even in the onPause()-method) is called, but most importantly I want to kill the download of pending images when the row goes off screen. I might only actually cancel the download after a short delay, so it can be resumed without wasting bandwidth if the row comes on-screen quickly again.
I, however, am unsure about a few things:
What is the best way to detect when a row goes off-screen so I can cancel the download?
Is calling notifyDataSetChanged() the best thing to do after the download has completed or is there a better way?
Also any comments on the whole strategy would be appreciated.
I don't think calling notifyDataSetChanged() is really needed... I would do it like that:
store URL as Tag in the view when created/updated
register a listener in downloader thread (async task???) for download keeping reference to the view and the URL
whenever image is downloaded asynchronously, I check TAG in the view and if it matches - i would update the ImageView (important to do it in UI thread, but when using async task, it is given). The image should also be stored on SD card (and every time you request URL you should check if it is not already downloaded).
every time when getView() reuses the view (passed view is not empty) I would check the Tag (old URL), replace it with the new URL and cancel the download of the oldURL.
I think it would be pretty much it (some corner cases might happen)...
I use the getFirstVisible and getLastVisible AdapterView properties to detect the visible rows, and put requests in a fixed size stack.
My project is open source and has a most permissive license, if you want to use it:
https://github.com/tbiehn/Android-Adapter-Image-Loader
-Travis
I found the remote resource managing / fetching in the Foursquared source code to be pretty helpful:
http://code.google.com/p/foursquared/source/browse/main/src/com/joelapenna/foursquared/util/RemoteResourceManager.java
It caches images on disk and handles all 3 of your feature requests. See an adapter for how to use it.
As for canceling a download when a row goes off screen you'll have to handle that yourself
Related
I am designing an Android application targeting >= API 17. I have created a class, DownloadImageTask which extends AsyncTask, and receives a string (URL) and an ImageView as arguments. In it, I am opening an HTTP connection, downloading an image from a URL, and using BitmapFactory to create a Bitmap object from the data, then setting the bitmap to the ImageView. The end result is a populated list of data which is available to the user to scroll through, with images populating as they can.
This appears to be a good design on the surface - but I am concerned that I am putting my app at risk for an OOM condition, or other violation of the user experience rules. I'd like to know if the way I've designed this is correct, or if not, how I should approach this.
Thank you in advance for your help.
Two considerations to your own approach:
You shouldn't pass the ImageView to the async task because in that way you are coupling your view and your service layer. So send to the async task the URL, and onPostExecute method call to Activity which implement an updateView (or the like) method.
About your OOM, you are right. The problem might arise if you use the original bitmaps which could have larger resolution than required. Therefore you should scale down the images you keep in memory.
The last issue might not be difficult if you use a few images otherwise could be problematic. So if you will be working with a lot of images and you are not forced to implement your own version, you should have a look to the existing libraries. Some are already mentioned:
Glide
Picasso
I have implemented the CursorAdapter in my android app, by extending it in my own class and using the newView and bindView methods from the same.
Following is the list of things that I need to do before data is shown to the user -
1) Retrieve image url's (stored in database) in a cursor which is returned by a CursorLoader
2) Get the actual image from the sd card / internal memory by using the url
3) Tie the gridview's column1 which contains an ImageView to the thumbnail image retrieved from the url
Now, here, I am generating the UI elements for GridView in the newView method, whereas binding them with the image in the bindView method. But the problem is that, the image loading is very slow and also, doesn't show up any images till the time, all are not fetched and bound. So, even though the UI doesn't go unresponsive, nothing is shown up for longer time.
Do we require use of AsyncTask from within the bindView, so as to weave threads and do the image to ImageView binding within AsyncTask rather than directly within bindView??
What is a correct approach to do an image load for gridview from within the bindView?
External Code link - http://www.megafileupload.com/en/file/560756/GeneralSummaryFragment-java.html
AsyncTask is one good approach here as also advocated by Google
Processing Bitmaps off the UI thread.
When I use ListView the getView() method is called many times. Every time when the getView() is called i load the image with Asyc task. I mean every time i reset the image which is annoying.
How to understand when to load the image?
You should cache loaded images, by storing i.e. on SD card, so once you got a copy there, no need to download it again. There's lot of ready-to-use classes that can do the job for you, like:
http://greendroid.cyrilmottier.com/reference/greendroid/widget/AsyncImageView.html
you must must have two flags.
One which says if you've already loaded the image, if true you do nothing.
One which says if you're currently loading the image, if true you do nothing.
The members will also help you on maintaining the state of the image.
Your code should look something like this:
private boolean isLoading = false;
private boolean hasLoaded = false;
if(!hasLoaded){
if(!isLoading){
isLoading = true;
//do async load
//on positive completition callback set hasLoaded to true
//on negative completition callback set isLoading to false
}
}
One of the best solution is to create image cache using the WeakReference. This way you can keep images in memory and only need load from server when they are not in memory. In this method the image would be removed from the memory when system encounter low memory situation. So your current activity would always keep the hard reference to the bitmap's required and the image cache would keep the weak reference to the bitmap's.
below reference links will help you
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
http://www.codeproject.com/Articles/35152/WeakReferences-as-a-Good-Caching-Mechanism
the Volley library (made by google) has a very intuitive class for an imageView that can have a url , called "NetworkImageView" .
you should check it out and watch the video, since they show that it's quite annoying to do it using asyncTask (plus the asyncTask is known to have a limit of tasks, about 255 or so) .
for setting the url, just use setImageUrl .
it has some useful methods for the phases of loading too: setDefaultImageResId , setErrorImageResId.
it's also supposed to have built in caching mechanism of some sort, but i haven't read much about it, so you might want to check out their samples.
this will remove the need to use asyncTasks for the listView's items.
one of my questions regarding the volley includes a sample code , here .
You can add a caching layer and optionally preloading the images. A good strategy for caching Images (Bitmap objects to be exact) is to use a strategy called LRU or least recently used.
Android support library has a class called LruCache that implements this strategy. So for example, when you download/load the image for the first time, you stick it into the cache. later, you can first check if it's already in cache and load it from there.
For preloading, A good rule of thumb is to preload the previous ten and the next ten items.
In my android application the scenario is this -
I have several images stored in the SD card.
Now in my app I have an Image view and I have to update this image view continuously
like a slide show is running. This has to be done by fetching images one by one from SD card and setting them in the same ImageView. Also the interval of image change in ImageView is fixed.
So I have a cursor which stores the information for fetching these images.
Now I am wondering what should be my approach for moving the cursor one by one and fetching images from SD card and setting them in my ImageView.
Should I use Timer Task and Timer or should I go with Handler or something else?
Please help as this is initial point of my app and I want to start in the right direction.
Since you're retrieving large data from the SD card, it might be best to use a thread to retrieve/decode the bitmap.
In this case, a Timer would be useful because it runs on it's own Java thread for scheduled periods of time. However, you have to keep in mind that you can't update a UI element from a non-UI thread. Thus, you'd need a syncing element in place for when the bitmap is fully loaded to finally put the decoded bitmap in to the ImageView. In which case, you would need to use Handler anyway to send message queues to the UI thread.
Thus, I'd probably recommend using a combination of Handler and AsyncTask. Create an AsyncTask class that retrieves and decodes the bitmap in doInBackground(). Update the ImageView in onPostExecute(). Use a Handler to create and execute a new AsyncTask ever Nth milliesecond (whenever you update the view).
One advantage to this is it keeps all image retrieval in one class. You can open the cursor, retrieve, close it. This is safer than keeping a long-running cursor open.
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.
No duplication of downloaders or Bitmaps
Canceling downloads/assigning of images that would no longer be needed, or are likely to be automatically removed (SoftReference, etc)
Note: an adapter can have multiple Views for the same ID (calls to getView(0) are very frequent)
Note: there is no guarantee that a view will not be lost instead of recycled (consider List/GridView resizing or filtering by text)
A separation of views and data/logic (as much as possible)
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.
Purging Bitmaps sufficiently distant from the current position in the list/grid, preferably only when memory is needed
Calling recycle() on every Bitmap that is to be discarded.
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)
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.
setTextFilterEnabled(true) (if based on ArrayAdapter's mechanism, will affect array indexes)
Usable in ExpandableList (affects the order the thumbnails are shown in)
(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.
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
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.