Xamarin Android - Improving GridView Recycling Performance
This is a quick performance-improvement follow-up to my previous blog post about implementing infinite scrolling in a Xamarin Android GridView
A Quick Recap
Previously, we were inflating a new instance of a view for every single item, as it was passed to us by the data adapter.
This obviously works, and serves to demonstrate how to load an item’s details into a view so that it can be displayed on-screen. The overridden GetView
method kindly takes care of recycling the views and the memory management for us.
The silly thing about doing it this way for Every Single Item is that we know exactly what each view will look like; there’s really no need to have to locate each TextView
or ImageView
control, every single time. It would be ideal if we only had to look for it once, and then just reference it every time we need it.
This post will show you how to do just that, using what’s called ‘the view-holder pattern’. In a nutshell, it allows us to capture the instances of those controls on each MyGridViewCell
view, and then just set their text and image source properties directly, the next time we need them.
Using the view holder method will increase the performance of your GridView (or any other view you use it with) by between 20% and 40%. When you have to keep your UI fluidly updated at 60fps, this is a massive saving! And it gives you an idea of just how much it costs your app in terms of processing time! When you only have 16 milliseconds to get your stuff ready for the next redraw pass, those precious few milliseconds you just saved can be put to better use elsewhere!
We need a view holder for each grid cell item which is (or is about to be) displayed. Not one for every item in the list. Remember, the GridView doesn’t show all the items, only the number of items which will fit on the screen. So we won’t need that many of them.
Just how many do we need? Well, it actually doesn’t matter. Or not to us, anyway. We’ll get the GetView
view-generation code to make them as we need them, and then get it to just recycle the ones it’s created.
Some of you may be thinking: this seems a little complex. It’s really not. The GetView
method does all of the heavy lifting for us. It’s less than 10 lines of code.
So what does a view holder look like? Well, it’s a pretty simple object, all it contains is a couple of properties corresponding to the controls we need to keep references to. In this case, we need to just keep track of a TextView
and an ImageView
.
They don’t come much simpler. Note that it inherits from Java.Lang.Object
. This is important, and you’ll see why in a second.
Managing the MySimpleItemViewHolder
instances
The GetView
method already recycles the views for us. We only need to make a couple of minor adjustments to it, in order for it to be able to make and recycle the view holders for us, too.
Briefly, this breaks down into the following operations:
- If we don’t have a current view, then make one, and make a
MySimpleItemViewHolder
instance to go with it. - Locate the controls we need on the view (in our case
TextView
andImageView
). - Assign those controls to the corresponding properties in the
MySimpleItemViewHolder
instance. - Set the values in those properties, based on our incoming item’s DisplayName and Image source.
- This will immediately have the effect of setting them on the controls (since they’re now pointed to by those properties).
- Attach the
MySimpleItemViewHolder
instance to the current view by putting it in theTag
property of the current view.- This is the key step.
And if we do have a current view (i.e. currentView != null
)?
- Extract the
MySimpleItemViewHolder
instance from thecurrentView.Tag
property. - Set the values of that instance’s
DisplayName
andThumbnail
image properties to the data provided by the incoming data item. - We’re done.
Rinse. Repeat.
Being able to place the MySimpleItemViewHolder
into the Tag
property is why we inherited it from Java.Lang.Object
. If we’d inherited from System.Object
, we’d have got an assignment error. And no, it’s not possible to cast a System.Object
to (or from) a Java.Lang.Object
.
So what does it look like?
Are there any changes to the infinite scrolling aspect?
No. All the heavy lifting is being done by our view holders. Nothing else changes. Hooray! :)
Conclusion
We are able to use the GetView
method’s ability to recycle the view objects to our advantage. When it recycles every view, it doesn’t (and will not) clear out the Tag
object attached to each one. This gives us a safe place to store our references to the controls on each view, so we can reuse them.
Finally, the View Holder Pattern is one of Google’s best practice methods of improving scrolling-view-recycling performance. So they’re not going to clobber your Tag
contents. They put it there for you to use for stuff just like this. Watch Romain Guy’s Google I/O 2009 talk on improving performance for more about view recycling and other layout and performance tips. Yes, it’s a little dated, but still relevant, and worth an hour of your time.
The full source is available in this GitHub repo. Please note: Rather than create a brand new repo for such a minor change, I’ve made these changes in the add-viewholder
branch.
In my next post, I plan on demonstrating how to implement a staggered grid (like they do over at pinterest.com ) using the new Android RecyclerView
and StaggeredGridLayout
. Yes it’ll do infinite scrolling. And no, you won’t have to do any Java bindings.
References
Some references you may find useful:
- Previous post on implementing an infinite scrolling GridView
- Romain Guy’s talk on making your Android App UI fast at Google I/O
- Xamarin Grid View sample
Everything in here is released as OSS under the MIT license, so feel free to use it any way you like.