GridView with many images results in slow/rough scrolling - android

In my app I have a GridView with many images (~100), layout similar to this one:
I'm testing my app on a HTC Desire HD. When I have 88 images in the gridview, the scrolling is smooth. Now, if I add 10 images, so I have 98 in total, there isn't anything close to smooth scrolling anymore - it's now "slow", more rough, it's like it's scrolling in big chunks.
This gridview is present in a TabLayout, in which I also have a ListView with many elements. When using 98 images in the gridview, also scrolling in this listview is affected. So it's like the whole application gets slower somehow (or uses more "power" than the phone can manage).
Is there any way I can optimize the gridview so it does not use so much power?
(PS: when using about 150 images, the application simply stops working (it disconnects, and no error is shown in the log/debug).)

Make sure you are recycling your images in the getView() method of the adapter. Something like this:
public View getView(int position, View convertView, ViewGroup parent) {
// if it's not recycled, initialize some attributes
if (convertView == null) {
convertView = new View(...);
}
...
instead of making new Views each time getView() is called.

In the end I went with the solution provided by NOSTRA here:
Lazy load of images in ListView
It works really well.

I don't code in Android but I'd suggest a lazy visibility approach.
All your images appear to be the same size, so handle the scroll event and show only the images that can be seen on the current view frustum (the screen) while hiding the the rest.
My guess is, Android is using its processing power on the images that can't be seen during scrolling which causes the jitters.

Firstly, do not keep all your bitmaps in memory private Bitmap[] mBitmap;. That's probably what crashes your app. Read them from the filesystem with BitmapFactory.
Secondly, you should not scale your images in your adapter, that is expensive:
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
Try with a set of images that do not need to be scaled.

Related

Recyclerview scroll lag

I have multiple elements in my recyclerview's row -> By multiple I mean multiple in number as well as multiple in type i.e mutliple imageviews, textviews etc.
Following is the gist so that you can see all the elements: https://gist.github.com/Harshhb101/55e25da72e3a474aeeb422d5e231d3e3
The issue is that I need to hide/show these elements based on a parameter which can have upto 10 values. Thus I will have 10 types of rows. Currently I have created only one layout for the row have elements for all types of rows and in some mobiles, the scroll has a lag. Majorly I am getting the lag where the rows have images. I am using Glide to load the images. Following is the gist for the onBindView: https://gist.github.com/Harshhb101/e10feb2cccda9d698ff06487bbb879ef
I did look up on stackoverflow but could not find anything solid but came upon using multiple viewholders. My question is that if I refactor my code, will it make a substantial difference by using multiple viewholders? Or is there something wrong in my approach that could be fixed to get a good scoll.
You need to make your RecyclerView images smaller to save memory and cache them. Read this.
Also if your ViewHolder contains images that has size wrap_content then images are loaded full size what is really bad for performance for example if view size on screen is 48dp x 48dp and picture is full HD, then full HD drawable going to be loaded in to memory what is making your scrolling slow and not smooth.
I suggest using fixed size in ViewHolder or override image size when loading in Glide
Please declare many view type instead of one view type for many kind. It's actually make the code more readability, but not much the reduce the lag because the recycle view/listview is have the reused mechanism.
Recommended to use fixed size in RecycleView.
I saw the Glide used for image loading. So did you config the cache?
I have doubt that you loading the big resolution image
After looking into the code i found couple of improvements.
Don't make God ViewType (One view type for all kind of views). instead create
individual viewtype for each kind of posts. so it'll eliminate the rendering cost of all the views and making them visible and invisble at runtime.
Reference demo of recyclerview with multiple viewtypes
Use some image loading libraries so that you dont have to handle caching images and bitmap related manipulations (Picasso will be good).
Picasso
And lastly avoid doing heavy tasks in onbind() instead prepare your data class specifically for recyclerview (if much caluclation required like heavy string manipulations etc) before setting recyclerview adapter.

Reusing Views when they are already assigned to the same element

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?

Android ArrayAdapter increasing cached views to improve performance

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.

Tips to speed up Gallery widget in Android Honeycomb

I'm looking for a great way to speed up the Gallery view widget in Android Honeycomb. I'm currently using it to display some fairly large images at roughly 340 x 600 pixels, and I'd love for it to be smooth as butter when scrolling through the images.
It's fairly speedy at the moment, but it doesn't compare to loading a ScrollView with ImageViews and scrolling through that.
Here's a simplified version of my getView() method from my custom BaseAdapter:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = (ImageView) new ImageView(Main.this);
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
((ImageView) convertView).setImageBitmap(createReflection(BitmapFactory.decodeFile(ImageFile, options)));
convertView.setPadding(20, 0, 20, 0);
return convertView;
}
I've been experimenting with lazy loading the images, but I didn't really like the result.
The difference between using a Gallery and:
loading a ScrollView with ImageViews and scrolling through that
is that with the ScrollView scenario, you are pre-loading all of the images, rather than loading them on the fly as you are in the Gallery scenario.
If your number of images is small, and you have enough RAM to support all of them, then just use your ScrollView.
Beyond that, AFAIK there's not a ton you can do. You can maintain a bitmap cache where you continue decoding a few images ahead of the current ones in the Gallery and have your Adapter pull from the cache. However, that will only get you so far -- small scrolls will be smooth, but flings past your cache capacity will still result in the decoding being done on demand. That's pretty much unavoidable.
Gallery does not support the convertView at the moment. You will always receive null for convertView. This is a known issue and there's no ETA for a fix.
Turning on Hardware acceleration of your application will have a significant impact (at least had it on my example app).
Add android:hardwareAccelerated="true" in the application element of your android manifest
http://developer.android.com/guide/topics/manifest/application-element.html#hwaccel
One of the elements which is slowing your Gallery view down is file IO.
I'm working on a slideshow app for which I have photos in 1280x720 resolution.
The actual file I/O takes 300-400 ms for each file.
Since the File I/O would normally run on the UI thread, this will cause a very visible "hick" in any ongoing photo transition.
In order to avoid this, you should:
Set a temporary loading drawable that is already cached
imageView.setImageResource(R.drawable.my_loading_drawable);
Create an AsyncTask which
loads the drawable in doInBackground(i.e. the File I/O)
Updates the imageView in onPostExecute
imageView.setImageDrawable(drawable);
PS if the user flicks through multiple pictures, the above approach will typically trigger multiple parallell asynctask which all will be using file I/O. Not good for performance and may crash your app. You should probably have more structured approach where you only allow one async task at a time.

Android: lazy loading in Gallery

I've reviewed some posts about lazy loading but I believe my problem is a bit different.
I have a gallery (my class extends Gallery) which displays 20 rather large in size images (400-500K each). I cannot load them all to gallery since I get an OutOfMemory exception.
So, I created an array of 20 Drawables and initially populated the first 9 elements (the images come from the Web) and set all the rest to null. My intention was this: on a fling to the right, fetch element no. 10 and set to null element no. 0. On another fling to the right fetch element no. 11 and set to null element no. 1 to null. Same logic on a fling left.
The problem is I can fling much faster than the elements are fetched.
My gallery has a BaseAdapter and its getView() looks something like this:
public View getView(int position, View convertView, ViewGroup parent){
ImageView imageView = new ImageView();
imageView.setDrawable(imageArray[position];
....
....
return imageView;
}
How do I tell getView() - if imageArray[position] is still null, show a "loading..." dialog and once it is set repeat yourself with the same position?
I don't want to see the imageView empty and then set on the fly. I want to not be able to see the imageView at all until it is set.
Thanks.
Gallery is designed for smooth experience. It will be very bad UI if you block the screen and don't switch to next image until it is fetched. This way user will not be able to fling at all. You should display some loading indicator instead of image while it is loading.
I think your scenario is rather common. You should download images and display them. If you get OutOfMemory you can try to supersample images Strange out of memory issue while loading an image to a Bitmap object.
If there's still OutOfMemory you should remove bitmaps from memory and cache them to SD. So when user flings back you can load images from SD again, it will be fast enough. And memory consumption will be lower. As you suggest you can have 10 most recent images cached in memory and others cached on SD.
You can take a look at my sample code Lazy load of images in ListView. Actually it's a ListView adapter but you can apply it to gallery with minor modifications. I think it will do exactly what you need.

Categories

Resources