In my app I'm displaying images on a grid view. In some cases there are a a lot of images on a single grid view (up to 1,000). This is driven off user data and is unavoidable.
It's impractical to load all images in memory. I tried that and was hitting memory errors fairly quickly. So to avoid this I create AsyncTasks in the getView() method of my array adaptor which load the image in the background. This works very smoothly so far.
The problem is that the set of images can change. notifyDataSetChanged() will trigger getView() to be called on every item on screen. The result is a lot of CPU time spent re-loading images which don't need to be changed.
Is there any way to detect if getView() is "reusing" a view to display the same element as it's already displaying? Alternatively is it possible to detect if an ImageView has already has a Bitmap assigned?
Related
I need to display a list of items each with text and various number of images.
I'm currently using a ListView with custom adapter to show these items. And for each item, I used a HorizontalScrollView with a LinearLayout in it to display the images. In the getView method of the ListView, I read the image URIs of each item and dynamically create ImageViews, then load the images asynchronously. I used a ViewHolder to hold the LinearLayout which contains all the ImageViews of each item.
The problem is, if I scroll down the ListView and scroll back, I'll lost the content of the item, which means I have to load the images again. And most of the images are too large and loads very slow. Actually on my app the screen can show only about 2 or 3 items once, so the scroll happens very frequently.
I have some ideas to improve this, but I'm not sure whether one of them will results better.
Since I'm just showing a thumbnail of each image, maybe I can save the thumbnails into a temp dir and load them dynamically, loading small images will be much faster. And I might have to clear that temp dir when it gets large.
I have at most 9 images for each item, so it might still be slow even if I cache the thumbnails and scroll frequently. And maybe I have to show the list manually instead of using ListView, so each item will not be reused, and the load will happen only once. But, the list will grow large in the future, if I preserve like 100 items in the LinearLayout my app may still crash.
Other better options...
Any advice will be helpful! Thanks!
You can use LazyLoading to display your image. You can use Universal Image Loader for this purpose. What lazyloading does is download an image once, cache it and display the image from cache during subsequent requests.
So I have a listview with an image and some text hooked by a custom array adapter. The problem I am facing is that the image is still loading a little bit too slow for my liking. I've watched the google tech talk and attempted to optimize my list by resusing the view by the convertView (if it's null then inflate, if not, resuse). I've also spawned AsyncTasks to load the bitmap with BitmapFactory.Options inSampleSize set to a power of 2, since the image is relatively small. I've also used the ViewHolder pattern to reduce findViewById calls.
However, even with this, when I scroll through my list, it is very noticeable how the convertViews are being reused because the image constantly gets updated as I scroll up and down.
What I noticed in the stock Android photo gallery is, with the hundreds of photos that I have, when I scroll through quickly, the photos initially load slowly, but then get cached. The amount cached is MUCH more than what the screen is capable of showing, so as I scroll, initially the photos load seamlessly until I scroll pass the amount of photos cached, and then the reload of photos is noticeable again.
Is there a way to do this with the ArrayAdapter? Basically, store more than the 9 views within my listview (What my screen is capable of showing) for the purposes of when a user scrolls down quickly, the user would have to scroll down a lot before the convertView is reused and thus the noticeable image reloads?
Thanks in advanced!
Load the images in the background, using multiple parallel threads.
For example: Sending Operations to Multiple Threads; the sample app downloads thumbnails from the Picasa Featured Photos RSS feed. Hope this helps.
I'm having an issue with an adapter for a grid view. The adapter causes a bitmap to be loaded every time a particular item is needed. I've got a disk cache and memory cache and I'm disabling loading while scrolling, so generally speaking, the gridview is quick.
But what I'd really like is to eliminate constantly having to rebind a particular Bitmap to an ImageView. Since the gridview is reusing views, the getView method has to keep reseting the ImageView and reloading the image from memory. This creates a really stupid effect as items slide offscreen and when returned to have a delay as the bitmap is read from the memory cache and posted to the ImageView.
Is there a way to get the GridView to 'hold on to' more views instead of being really frugal? I have a maximum of about 20 items and im scaling all the bitmaps, so I'd really like to just hold on to more of the views if possible.
I was able to solve this using a better in memory cache. The real distinction was to add a mechanism to check if the image was in memory and not 'reset' the view if the required image was in memory. It still seems bogus that there is not an easy way to set the number of cached views off screen for grid view, but better memory cache management made the problem less noticeable.
I have implemented lazy loading of images(from remote server) in my application(twitter kind of app) and the fetching part and loading the image into the exact imageView works perfectly fine. But the "getView" part of the adapter gets called only after I scroll down the ListView and so images are fetched from remote server(I fetch images by executing an asyntask inside the adapter "getView" code) only after I scroll down and I can see a considerable amount of delay in the images being loaded. Once the images are loaded and stored in memory cache there is no problem while scrolling up or down. The only problem is images loading for the first time loading slow
So is there a way to rectify it in such a way that by the time I scroll down I should be able to get the image bitmap and the set the bitmap when I scroll down. I want it to be exactly like twitter app where I couldn't see lag in loading profile pictures.
TIA
Well your approach is incorrect you have to fetch all the images before showing them as get view called every time you scroll the list so every time your service get called and fetch the no. of images that a list can display on screen (say 4-5 at a time).
What you have to do is :-
1) Fetch all images.
2) As you fetch all the images set adapter to listview and you will have all images in your list without any delay as you already have downloaded the images,
But this is not lazy loading --
lazy loading means user got to see only some part of images at a time if he doest not scroll there is no point downloading images from service.
Hope that will be helpful
If you call your webservice in the getView() method, it will be called every time you scroll up and down. Since the getView method is called for the rows that are visible on screen.
What you could do, is load the images to a HashMap<int, Object>, where int == position in listview, and object is the image. In the getview you could then check if positions + (1 -> 10) have been loaded, and if not, retrieve them from the webservice in some kind of background task.
Load the images in the hashmap, and make the getView() method use those images. Not the images directly retrieved from the webservice. Otherwise, you indeed would have to wait for them to be retrieved. Also this way you cache the images in the hashmap instead of repeatedly retrieving the same ones when scrolling up and down.
Of course there are already people who have made such adapters, just by searching for it here I found this SO question, which has an answer that refers to the CommonsWare endless adapter.
There is a library called AALP that you can use to help you with Lazy loading of list views. https://bitbucket.org/zedvoid/aalp/src
This could save you some time and effort hopefully.
In my Android App I have a listview containing 30 rows, and each row consists of several textviews of which one is spannable and sometimes contains a lot of formatted text and images.
Those images are loaded from the web asynchroneously: A placeholder is displayed until the image has been downloaded is then replaced by the image.
Unfortunately, the rows of the listview are loaded when I scroll over them. This makes the whole thing very slow. Also, those images are loaded from the web again and again, whenever I scroll over the row.
How can I turn it off, that the ListView rows are loaded when I scroll over them? They should be loaded once when I start the activity and never again.
Best regards and thanks in advance,
Jan Oliver
When you do a lazy-loading ListView, is because you want to speed it up your app. Turn it off is not the best solution. So, what you can do is implementing a basic cache system in order to avoid downloading and setting the ImageView again and again.
The easiest way to do so is implementing a HashMap with URLs as keys and Bitmaps as values. Something like this:
Map cache = new HashMap();
// then, on your lazy loader
Bitmap image = cache.get(urlOfTheImage);
if( image == null ){
// download and decode the image as normal,
// then assign the decoded bitmap to
// the 'image' variable
cache.put(image);
}
imageView.setImageBitmap(image);
If those images will be the same always, meaning that each time you open the app the same images will be downloaded, then you better save those images in the filesystem and use them from there.
On the other hand, if the images tend to change, you could implement some interesting stuff: use SoftReferences. There's an explanation in this video. This can also be used if you are loading images from the filesystem.
Edit
With regards to your comment, I highly recommend you watching the video I posted. It's one hour long, but it really worths the effort. When using an adapter, checking if the convertView is null is just a simple way to improve performance, though there are some other techniques that will improve your app even more. Also, if you had have problems while using that trick, is because you are probably implementing it the wrong way. Remember: even if you don't re-inflate the views, you do have to set the value of each one of the children views, otherwise you will experience some problems.
If you can, start with an Image Array full of the "placeholder images", then download the images in to an Array firing an AsyncTask during on Create. During row view building just refer to the array. That way if it has the new image it will load it, if not it will get the placeholder.
If you have a lot of data its gonna get real slow and be a crappy expirience for the user.
Create a list of objects that represent each row. Create a loader as a background thread that updates the objects as it loads the data. Your list view will draw data from the objects.
(Not a good idea if you have hundreds of rows and a huge amount of data in each row - in that case, you should only load data within a few rows of the currently active row and have some sort of MRU cache).