#PerfMatters using custom Views in Android to improve performance — Part 1

Custom Views and ViewGroups can be a great way to improve performance and make code more maintainable in your application. Admittedly, this can come at the cost of writing more code; however, it does not always have to increase code complexity.

As Android developers, we owe a lot to custom Views and to those who have mastered them. A lot of popular libraries out there are made possible because of custom Views. In this series of articles I hope to demystify custom Views and show you scenarios how you can use a custom view to improve your apps performance by saving on layout passes and at the same time creating Views and ViewGroups that would either not be possible or at least extremely tricky to implement using the built in components.

#PerfMatters therefore custom Views

In part 1, we will concentrate on how we can build a custom View and why it is a better option than using a default components. In part 2, we will see how we can build custom ViewGroups to improve performance. As an example, imagine that you have to build a horizontal bar view to depict a value.

The requirements are as follows:

  • The text within the bar should be to the right of the fill, if you run out of space, then it should be to the left.
  • You should be able to set the fill percentage and text at run time.

Building the HorizontalBarView using default components

Ignoring the fact that we now have a Percent Support Library, the best way to accomplish this layout with the the default components would be to use LinearLayouts with weights.

The Light blue is a LinearLayout with orientation vertical. The light green is a LinearLayout with orientation horizontal and the yellow are TextViews with weight set to 0.3 and 0.7 respectively. We have to use TextViews because we want to be able to show the text label.

As you can see with the 3rd and 4th bars below, using this approach it is possible to achieve the layout above. However, we have already encountered our first problem, in order to get the look we want, we have to set the background color, text color and gravity for each of the text views. Not only that, we have to determine which text view to display the text in. Some of this complexity can be reduced using styles, however, this would make it difficult to change the styles at run time.

The layout to achieve the look for the bars using built in components is shown below. The complexity is not really mitigated even if you were using the Percent Support Library.

<LinearLayout ...
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="22dp"
android:orientation="horizontal">

<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.3"
android:background="@android:color/holo_blue_dark"
android:gravity="center_vertical"
android:padding="4dp"
android:textColor="@android:color/white"
android:textSize="10sp" />

<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.7"
android:background="@android:color/darker_gray"
android:gravity="center_vertical"
android:padding="4dp"
android:text="Yes we can 30%"
android:textColor="@android:color/black"
android:textSize="10sp" />
</LinearLayout>
 ...
</LinearLayout>

You can pretty much guess what the code looks like to set the values for the bars and to decide what side the text goes on. You can image what a headache it is to coordinate these Views.

By comparison, the layout for the custom View is rather simple.

<com.example.alimuzaffar.perfmatters.customviews.HorizontalBarView
android:id="@+id/layout_one"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="22dp"
app:bar_fillPercentage="30.0"
app:bar_text="Yes we can: 30%"
app:bar_textPadding="8dp"
app:bar_textSize="10sp" />

The code for the custom View is a little complex, but effectively we are just calculating the start and end points for 2 boxes that represent the filled and empty bars. Then we draw the text to the right of the filled bar if there is enough space, otherwise we draw it to the left. You can also see the various other operations that are View supported as exposed by the custom attributes in attrs.xml.

Measuring the performance of each approach

For the purpose of this test, I created an Activity with 12 bars built using the default Views and resized them 7 times by programmatically emulating the click of a button. Then I repeated this test using my custom View (Code for custom HorizontalBarView on Gist).

There are 2 measures of performance that I am going to use:

  • Overdraw
  • Layout and measure times

Avoid Overdraw

Surprisingly, here we notice that as long as both the default and the custom approach is thought through well, their overdraw is roughly the same. I have to admit that initially the custom View had more overdraw than the approach using the default Views, however, the beauty of custom Views is that I was able to adjust it to improve overdraw.

Blue here means that a pixel was drawn once, and green means that it was drawn twice. As you can image, if we are going to draw on the bar, it’s hard to avoid overdraw for the text.

GPU Rendering

Secondly we want to measure how long it takes to render the UI relative to the 16ms per frame bench mark. At first I used the GPU Rendering option under the Developer options to see if one approach was clearly better than the other.

The only lines that we are interested in above are the blue and red lines. The blue line represents the time used to create and update the View’s display lists. The red line represents the time spent by Android’s 2D renderer issuing commands to OpenGL to draw and redraw display lists. The height of this line is directly proportional to the sum of the time it takes each display list to execute — more display lists equals a taller red bar.

As you can see, the blue and the red lines appear to be flatter for the custom Views when compared to the default Views. While encouraging, this is mostly anecdotal without some hard numbers to back up the visual graph. When the Views are profiled, we get the following render times for the screen each time all the views are resized.

//CUSTOM
1. Time Taken 37917ns
2. Time Taken 39063ns
3. Time Taken 47239ns
4. Time Taken 36771ns
5. Time Taken 39323ns
6. Time Taken 39271ns
7. Time Taken 35417ns
Avg ~38800ns

//DEFAULT
1. Time Taken 41459ns
2. Time Taken 40573ns
3. Time Taken 39011ns
4. Time Taken 51927ns
5. Time Taken 40833ns
6. Time Taken 38334ns
7. Time Taken 40886ns
Avg ~41100ns

You can see that on average, the performance is roughly 5% better for the custom View. The difference is a tiny fraction of a millisecond, however our Views are rather simple, the more complex our Views, the more layouts will be triggered and the more this number will add up. More importantly, while not significantly faster, we are still rendering 12 custom Views compared with 36 Views for the 12 bars build using default Views.

Other improvements

In this article, we haven’t tackled the how complex it is to display the text on the correct side for the default Views or how complex it is to recalculate the weights when we want to change the values. All of this is greatly simplified when your View is built to handle those conditions by itself. In addition, in one place we can expose functionality to set text, typeface, text and background colors and more.

Whether you use a custom View or a compound View with built in components, you still have to calculate the size of each bar, set the text colors and figure out which side the text will be rendered on. The main difference is that that in the case of our compound View that uses default components, you have to keep track of multiple Views, the Views measure and layout themselves which may lead to some inefficiency.

One way we can keep using the default Views and still improve performance may be to measure the child Views ourselves. After all, we are setting the weights on them, we know how they are going to be laid out, so why don’t we tell them rather than letting them figure it out for themselves. This will lead us to part 2 of this article where we will build a custom ViewGroup which will measure and layout its children to improve performance.

Yay! you made it to the end! We should hang out! feel free to follow me on LinkedIn, Google+ or Twitter.

A software engineer, an Android, and a ray of hope for your darkest code. Residing in Sydney.

The (retired) Pub(lication) for Android & Tech, focused on Development