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
Related
I am currently working on my first serious android app. It is the first time I am appliying solid patterns programing and design which I learnt during the university studies. I have come across a situation where I dont know the "correct or best" way I should implement it. The problem is as follows:
As you might already know, in Android is not a good idea to keep models with bitmaps as you can easily reach the memory heap. I got a model in my app called "Event". An event has an id, name, description, bitmap etc.. However as I have stated before, when loading events from the database I load every attributes but the bitmap. At the moment, I have got it as a bitmap attribute referencing null with a set and get methods. Thus, my question is, from the point of view of patterns, clean programing, high cohesion and low coupling paradigm etc, how is the best way to retrieve the bitmap from the database? and where should it be retrieved, into the model or when it is required somewhere in the UI (i.e. a listview) ?
Until now I have thought the following solutions:
1.- Lazy loading pattern. Which means to develop the getBitmap inside the event model as a call to the database retreiving the bitmap associated with the event.
Pros: Very easy to implement; the model knows how to find its own bitmap atribute.
Cons: High coupling between the database interface and the model; the model shouldnt have awareness from outside itself; the model wouldnt be a POJO; models shouldnt have complicated methods; many calls to the database (imagine many getBItmap calls).
2.- Getting the model id and calling the database interface from the bussiness logic everytime I need the Bitmap. The model would never have a bitmap referenced from itself.
Pros: Bussiness logic calls the database interface, low coupling and correct model implementation.
Cons: Many objects which need the model bitmap should have a database interface reference, increasing copuling between the object and the database interface along the app. Think on an Android listview adapter which needs the bitmap from the event, it should call the database interface which seems kinda ugly. Besides, shouldnt be the own event model responsible to provide its own bitmap?
3.- Your own answer :)
Thank you very much for reading and I ll appreciate your answers.
Explanation of overall architecture:
Activities and the rest of the bussiness logic call a database interface (facade pattern) which I have implemented using Parse analytics API which allows to upload files, images etc as attributes on tables. Thus, if you want to retrieve a bitmap, simply query the parse API (implemented in the facade).
A simple case of bitmap retrieval would be (as I typed before) building the event view for a listview into the adapter, which would need the title, description, bitmap, etc of each event loaded.
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.
I am stuck up at one point. I need suggestions for the same.
I am creating an application which involves json parsing. After the parsing I am getting data which involves event name, event description, event place, event image url and so on. The data is huge. You can imagine facebook kinda stuff.
Now the problem is the data is getting parsed but because the event image is in the form of url, I need to convert it into Bitmap.
I have kept all the process of json parsing and bitmap conversion into one AsyncTask(doInBackground()).
This is taking a lot amount of time. I want something like facebook that the data gets loaded and is shown to the end user but the images load slowly and steadily. (I mean when we scroll facebook, then images don't come up immediately).
I want a similar functionality. I need suggestions.
You can still use an AsyncTask, only you'll need to use two seperate tasks.
One for the loading and parsing of the JSON, and one for the loading of the image.
After parsing the JSON, you'll need to start an AsyncTask for every image you're trying to load, making them all load on their own thread. It will show once the item is done loading.
Here is solution for you https://github.com/nostra13/Android-Universal-Image-Loader
You just pass url and imageview resource to loader, and it handles everithing for you. Also support caching. This library is simple and widly used
This concept is called Lazy Loading (AFAIK). You can use already development ImageLoader for this. It will download Image in Background and once it download it will set as background of ImageView. See Custom Downloader
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.
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