Android Performance — Lazy inflating Fragment with ViewStub
Hello World, this is my first Medium article and also my first tech blog. :)
Using ViewPager to hold your Fragments is a very common pattern on Android. However, your UI is getting complex and having more and more Fragments on your ViewPager, it will slow down your app.
Although ViewPager is already providing us a handy API to control the number of Fragments we want to initiate and hold.
public void setOffscreenPageLimit (int limit)
The minimum limit you can set here is 1. Can I further do some more optimization here to speed up the app on first launch? Sure, you can try out ViewStub!
Recently I have been working on some performance tuning on an app in which around 10 fragments are required to be initiated at the same time due to requirements. This greatly impact the start up time of the app.
Then I started to do some research. I learnt that view inflation is a heavy process. The first thing that I tried is to remove any redundant layout or unused layout, which is described here. Secondly I found out that there’s an API called ViewStub, which can be used to lazy inflate views.
Cool! That’s something that I may try to look for. When using ViewPager, user can see one page at a time. Why don’t we just inflate the view when the user swipe to that page? So here is my solution:
- Creating a layout, which will hold the fragment to be inflated. Your fragment will be inflated in fragmentViewStub. The ProgressBar here is just a placeholder to indicate user that it’s loading.
2. Creating a base fragment like this, which will be inherited by any fragments that would like to do lazy inflation via ViewPager.
Inside onCreateView(), we use getUserVisibleHint() (default is true) to check whether the current fragment is already visible to user, if yes, we inflate it directly by calling mViewStub.inflate() else don’t inflate it.
We created an abstract method getViewStubLayoutResource() to tell ViewStub which layout to be inflated, set it via mViewStub.setLayoutResource().
After inflation, we have a callback called onCreateViewAfterViewStubInflated(), where your view is actually attached and allow you to setup your presenter and views (if you are using MVP pattern)
We will need to dismiss the ProgressBar also after view has inflated.
So, where is the lazy inflate magic?
There you go, setUserVisibleHint(). ViewPager will call setUserVisibleHint() function to notify whether the Fragment is visible to a user. So we inflate our Fragment here and the other callbacks are the same as above!
Bare in mind that ViewStub can only be inflated once, so we use a flag mHasInflated to mark whether this Fragment is already inflated. You can use ViewStub.setVisibility(View.Visible) to do inflation also, this can be called multiple times but won’t return the view to you.
Simple right? The performance gain is quite significant, it speed up almost 0.5s when we apply these changes to 3 of our Fragments. Others are not applied yet due to some legacy code inside in which refactoring is required.
To conclude, view inflation is a heavy process, to speed things up in your app, you can try to
- Simplify and optimize your view hierarchy
- Do lazy view inflation when possible, especially using ViewPager
Feel free to leave your comments if you have other great idea or something that I can improve, since I’m still new to Anroid! :)
Edit: Thanks to Noa Drach, I’ve updated the BaseViewStubFragment to fix the orientation change problem