Android Performance: Rendering

Android系统性能 (2)

Posted by Cliu on August 20, 2019

Based on resources of the udacity course Android Performance developed by Google. Code samples can be downloaded from Github.

Dialogue: Intro

>> Huh. 140 grams of fiber. Why would you need that much?
>> Hey. Morning, Colt.
>> Hey, Chris. What’s going on?
>> How’s it going? I’m just recovering from a long day yesterday. I spent all day making this new app. It’s got a material theme, some tasteful animations, a nice transition. When I tilt the screen, look at that, it subtly adjusts the color scheme.
>> Nice!
>> Pretty slick huh?
>> Yeah, it looks really good, Chris, but you know your application is running really slow, right?
>> Yeah, for some reason, Android feels a bit slow. I mean, I think you guys might need to tune it up a bit. It would make our lives a little easier.
>> Ha ha. I’m going to let that one slide. Why don’t you back up and tell me what you’re doing to create this transcendent experience of yours.
>> Yeah, sure. So to get this nice color blending happening here, I have to layer a bunch of views, color them, and then when I animate them, I actually have to cut out circles, redraw them back to the screen. And as the user scrolls, I apply this cool new animation to draw the new content on top of the old. I might even put an Easter egg right there.
>> Okay. Timeout, Chris. I think you are violating some of the principles of modern application performance design. Just because you can overlay 90 views that are animating and transparent, doesn’t mean you actually should.
>> Okay. But that’s the user experience that seems to be selling these days. If it runs slow, it’s Android’s problem, right? Not mine.
>> No, you gotta remem, put it this way, Chris. You wouldn’t use bubble sort on an array that’s got 20 million elements in it, would you?
>> That’s true. I’d probably use some, something way faster like merge sort or quick sort.
>> See exactly. You gotta remember that the Android system is doing a ton of work to make things fast and look beautiful, but you still have to live up to your end of the bargain and do a little lifting too, right? All of those overlays and transparencies are beautiful, but they actually have an implicit performance cost that comes with them. This is why rendering performance is one of the biggest pitfalls that most developers fall into.
>> Right.
>> They spend so much time making these beautiful visuals. They forget all of the performance cost that comes along with it.
>> All right, well. But, I still want to keep this user experience. So, how do I make it fast?
>> Huh. That’s the easy part, Chris. You watch some videos.

Rendering Performance

Rendering performance is the most common performance issue that you run into while building your app. On one hand, your designers want to produce the most usable transcendent experience for your users, but on the other hand, all those fancy graphics and transitions may not work well on every single device.

Let’s take a look at what rendering performance is all about. Now firstly, know that the system will attempt to redraw your activity, every 16 milliseconds or so, which means that your application needs to run all of the logic to update the screen in that 16 millisecond window, so that you can hit 60 frames per second. This frames per second number, generally comes from the phone’s hardware, which defines how fast the screen can update itself in a single second.

Now, most devices will refresh at about 60 hertz, meaning you’ve got 16 milliseconds to do all of your drawing logic each frame. If you miss that window, say take 24 milliseconds to finish your calculations, you get what we call a dropped frame. The system tried to draw a new picture to the screen, but one wasn’t ready yet. So it didn’t refresh anything. The user ends up seeing the same graphic for 32 milliseconds rather than 16.

Hm. Any animations going on during a dropped frame will manifest themselves to your users with a jump in their smoothness. Now because even if there’s one dropped frame, the animation won’t look smooth to anybody. And multiple dropped frames are at the core of what users start calling a laggy or janky experience. It gets even worse when lag happens while your users are interacting with the system, for example, when they’re dragging a list view or trying to type in some data. This is what users mercilessly start complaining about.

Now that we have a better understanding of how much time it takes per frame to draw, let’s take a look at some of the problems that cause lag, and how you can address them in your applications.

Rendering Pipeline

The Android rendering pipeline is broken up into two key sections, the CPU and the GPU, which both work together in order to draw images on the screen. Each one has some specific processes that are defined by it and need to obey specific operating rules in order to be performant.

Now on the CPU side, the most common performance problems come from unnecessary layouts and invalidations that is part of your view hierarchy having to be measured, torn down, and rebuilt. This generally comes in two problematic flavors:

  • either the display lists are being rebuilt too many times during a frame

  • or you’re spending too much time invalidating too much of your view hierarchy and needlessly redrawing.

Both cause taxing CPU overhead in refreshing display lists and other related cache GPU resources.

Now the second major problem is on the GPU side, involving an inefficiency called overdraw, which is effectively when we waste GPU processing time coloring in pixels that end up getting colored in later, by something else.

Now, in this lesson, we’re going to talk more about invalidations, layouts, and overdraw, and how you can use the tools available in the SDK to find out if they’re hurting the performance of your application. And don’t worry about all the crazy diagrams you’ve seen so far, once we’ve covered the theory, Chris is here to walk you through the code samples that will teach you how to fix these exact kind of problems in your applications. So, let’s get started with something easy, overdraw.

How the Hardware’s Working

Understanding how to make a great performing app has everything to do with understanding what’s going on under the hood. See, if you don’t know how the hardware’s working you’ve got a good chance of using it poorly. Now the main question is this. How does your activity actually get drawn to the screen? Or rather how does all that crazy XML and markup language turn into pixels the user can see and understand?

At its core, this is done with a process known as rasterization. This is the process of taking some high level object like a string or a button or a path or a shape and turning it into pixels in a texture or on your screen. Now rasterization is a really, really time consuming process. And as such, there’s a special piece of hardware in your mobile device that’s designed to make it happen a lot faster. The graphics processing unit, or GPU, was introduced to mainstream computers back in the early 90s, to help accelerate the rasterization process.

Now, the GPU itself is designed to use a specific set of primitives. Dominantly that is polygons and textures, a.k.a, images. And your CPU is responsible for feeding these primitives to your GPU before it can draw anything to the screen. This is done through a common API on Android known as OpenGL ES, which means that anytime your UI objects, like buttons or paths or check boxes, need to be drawn to the screen, they’re first converted to polygons and textures on the CPU, and then passed off to the GPU to rasterize. And, as you would imagine, this process of converting a UI object to a set of polygons and textures is not the fastest of operations. Likewise, actually uploading that process data from the CPU to the GPU isn't that fast either. So it makes sense then that you’d want to reduce the number of times you have to convert an object, as well as the number of times you have to upload it for drawing.

Thankfully, the OpenGL ES API allows you to upload content to the GPU and then leave it there. When you’d like to reference drawing a button again in the future, you simply have to reference it in GPU memory, and then tell OpenGL how to go about drawing it. The general rule of thumb is this. Optimizing for rendering performance means getting as much data onto the GPU as fast as possible, and then leave it there without modifying it for as long as possible. Because every time you update a resource on the GPU, you lose precious processing time.

Android System Does A Lot

Now, since the release of the Honeycomb version of Android, the entire UI rendering system works with the GPU, and with every release since then, more improvements have been made to the rendering system performance. The Android system does a lot, and, and I mean a lot of work to reduce, reuse, and recycle GPU resources on your behalf so that you really don’t have to think about it. For example, any resources that are provided by your theme, that is Bitmaps and drawables, et cetera, are all grouped together into a single texture, and then uploaded to the GPU on your behalf alongside commonly used meshes, like Nine Patches for instance. This means that you, anytime you need to draw one of these resources, you don’t have to do any conversions. See, all the content is already stored on the GPU, making these type of views really fast to display.

However this whole rendering process gets more and more complex as your UI objects get more and more advanced. Take for example, displaying images. This actually means uploading the image on to the CPU into its memory and then passing it off to the GPU to render. Using paths is a whole separate mess. As you might need to create a chain of polygons in the CPU or even create a masking texture to define the path shape on the GPU. Now, drawing text is a complete double whammy, right? I mean first, we have to draw the characters in the CPU to an image, and then upload that image to the GPU, and then go back through and draw a rectangle on the screen for each character in our string.

Now, the performance of those operations are mostly handled by the Android system on your app’s behalf, and unless you’re doing something excessive, you really shouldn’t see much of a GPU problem there. However, there’s one GPU performance bottleneck that challenges every single developer equally. It’s called overdraw.

Overdraw

If you’ve ever painted a room or a house, you know that it takes a lot of work to color in those walls, and if you need to end up repainting it again, you’ve wasted a ton of work doing it the first time. This same concept of wasting effort to paint something can also contribute to performance problems in your applications. See, at the intersection of performance and design lies a common performance problem. Overdraw.

Overdraw is a term used to describe how many times a pixel on the screen has been redrawn in a single frame for example if we have a bunch of stacked UI cards, all of the cards that are on the top part of the stack closer to the user will hide large portions of the cards that are hidden underneath. Meaning that we’ll spend time drawing those cards which are mostly invisible. This is actually a large problem because each time we’re rendering pixels that don’t contribute to the final scene, we’re wasting GP performance. And with modern layouts, it’s easy to fall into a trap where we’re stacking and layering views to give us this beautiful, transcendent design. But also creating the same overdraw problem. To maximize performance in your application, you’ll want to minimize overdraw.

Fortunately, it’s easy to see the amount of overdraw in your applications right on your Android device. Simply go into developer mode, and then turn on the Show GPU overdraw flag. Now, don’t panic while your phone turns into some sort of visual awesomeness. This is completely normal. Android is using different colors to highlight areas of overdraw occurring on your screen. If you’ve only rendered a pixel one time, you should see it in its true color with no tint. However, as the overdraw increases, so do the colors. A one x overdraw, for example, is tinted blue, meaning that you’ve now redrawn this pixel one extra time. And, of course, two x, three x, and four x follow the same pattern. So when you’re tuning your app’s user interface, you’ll want to reduce as much overdraw as possible, reducing all of those areas of red in favor of the nice blues.

To accomplish this goal, there’s two main ways you can remove overdraw.

  • Now firstly, you’ll want to eliminate unneeded backgrounds and drawables from views that won’t contribute to the final rendered image. Remember, this is wasted performance.

  • Secondly, you can define areas of your screen that you know will hide portions of your view, which can help reduce CPU and GPU overhead. So let’s get started at the top and join Chris as he’s going to take us and help us look at overdraw.

Case Study: Visualize & Fix Overdraw

Hey, what’s going on? And thanks Colt. Now, I’m really excited to join heads and perf out a developer situation involving overdraw. Now, if you want to follow along, you’ll want to clone the code sample at the URL in the instructor notes.

All right, so here we are in our sample code application. Now, let’s imagine, you just created a chat application, and you want to find out how well you did in respect to overdraw performance. The first thing you’ll want to do is gather information about how your app is performing. To do this you’re going to turn on the GPU overdraw debug setting on your device. You will find it within the Developer Options section of your systems settings, like so. Okay, it’s on now.

Now, back to our chat application. Uh-oh, look at all this excessive overdraw happening here. You want to reduce this, particularly the red areas. So here’s a little reminder about what the colors mean. Cool, ready to dig in? Let’s dive into how the UI is currently built, and see if we can clean it up a bit to reduce some overdraw. Now, as Colt mentioned, one way to do this is to remove unnecessary backgrounds and drawables. So let’s take a stab at doing that. How about we spiff up Chatum to be like this? But let’s chip at the problem one step at a time. For example, notice that we have a green or two x overdraw occurring in Chatum’s background right here? Now why could this be? Well it turns out that Chatum’s base activity uses a frame layout with an opaque white background that fills the entire screen. We like this, but it collides with Android's material theme defaults, particularly the window background drawable. This causes unnecessary overdraw. Now as a developer, we have a design decision to make. Let’s say we want to keep our white background, which means there’s no real purpose for the material’s theme. So one optimization we can make here is to set our activity’s background drawable to null.

Now, let me show you how you can do this in code. In Chatum’s base activity, let’s look at the onCreate method. Use the following statement to nullify the background. All right, sweet. By nullifying the background we’ve reduced this overdraw from a green to a blue, effectively making the overdraw now one x. Nice. Now this was a programmatic change. But lets take a look at our XML markup to see if we can tweak anything else. Now, you probably already noticed that there are three XML files that specify Chatum’s user interface. There’s the base activity Chatum Latinum, the XML for our chat fragment, and then lastly, the individual XML for our chat items. As mentioned before, we intentionally want to keep this white background right here. So, let’s not remove anything. But maybe there’s some opportunity for tweaks in the remaining two XML files. This is where I could use your help. Do me a favor and comb the remaining XML files, and see if you can remove any unnecessary backgrounds declared. When you’re finished, enter the number of backgrounds you’ve removed in this box here. If you’re having trouble, no need to worry. Just move on to the solution.

So there should have been four unnecessary backgrounds in the remaining files. Let’s go ahead and review. In our base activities XML, remember, we wanted to keep this white background. Now, in the XML file for our chat fragment, we’ve declared an unnecessary white background right here. We don’t need this because we can use the white one from our main activity. That’s it for this file. Now, in the XML for our individual chat items, we have three unnecessary backgrounds. We have one right here that we don’t need, one right here that we don’t need, and lastly, down here, we don’t this white background in our text view. So we can go ahead and remove these. All right. Cool. Let’s see what kind of progress we made as far as overdraw is concerned. Now this is what your screen should look like with these backgrounds removed properly. Much cleaner, right? Okay, good work.

Now, we’re just about done, but there’s actually one last optimization you can make. Notice there’s overdraw here by the avatars, because we are drawing a rectangle and then the avatar image on top of it. Let’s try and be a bit smarter here, let’s only set a background when an avatar can’t be found. Now, we can do this with some conditional code. All right,. So let’s jump into our chat adapter code, which is responsible for filling in our individual chat items as they get loaded. Let’s go to our get view method. Now, down here at the bottom we actually have some logic that’s displaying both the avatar and setting a background color at the same time. Let’s see if we can get a bit more intelligent here. Let’s write some code that only sets the background color when an avatar isn’t present. And when it is, we’ll set the background color to be transparent and just load the avatar.

We can do it like this. All right, so here’s our updated code. Notice that when an avatar isn’t present, what we’re going to do is load a transparent color into where the avatar would normally be, and then set a true background color for the avatar. Now, in the else case, which represents when the avatar is present, we’ll go ahead and load the avatar properly, and then we’re going to set the background color to transparent. This way we’re minimizing overdraw. All right, so let’s go ahead and see how this improvement helps us out. Nice. As you can see here now by our avatars, much less overdraw with our updated code. All right, so that was our last optimization, which means we’re done.

Let’s go ahead and recap. When we started, our overdraw was much more prominent.

  • The first thing we did was set the background drawable to null.
  • The second thing we did was remove unnecessary background declarations from our XML markup.
  • Now, the third and last thing we did was display a background color only if there is no avatar present.

Now, with these changes, we ended up with this. Much, much cleaner in terms of overdraw performance. So, awesome job. And remember, perf matters.

Now in terms of running performance, keep in mind that some overdraw might be necessary and acceptable, like the screen here from Android’s action bar. But if you want to polished app experience, you’ll want to make sure you reduce it as much as possible. All right, cool. Let me send it back to Colt to tell you a little bit more about clipping, another optimization that you can use to reduce overdraw. All yours, Colt.

Clipping

It’s worth pointing out that the Android framework knows that overdraw is a problem, and will go out of its way to avoid drawing UI widgets that may be invisible in the final image. This type of optimization, called clipping, is highly important to UI performance. If you can determine an object will be fully obscured, there’s no reason to go about drawing it. In fact this is actually one of the most important performance optimizations that Android performs.

But sadly this technique doesn’t extend itself to complex custom views, where you’re overriding the canvas dot on draw method. In these cases, the underlying system doesn’t have insight into how you’re drawing your content, which makes it really hard for it to remove hidden views from your rendering pipeline.

For example, in this stack of cards, only the top card is fully visible and the rest of the cards are mostly hidden, which means that drawing all of those overlapping pixels is wasted processing time. To address this problem, the canvas class is equipped with a few special methods that you can use to tell the Android framework what parts of the canvas are hidden and don't need to be drawn. The most useful method is canvas dot clipRect, which allows you to define the drawable boundaries for a given view, meaning that if any canvas drawing happens outside these boundaries, it’ll be ignored. This could be really helpful when you’ve got this type of staggered view, like our cards for example. If you know how much of your custom view is visible, or rather you know how much is obscured, you can define the clipRect boundaries, which will prevent any drawing from happening in the obscured areas.

Now the clipRect API helps the system know what to avoid drawing. But it would also be helpful for your custom view to do some of this work on its own, ahead of time for example, it would be really helpful if you knew what you were going to draw if it’s actually outside of the clipping rectangle. Fortunately, you don’t have to figure out all that intersection logic yourself, the canvas.quickReject method tests whether a given area is completely outside the clipping rectangle, in which case you can skip all that drawing work. Now let’s take a look at a common situation where this happens and take a swing at fixing it.

Case Study: Fixing Overdraw with Canvas API

All right. I hope you’re all stretched out because it’s time to step up to the perf plate again. Let’s take another look at an overdraw situation, this time involving custom views. Now as a developer, these are custom situations where you want to create a unique experience or behavior that isn’t quite covered by Android’s built in views.

For example, in the following code, some of the UI elements or at least portions of them may be invisible to the final scene, and thus the app probably doesn’t need to draw them. So, as Cole mentioned, these are situations where you might want to employ clipping to prevent unnecessary use of GPU resources. So, let’s hop back to the render app and take a look at Droid cards.

All right. Now in the background here, I’ve brought up some of the source files for this activity. Now as you can see in this DroidCardsView Java file, the UI for this activity is implemented using a custom view that displays a stack of overlapping Droid playing cards. Now, we don’t have time to cover it now but if you’d like to review creating custom views, feel free to check out some of the useful resources in the instructor notes. All right, so let’s gather some information about our app. Let’s turn on GPU overdraw debug again and see how this activity looks. Whoa! There’s clearly lots of overdraw happening here. Particularly these red areas right in the center. Now, I have a question for you. What could possibly be causing this much overdraw? Is it, we’ve inadvertently declared extraneous backgrounds, we’re drawing cards to the screen in a way in which they overlap leaving some portions hidden, we shouldn’t be using a custom view in this scenario? Or is it the frustration that builds up from developers who chose not to address rendering problems like overdraw?

If you’ve concluded that all this red overdraw is the result of wastefully drawing hidden portions of the cards that lie underneath the top card, then you’d be correct. As it currently stands in this code, the cards are drawn in their entirety through this loop and slightly offset from each other, and thus there is natural portions of overlap. Now unfortunately, as the cards are drawn from the bottom up here, each successive card ends up covering the existing cards below it. This needlessly wastes GPU cycles. So let’s go and fix it.

All right, so here we are back in the Android Studio, reviewing the source for our DroidCardsView. Again, this is our custom view. Just to recall, it’s in this custom view that we build our stacked playing cards. For example, each card has its own bitmap, and we draw them to the screen by overriding the onDraw method. Now remember, by overriding the onDraw method, Android won't be able to optimize the rendering of this view, so it’s our job as developers to properly clip each card as it’s drawn to the screen to avoid the unnecessary overdraw. Fortunately, the Canvas API gives us just the right methods we need to draw our cards more efficiently.

Let’s take a look at the documentation. Let’s use the canvas.clipRect method to improve our code. We’re going to use this variant here that takes four floats as parameters. All right, now we’re back in Android Studio. Let’s tweak how we draw each cascaded card so that we reduce this overdraw. I’m going to use this nifty feature within Android Studio to bring up a diff view, so you can see the before and after state of the code. ‘Kay, all right, here we are in our comparison view, and let’s look particularly at the onDraw method, which is the one we’re overriding. We’ve got the old state of the for loop here on the left, but let’s focus here on the improvements that we’re going to make on the right.

Now, as we iterate over the cards, let’s go over each step of improvement. Number one, first, we’re going to calculate the position for the card. Then we need to call this function canvas.save, which is going to save our existing canvas state. In other words, it maintains the existing state of your screen before we apply the clipRect API. Now, when we call this clipRect method, we’re basically doing some geometric restrictions. When we pass in these parameters, we’re basically telling the system to only draw a portion of the card that we want to see visible. Obviously, the rest of it would be hidden. Now, only the parts of the card that lie within the bounds of the restriction that we just put will get drawn. Finally, we’re going to call canvas.restore, which is going to revert the canvas to a non-clipping state. In other words, it’s going to undo the restriction that we put in place when we called clipRect. And then we repeat this loop for all the cards except the top one. Now specifically, we process the top or last card differently than its underlying siblings. For this one, no clipping is needed, so we’re going to go ahead and draw it in its entirety. You can see that via the statement right here.

All right, let’s build our improved code and see if we’ve reduced a bunch of overdraw. All right, awesome. As you can see here, much less overdraw. Now I hope it’s clear that with the Canvas API, we have a straightforward way to draw efficiently when working with custom views. Oh, and in case you want to explore the Canvas API some more, be sure to check out the full documentation in the instructor notes.

Now, views like these are great for helping us create a unique and compelling UX, but remember that we must clue in the system to help Android render such views in a performant fashion. So whenever you’re creating a custom view, be sure to check for overdraw, and be ready to revive your friend, the clipRect method. All right, awesome work. Let’s head back to Colt for more perf wisdom.

Display List

Now that you’ve gotten overdraw under control, it’s time to bubble up a bit and start looking at the CPU portion of the rendering pipeline. In order to draw something on the screen, Android generally needs to convert that high-level XML into something that the GPU can accept and use to render to the screen. This is done with the help of a internal Android object called a display list.

A display list basically holds all the information needed to render a view on the GPU. It contains a list of any GPU resident assets that might be needed, as well as a list of commands to execute with OpenGL in order to render it.

The first time a view needs to be rendered, a display list will be created for it. And when we need to render that view to the screen, we execute that display list by submitting its drawing commands to the GPU. If we want to render that view in the future again, like if its position changed on screen, we simply need to execute that display list again. However, in the future, if some visual part of this view changes, the previous display list may no longer be valid. As such, we’ll need to recreate the display list and then re-execute them to update the screen.

Now, remember this. Anytime the drawing content of your view changes, it will repeat the process of recreating the display list and then re-executing it to get it to the screen. The performance of these operations varies, depending on how complex your view actually is. And depending on the type of visual change, there’s other impacts on the rendering pipeline as well. For example, let’s say that a text box suddenly doubles in size, causing the parent container to reposition other sibling views before updating its own size. In this case, we’ve updated one view and it cascaded to other work that needed to be done. These types of visual changes require additional stages of the rendering pipeline to occur.

See, when the sizing of your view changes, the measure phase will be kicked off. And it will walk through your view hierarchy, asking each view what its new sizes are. Basically, this happens anytime you change the size of a view, for example, padding, drawable size, set text’s scale, width, height, et cetera. And if you change the position of things or call request layout, or a view group repositions its children, then the layout phase will kick off, traversing the hierarchy and determining where all the new locations of the objects should actually be on the screen.

Now, the Android runner system is really, really efficient at handling the record and execute phases of the pipeline. And unless you’re doing something crazy with custom views, or suddenly a ton of views are getting drawn at once, these shouldn’t account for much of your frame time. Measure and layout phases are also pretty performant, but are more prone to run into performance problems when your view hierarchy gets out of hand.

See, the time taken to execute these functions is proportional to the amount of nodes in your view hierarchy that have to be processed as a result. The more views that this system has to touch, the longer it takes, and some views may have worse overhead than others. And the number one overhead for this operation is having a lot of redundant unneeded views lying around in your view hierarchy. See, these views don’t visually contribute to the final scene, and generally end up just eating up your performance when a measure or layout phase actually kicks off. Thankfully, there’s a great tool available to help you find and fix these rogue views called Hierarchy Viewer. Let’s take a look.

Hierachy Viewer

Thanks, Colt. Let’s take a look at our first tool. Hierarchy Viewer is a tool that’ll help you visualize your entire UI structure at a glance, and also provide you with a better way to understand the relative rendering performance of distinct views, within that structure.

Here’s what it looks like, so let’s go ahead and set it up. You’re actually going to need some actual code to try this on, so we’re going to use Sunshine from the Android fundamentals class. You can find the details on how to pick up this code from the instructor notes down below. When you’re done, click Continue to move on. Oh, and one more note, if your phone is rooted and running Android Jellybean or newer, you're good to go, otherwise, you’re going to need to set up an environment variable on your computer, so that Hierarchy Viewer is able to communicate with your device. You can also find the details for that, in the instructions as well.

All right, so let’s go ahead and start up Hierarchy Viewer, the first thing you want to do is hop into Android Studio. Then, in order to get the Hierarchy Viewer, we’re going to want to start the Android Device Monitor. Now, you can do that through a menu, you can come up to the top here, click Tools, go to Android, and then select Android Device Monitor. Or you can click this little handy green little icon, that also does the same thing. So I’m going to go ahead and click that to start the Android Device Monitor. So here we are in the blank default layout of Android Device Monitor, now to get to Hierarchy Viewer, you’re going to want to open up a perspective. So I’m going to do that, by clicking this menu here, and then clicking on Hierarchy view, and then click OK to launch it. All right, so here’s the default layout for Hierarchy Viewer. If your screen looks different than this, you can go to the default view by clicking the Window menu, and click on Reset Perspective, that’ll reset things for you.

All right, so let’s talk about the different panes that are available to you in the UI layout for hierarchy viewer. On the left here you’re going to find the windows tab, you should see your phone and a list of running applications in it. Make sure you can see sunshine amongst the list, and then go ahead and double-click to load it up. Now as you can see, hierarchy viewer shows you, your entire view structure for the activity you selected, in this case this was the main activity of our sunshine app. Now, if you hover over here this is called the tree overview, and it gives you a bird’s eye view of your entire UI structure. Now you’ll also see this view port, which allows you to move around and change what shows in the detailed view on the left. This is the detailed view. As you can see when I move the view port, the details also change in the detailed view. This is a great way to zoom in on portions of your hierarchy. Now if you hopped over to the tree view, there’s this other zoom control that allows you to zoom in and out on your structure as well, like so. I can also drag the layout to move the position, zoom in and out to get a little bit more context.

Now when you click on a view, you can see all of its properties, in the view properties tab, that opens up here on the left hand side. I’m going to go ahead and expand it, and it’s got these collapsible menus that show you various details about the view. For example, I can look at events and other details related to drawing on the screen. Things like alpha, things like pivot, things like rotation. Now, double-click on a view to get a preview of what’s drawn to the screen of this particular view, that you’ve selected. Now, let’s move over here to the lower right. This is the Layout view. Now, when you click on a view on the hierarchy, the Layout view highlights the same view in that Layout view. In other words, you can look at this Layout view as a wireframe off your device’s screen markup. So you can select items in the detail view and see where they will be laid out on your device’s screen. You could also go the reverse direction and click items here, and see items light up in the tree view on the top above it. So for example, if I click on this box right here, this corresponds to this view over here, and if I move the view pager, and zoom in a little bit, I can drill down into details about this view.

Now unfortunately you can’t go directly from hierarchy viewer to your source code, because it's actually connected to your running app, not the source itself. But each node shows the type and the ID, so that you can reference it later or find it later in your source code. All right, so I want to step back for a second and I want to make sure that you know that hierarchy viewer is really useful in two key ways:

  • The first, it’s going to help you understand your structure of your user interface from Android’s perspective.

  • And two, it can help you figure out, where you might have superfluous views, or opportunities to flatten your view hierarchy to save memory, and improve rendering performance.

But now some real performance insights come from our per node profiling feature and hierarchy viewer. The tool models, the overall rendering process of the entire user interface, and provides rendering data for a particular node relative to the other nodes in that particular tree.

All right, so lets go back over here to our detailed tree view. Now click on a root node of a sub-tree that you actually want to profile. Just for test purposes right now. I’m going to go ahead and click on this action bar container. Now in order to invoke the profiling feature of hierarchy viewer, you’re going to want to click on this Venn diagram icon, right here. So, I’m going to go ahead and do that right now. Now, let’s drill down a little bit more specifically. Now, as you may notice, each view gets three distinct dots, and they can be of different colors, green yellow or red, and we’ll talk about what that means in a little bit.

  • But, there’s also a meaning to the order of the dots. Now the left most dot represents the measure phase of the rendering pipeline, the middle one, the layout phase, and the right most one, the draw phase of the rendering pipeline.

  • All right, so lets talk about the colors now. Now the colors of the dots indicates the relative performance of this node, in respect to all other profiled nodes.

  • So what do we mean by relative performance? Now let’s take a look at this one. Greens means that for this phase of the pipeline, this view renders faster than at least half of the other views. Now, if you see yellow, it means it’s in the bottom 50%.

Now, if you see red, that means it’s one of the slowest nodes in your view hierarchy. All right, so here’s a little bit of insight. A red node is a potential problem in any situation where you would not expect slow performance. For example, in a leaf node or a view with only a few children, that should be a red flag. Now also remember, when dealing with a large hierarchy something does need to be the slowest node, so the question is, is it the node that you expect it to be? Also keep in mind that just because it’s performing relatively slow, it might not be performing absolutely slow, that’s where the actual numbers are helpful. All right, so now that you’re familiar with hierarchy viewer, let’s go ahead and try it on some sample code for this lesson.

Case Study: Flatten View Hierarchy

Now as you may know, when you’re building a user interface for Android, it’s generally good practice to keep your layouts as simple and as flat as possible. Fredo had some good advice. Remember that inflating your layouts is an expensive process. Each additional nested layout and included view directly impacts the performance and responsiveness of your application. So remember, gain information and then gain insight into your app’s behavior.

All right, so here I am back in Android Device Monitor and I’ve got the hierarchy view perspective open. And as before, let’s go over to our windows pane here, select our device, and choose the activity we’d like to inspect. In this case, we’re going to look at the mobile perf compare layouts activity. So I’m going to go ahead and double click that to load it up. All right, so I want to zoom in on the particular area of the layout here. In particular this root node here which is our linear layout. This is the root view group that’s going to be displaying these two lines that we’re looking at right here. Now I want you to notice the two different childs that come off of this parent linear layout. One of them represents the first line here of our chat interface, but it’s implemented using a nested linear layout. Now the second child here corresponds to our second line in our layout. And instead of using a nested design, we’ve used a flat design using the relative layout view group. So, if we hop back into Android Studio, this is what it corresponds to in XML.

We’re back in Android Studio, looking at our source, and we’re down in our layouts folder, and we’re looking at the activity compare layouts XML file. Now, let me bring the phone back on the screen so we can compare. All right so we have a parent container that’s a linear layout of vertically oriented items. So the parent container here is a linear layout and the orientation is for vertical items, so items that are going to be listed from top to bottom. So what I want to draw your attention to here is for our first chat line right about here, we have one type of implementation using more of a structured or nested layout. This is little bit more intuitive and might be similar to a way when you build an application in steps and it’s very logical. So, for example, we start with a parent linear layout that’s horizontal in nature and on the left, we’re going to set up a our ImageView. And on the right, we’re going to create another nested linear container to contain our text. But in this case, the orientation is going to be vertical instead of horizontal. So that represents the first slide.

Now as I mentioned in hierarchy viewer, we also have the second line of our chat template here. And instead of using a nested structure, we’ve decided to take the same visual design and instead of build it in terms of a hierarchy, we’ve decided to look at all the elements in terms of boxes and see if we can describe them with their relative positions to one another. Now what this gives us is a flattened layout. So instead of two layers of depth, we have one or a flattened layout using this relative design. Now, what we’ve done here is a relative layout and we’ve specified our text now in relation to the image view or the placeholder for our avatar here. For example, this first line on the top is going to be the right of the avatar, and then similarly the second line of text is also going to be the right of the avatar. Now, we can also delineate which order we want the text items to follow. In this case we use the attribute layout below and this specifies that this particular text view actual comes below this particular one up here.

All right, so what does that really mean? What does that gain for us in terms of performance? So let’s hop back the hierarchy viewer. All right, so here we are back in our layout. Again, we’ve got the linear layout design up top and then the flattened relative layout design on the bottom. Now we can use the rendering/profiling feature of hierarchy viewer. We kind of get a sense of how each design behaves. So let’s select the root node here. And we’re going to click on the profile rendering button represented by this Venn diagram here. So let’s go ahead and click it. And as you can immediately see, what I want you to notice is via the yellow color here. The linear layout design is slower than the relative layout design in terms of its rendering process measure layout and draw. So let’s go ahead and click that a few more times. Again this linear layout design is imposing a bit more overhead on the rendering pipeline compared to the relative layout which is all green here. Let’s sample it a few more times. All right, they’re about even here. Let’s try one more time. All right now, so it should be clear that when we profile this parent node here, and we look and inspect at the two distinct layout designs, we notice that the linear layout, or the nested version, is quite a bit slower, or at least clearly slower than our flat and relative layout version.

So again, when you can see these opportunities to flatten in your layout design or when you are re-factoring, see if you can take steps to flatten your design when possible. So let’s go ahead and apply this best practice to our Chattinum Lattinum design that we looked at earlier. Let’s see if we can find some inefficiencies and flatten that layout to see if we can gain some performance.

All right, so here we are, back in Android Studio again. Let’s go ahead and look at the layout design for Chatum Latinum. So, there’s actually three files that we looked at that make up the design, but the most important one that we want to see if we can essentially flatten is the layout XML for our individual chat items, or distinct rows. All right, so just to be clear, whenever I’m going to bring up the Chatum Latinum activity again. And the layout that we’re concerned with the most here is the individual chat items that we see horizontally here. So let’s go back and take a look. So here’s the markup for our individual chat items. As you can see here, we have a similar design to almost the unoptimized version of the compare layouts activity that we looked at just previously. Now, this is a natural way to create a list item, again it’s nested in structure or following something that’s similar to the dom like the web. But this nested nature of linear layouts is not necessarily needed. And we can go ahead and flatten a bit more.

So let’s take a look at how we can do that, I’m going to bring up a branch that has an optimized version so we can talk about it. All right, so I’ve brought the phone back on this screen to kind of compare how we’re able to achieve the same design, that originally started as nested, into a flattened version using a relative layout. Now as before, we have this image view in our layout that represents the chat avatar, but when we implement using a relative layout, this can be sort of like an anchor, that we specify some other items relative to him. For example, we have a bunch of text on this screen, we’ve got the chat author name, we’ve got a date and time, and then we have the actual chat text. And this is all declared in the IDs for the individual text views.

Now, let’s see how we can orient them in a relative nature. So, for example, we took the chat author name, and we used this attribute called layout to the right, to describe it to be on the right of the avatar. In similar we also did the same for the chat text, which is also specified to the right of the chat avatar. But then we need to determine that the core of the message, actually lies below this chat author name. So we use this layout attribute to say below the chat date and time. And then lastly, we have a chat date and time here, and we do a small little adjustment to make sure that stays to the right, using the align pair right attribute. So look at that, with this relative description and this relative design we’ve flattened our layout, achieved the same thing visually and gained a bit more performance, as you saw in hierarchy viewer.

All right, so the main idea is that when you’re revisiting and refactoring your layouts, see if you can look for opportunities where you might have inefficiencies like nesting, and replace them with implementations that are a bit more flat, like this relative layout design. All right, that was great, thanks a lot.

Dialogue: Outro

>> Huh. So Chris, how did that rendering performance go the other day?
>> Oh, man, everything’s great. It’s back to being superfast.
>> Yeah.
>> You know, I hate to say it I think you were right.
>> Well Chris. It’s not about being right or wrong, even though I’m right. It’s about understanding that in the mobile application development space, it’s all about trade offs, right? You have to make performance conscious decisions between the features you want and the applications and devices that people are going to be running on. You can’t just run off into the weeds, make whatever you want, and then get mad at the system when it doesn’t perform the way you want it to.
>> Yeah, I know. That’s totally fair. Oh wait. With all of that extra frame time, more animations.
>> Well, wait, no, Chris, that’s not what I was talk-