Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Profiling UI Layouts In Android For Performance Improvements

TwitterFacebookRedditLinkedInHacker News

Something that any application should be concerned with is performance. One way to look at performance is via profiling. Most IDEs and platforms provide tools to profile your application to take a look at detailed system information about various parts of your application. With Android applications you can execute an adb command to generate a very thorough snapshot of the graphics information and that’s what we’re going to look at in this post.

What is Profiling?

Profiling is the analysis that measures the memory or complexity of a program, the usage of the program, or the frequency and duration of function calls. In simple terms it is analyzing how your program is performing on the system it is executing on.

Getting Started with the Android Debug Bridge

You will need to have adb configured on your machine since we’ll be using the Android Debug Bridge to generate reports for our application.

You will also need to know your android application’s package name, this would be something like com.instagram.android. If you are unsure of the Android app’s package name you can get this from the AndroidManifest.xml or the projects gradle file.

While this isn’t exactly profiling it is useful to determine the graphics info of your application. If you want to profile the CPU, RAM, network, etc. during execution, Android Studio provides an excellent profiler to dig deep into the system.

The ADB Command

Once adb is setup and we know the application’s package name. Execute the following command to generate a system report for our process. We are going to use Instagram as our example here.

adb shell dumpsys gfxinfo com.instagram.android > layout-profile.txt

Let’s look at this command so we understand what it’s doing.

First, we are executing a dumpsys and since we are interested in the performance related to our app’s UI we are passing gfxinfo which is for graphics information.

Then you’ll notice com.instagram.android (Instagram’s package name), we are providing this argument to the command because we are only interested in the data for one process and not the entire system.

Last you’ll notice > layout-profile.txt this is writing the output from the command to a txt file in the directory we execute this command.

Report Sample

Applications Graphics Acceleration Info:
Uptime: 537251619 Realtime: 836306786

** Graphics info for pid 2094 [com.instagram.android] **

Stats since: 530111479831959ns
Total frames rendered: 35466
Janky frames: 1167 (3.29%)
50th percentile: 5ms
90th percentile: 7ms
95th percentile: 11ms
99th percentile: 25ms
Number Missed Vsync: 141
Number High input latency: 7859
Number Slow UI thread: 314
Number Slow bitmap uploads: 0
Number Slow issue draw commands: 136
Number Frame deadline missed: 470
HISTOGRAM: 5ms=29796 6ms=1651 7ms=831 8ms=440 9ms=390 10ms=397 11ms=258 12ms=177 13ms=124 14ms=76 15ms=87 16ms=148 17ms=331 18ms=131 19ms=75 20ms=57 21ms=38 22ms=32 23ms=24 24ms=28 25ms=25 26ms=23 27ms=15 28ms=7 29ms=12 30ms=9 31ms=7 32ms=15 34ms=18 36ms=22 38ms=17 40ms=17 42ms=13 44ms=11 46ms=14 48ms=22 53ms=8 57ms=19 61ms=19 65ms=6 69ms=12 73ms=13 77ms=10 81ms=3 85ms=3 89ms=1 93ms=3 97ms=2 101ms=1 105ms=0 109ms=2 113ms=1 117ms=5 121ms=1 125ms=1 129ms=0 133ms=3 150ms=11 200ms=3 250ms=1 300ms=0 350ms=0 400ms=0 450ms=0 500ms=0 550ms=0 600ms=0 650ms=0 700ms=0 750ms=0 800ms=0 850ms=0 900ms=0 950ms=0 1000ms=0 1050ms=0 1100ms=0 1150ms=0 1200ms=0 1250ms=0 1300ms=0 1350ms=0 1400ms=0 1450ms=0 1500ms=0 1550ms=0 1600ms=0 1650ms=0 1700ms=0 1750ms=0 1800ms=0 1850ms=0 1900ms=0 1950ms=0 2000ms=0 2050ms=0 2100ms=0 2150ms=0 2200ms=0 2250ms=0 2300ms=0 2350ms=0 2400ms=0 2450ms=0 2500ms=0 2550ms=0 2600ms=0 2650ms=0 2700ms=0 2750ms=0 2800ms=0 2850ms=0 2900ms=0 2950ms=0 3000ms=0 3050ms=0 3100ms=0 3150ms=0 3200ms=0 3250ms=0 3300ms=0 3350ms=0 3400ms=0 3450ms=0 3500ms=0 3550ms=0 3600ms=0 3650ms=0 3700ms=0 3750ms=0 3800ms=0 3850ms=0 3900ms=0 3950ms=0 4000ms=0 4050ms=0 4100ms=0 4150ms=0 4200ms=0 4250ms=0 4300ms=0 4350ms=0 4400ms=0 4450ms=0 4500ms=0 4550ms=0 4600ms=0 4650ms=0 4700ms=0 4750ms=0 4800ms=0 4850ms=0 4900ms=0 4950ms=0
Font Cache (CPU):
  Size: 664.05 kB
  Glyph Count: 60
CPU Caches:
GPU Caches:
  Other:
    Buffer Object: 63.00 KB (2 entries)
    Texture: 32.00 KB (1 entry)
  Image:
    Texture: 5.95 MB (41 entries)
  Scratch:
    Buffer Object: 64.00 KB (2 entries)
    RenderTarget: 1.27 MB (5 entries)
    Texture: 3.00 MB (2 entries)
Other Caches:
                         Current / Maximum
  VectorDrawableAtlas    0.00 kB /   0.00 KB (entries = 0)
  Layers Total           0.00 KB (numLayers = 0)
Total GPU memory usage:
  10879652 bytes, 10.38 MB (7.35 MB is purgeable)


Pipeline=Skia (OpenGL)

Layout Cache Info:
  Usage: 5000/5000 entries
  Hit ratio: 191887/199638 (0.961175)
Profile data in ms:

	com.instagram.android/com.instagram.android.activity.MainTabActivity/android.view.ViewRootImpl@c358bce (visibility=0)
	AtchDlg:com.instagram.android/com.instagram.android.activity.MainTabActivity/android.view.ViewRootImpl@2b97aef (visibility=0)
View hierarchy:

  com.instagram.android/com.instagram.android.activity.MainTabActivity/android.view.ViewRootImpl@c358bce
  398 views, 346.06 kB of display lists

  AtchDlg:com.instagram.android/com.instagram.android.activity.MainTabActivity/android.view.ViewRootImpl@2b97aef
  1 views, 0.84 kB of display lists


Total ViewRootImpl: 2
Total Views:        399
Total DisplayList:  346.90 kB

No doubt that is a lot to digest and we don’t have the source to modify and play around to see what optimizations we could make so let’s try with a NativeScript application. We’ll generate the report with the basic “Hello World” sample and then we’ll add other views to the page and see how that affects memory usage.

Create a NativeScript Application

Assuming you have the NativeScript CLI installed, execute tns create profile from your terminal to generate a bare “Hello World” NativeScript application. Run the app with tns run android in the root of the project and then execute the gfxinfo command using the package name for our NativeScript application.

It should be adb shell dumpsys gfxinfo org.nativescript.profile > layout-profile.txt unless you didn’t use profile for the app name when creating. The report in the text file will look similar to the output below:

Applications Graphics Acceleration Info:
Uptime: 537741987 Realtime: 836797155

** Graphics info for pid 16911 [org.nativescript.profile] **

Stats since: 537701663540996ns
Total frames rendered: 6
Janky frames: 2 (33.33%)
50th percentile: 9ms
90th percentile: 250ms
95th percentile: 250ms
99th percentile: 250ms
Number Missed Vsync: 1
Number High input latency: 5
Number Slow UI thread: 1
Number Slow bitmap uploads: 0
Number Slow issue draw commands: 1
Number Frame deadline missed: 1
HISTOGRAM: 5ms=3 6ms=0 7ms=0 8ms=0 9ms=1 10ms=0 11ms=0 12ms=0 13ms=0 14ms=0 15ms=0 16ms=0 17ms=0 18ms=0 19ms=0 20ms=0 21ms=0 22ms=0 23ms=0 24ms=0 25ms=0 26ms=0 27ms=0 28ms=0 29ms=0 30ms=0 31ms=0 32ms=0 34ms=0 36ms=0 38ms=0 40ms=0 42ms=0 44ms=0 46ms=0 48ms=0 53ms=0 57ms=0 61ms=0 65ms=0 69ms=0 73ms=0 77ms=0 81ms=0 85ms=0 89ms=0 93ms=0 97ms=1 101ms=0 105ms=0 109ms=0 113ms=0 117ms=0 121ms=0 125ms=0 129ms=0 133ms=0 150ms=0 200ms=0 250ms=1 300ms=0 350ms=0 400ms=0 450ms=0 500ms=0 550ms=0 600ms=0 650ms=0 700ms=0 750ms=0 800ms=0 850ms=0 900ms=0 950ms=0 1000ms=0 1050ms=0 1100ms=0 1150ms=0 1200ms=0 1250ms=0 1300ms=0 1350ms=0 1400ms=0 1450ms=0 1500ms=0 1550ms=0 1600ms=0 1650ms=0 1700ms=0 1750ms=0 1800ms=0 1850ms=0 1900ms=0 1950ms=0 2000ms=0 2050ms=0 2100ms=0 2150ms=0 2200ms=0 2250ms=0 2300ms=0 2350ms=0 2400ms=0 2450ms=0 2500ms=0 2550ms=0 2600ms=0 2650ms=0 2700ms=0 2750ms=0 2800ms=0 2850ms=0 2900ms=0 2950ms=0 3000ms=0 3050ms=0 3100ms=0 3150ms=0 3200ms=0 3250ms=0 3300ms=0 3350ms=0 3400ms=0 3450ms=0 3500ms=0 3550ms=0 3600ms=0 3650ms=0 3700ms=0 3750ms=0 3800ms=0 3850ms=0 3900ms=0 3950ms=0 4000ms=0 4050ms=0 4100ms=0 4150ms=0 4200ms=0 4250ms=0 4300ms=0 4350ms=0 4400ms=0 4450ms=0 4500ms=0 4550ms=0 4600ms=0 4650ms=0 4700ms=0 4750ms=0 4800ms=0 4850ms=0 4900ms=0 4950ms=0
Font Cache (CPU):
  Size: 35.45 kB
  Glyph Count: 17
CPU Caches:
GPU Caches:
  Other:
    Buffer Object: 63.00 KB (2 entries)
  Scratch:
    Buffer Object: 64.00 KB (2 entries)
    RenderTarget: 7.44 MB (1 entry)
    Texture: 1.00 MB (1 entry)
Other Caches:
                         Current / Maximum
  VectorDrawableAtlas    0.00 kB /   0.00 KB (entries = 0)
  Layers Total           0.00 KB (numLayers = 0)
Total GPU memory usage:
  8977408 bytes, 8.56 MB (7.56 MB is purgeable)


Pipeline=Skia (OpenGL)

Layout Cache Info:
  Usage: 13/5000 entries
  Hit ratio: 39/52 (0.750000)
Profile data in ms:

	org.nativescript.profile/com.tns.NativeScriptActivity/android.view.ViewRootImpl@f1ce416 (visibility=0)
View hierarchy:

  org.nativescript.profile/com.tns.NativeScriptActivity/android.view.ViewRootImpl@f1ce416
  15 views, 13.73 kB of display lists


Total ViewRootImpl: 1
Total Views:        15
Total DisplayList:  13.73 kB

Modify the User Interface and Try Again

Let’s make a small UI change to our app by modifying the main-page.xml to have the following markup:

<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo" class="page">
    <Page.actionBar>
        <ActionBar title="My App" icon="" class="action-bar">
        </ActionBar>
    </Page.actionBar>
    <StackLayout class="p-20">
        <Label text="Tap the button" class="h1 text-center"/>
        <Button text="TAP" tap="{{ onTap }}" class="btn btn-primary btn-active"/>
        <GridLayout rows="*, *, *" columns="*, *, *, *">
            <Image src="res://icon" row="0" col="0" stretch="none" />
            <Image src="res://icon" row="0" col="1" stretch="none" />
            <Image src="res://icon" row="0" col="2" stretch="none" />
            <Image src="res://icon" row="0" col="3" stretch="none" />
            <Button row="1" col="0" text="YES" tap="" />
            <Button row="1" col="1" text="YES" tap="" />
            <Button row="1" col="2" text="YES" tap="" />
            <Button row="1" col="3" text="YES" tap="" />
            <Image row="2" col="0" src="https://secure.gravatar.com/avatar/c53602f132b37e1e904df9d7bc431732?s=160&d=identicon&r=g" stretch="none" />
            <Image row="2" col="1" src="https://secure.gravatar.com/avatar/c53602f132b37e1e904df9d7bc431732?s=160&d=identicon&r=g" stretch="none" />
            <Image row="2" col="2" src="https://secure.gravatar.com/avatar/c53602f132b37e1e904df9d7bc431732?s=160&d=identicon&r=g" stretch="none" />
            <Image row="2" col="3" src="https://secure.gravatar.com/avatar/c53602f132b37e1e904df9d7bc431732?s=160&d=identicon&r=g" stretch="none" />
        </GridLayout>
        <Label text="{{ message }}" class="h2 text-center" textWrap="true"/>
    </StackLayout>
</Page>

All we are doing is adding some simple images and buttons to the page to see the difference when running the gfxinfo report which is below.

Applications Graphics Acceleration Info:
Uptime: 537919744 Realtime: 836974911

** Graphics info for pid 16911 [org.nativescript.profile] **

Stats since: 537701663540996ns
Total frames rendered: 12
Janky frames: 4 (33.33%)
50th percentile: 10ms
90th percentile: 133ms
95th percentile: 250ms
99th percentile: 250ms
Number Missed Vsync: 2
Number High input latency: 6
Number Slow UI thread: 2
Number Slow bitmap uploads: 0
Number Slow issue draw commands: 1
Number Frame deadline missed: 2
HISTOGRAM: 5ms=3 6ms=0 7ms=0 8ms=0 9ms=3 10ms=1 11ms=0 12ms=0 13ms=1 14ms=0 15ms=0 16ms=0 17ms=0 18ms=0 19ms=0 20ms=0 21ms=0 22ms=0 23ms=0 24ms=0 25ms=0 26ms=1 27ms=0 28ms=0 29ms=0 30ms=0 31ms=0 32ms=0 34ms=0 36ms=0 38ms=0 40ms=0 42ms=0 44ms=0 46ms=0 48ms=0 53ms=0 57ms=0 61ms=0 65ms=0 69ms=0 73ms=0 77ms=0 81ms=0 85ms=0 89ms=0 93ms=0 97ms=1 101ms=0 105ms=0 109ms=0 113ms=0 117ms=0 121ms=0 125ms=0 129ms=0 133ms=1 150ms=0 200ms=0 250ms=1 300ms=0 350ms=0 400ms=0 450ms=0 500ms=0 550ms=0 600ms=0 650ms=0 700ms=0 750ms=0 800ms=0 850ms=0 900ms=0 950ms=0 1000ms=0 1050ms=0 1100ms=0 1150ms=0 1200ms=0 1250ms=0 1300ms=0 1350ms=0 1400ms=0 1450ms=0 1500ms=0 1550ms=0 1600ms=0 1650ms=0 1700ms=0 1750ms=0 1800ms=0 1850ms=0 1900ms=0 1950ms=0 2000ms=0 2050ms=0 2100ms=0 2150ms=0 2200ms=0 2250ms=0 2300ms=0 2350ms=0 2400ms=0 2450ms=0 2500ms=0 2550ms=0 2600ms=0 2650ms=0 2700ms=0 2750ms=0 2800ms=0 2850ms=0 2900ms=0 2950ms=0 3000ms=0 3050ms=0 3100ms=0 3150ms=0 3200ms=0 3250ms=0 3300ms=0 3350ms=0 3400ms=0 3450ms=0 3500ms=0 3550ms=0 3600ms=0 3650ms=0 3700ms=0 3750ms=0 3800ms=0 3850ms=0 3900ms=0 3950ms=0 4000ms=0 4050ms=0 4100ms=0 4150ms=0 4200ms=0 4250ms=0 4300ms=0 4350ms=0 4400ms=0 4450ms=0 4500ms=0 4550ms=0 4600ms=0 4650ms=0 4700ms=0 4750ms=0 4800ms=0 4850ms=0 4900ms=0 4950ms=0
Font Cache (CPU):
  Size: 37.09 kB
  Glyph Count: 19
CPU Caches:
GPU Caches:
  Other:
    Buffer Object: 63.00 KB (2 entries)
  Image:
    Texture: 648.06 KB (8 entries)
  Scratch:
    Buffer Object: 64.00 KB (2 entries)
    RenderTarget: 7.44 MB (1 entry)
    Texture: 1.00 MB (1 entry)
Other Caches:
                         Current / Maximum
  VectorDrawableAtlas    0.00 kB /   0.00 KB (entries = 0)
  Layers Total           0.00 KB (numLayers = 0)
Total GPU memory usage:
  9641024 bytes, 9.19 MB (8.19 MB is purgeable)


Pipeline=Skia (OpenGL)

Layout Cache Info:
  Usage: 14/5000 entries
  Hit ratio: 94/108 (0.870370)
Profile data in ms:

	org.nativescript.profile/com.tns.NativeScriptActivity/android.view.ViewRootImpl@f1ce416 (visibility=0)
View hierarchy:

  org.nativescript.profile/com.tns.NativeScriptActivity/android.view.ViewRootImpl@f1ce416
  28 views, 24.74 kB of display lists


Total ViewRootImpl: 1
Total Views:        28
Total DisplayList:  24.74 kB

Now that we have a report with zero modifications to the “Hello World” template vs. a small change to add several images and buttons we can see that we increased the Total GPU memory usage, which is to be expected. We also increased the Total DisplayList, again normal and to be expected since more UI views were added to the page.

A key take away from the report is looking at the Total GPU memory usage data. I’ve worked on several applications that had layouts that were duplicating the device’s drawing effort to render go from 23+MB to ~10-12MB. Also remember that this concept applies to any target platform that renders a UI. The more UI to measure and draw, the more resources the machine will use to do so.

How to Improve Your Skills

  • Flatten your layouts so that you reduce the resources for measuring and drawing the UI.
    • This was talked about at the NativeScript developer days conference in 2017 - video here.
  • Compress and size images correctly.
  • Use list views (view recyclers) also known as virtualization list components on the web.
  • Profile and measure your changes during development to see if performance is improving.

Conclusion

In this post we reviewed how to generate graphics information reports using the Android Debug Bridge (ADB) for Android applications. With the reports you can view many statistics related to the applications frame and graphics performance to determine how well the UI is functioning. This is scratching the surface of profiling the layouts of Android applications but it should provide enough insight to monitor your development and be aware of your app’s layouts and implementation.

Brad Martin

Brad Martin

I'm a husband, father and full stack software developer. You can find out more about me on my personal blog @ http://bradmartin.net