Tag Archives: Performance Optimization

Performance Class helps Google Maps deliver premium experiences

Posted by Nevin Mital - Developer Relations Engineer, Android Media

The Android ecosystem features a diverse range of devices, and it can be difficult to build experiences that take advantage of new or premium hardware features while still working well for users on all devices. With Android 12, we introduced the Media Performance Class (MPC) standard to help developers better understand a device’s capabilities and identify high-performing devices. For a refresher on what MPC is, please see our last blog post, Using performance class to optimize your user experience, or check out the Performance Class documentation.

Earlier this year, we published the first stable release of the Jetpack Core Performance library as the recommended solution for more reliably obtaining a device’s MPC level. In particular, this library introduces the PlayServicesDevicePerformance class, an API that queries Google Play Services to get the most up-to-date MPC level for the current device and build. I’ll get into the technical details further down, but let’s start by taking a look at how Google Maps was able to tailor a feature launch to best fit each device with MPC.

Performance Class unblocks premium experience launch for Google Maps

Google Maps recently took advantage of the expanded device coverage enabled by the Play Services module to unblock a feature launch. Google Maps wanted to update their UI by increasing the transparency of some layers. Consequently, this meant they would need to render more of the map, and found they had to stop the rollout due to latency increases on many devices, especially towards the low-end. To resolve this, the Maps team started by slicing an existing key metric, “seconds to UI item visibility”, by MPC level, which revealed that while all devices had a small increase in this latency, devices without an MPC level had the largest increase.

A bar graph displays A/B test results for Seconds to UI item visibility, comparing control results with those using increased transparency across different Media Performance Class Levels.  A green horizontal line and text indicate the updated experience shipped to devices qualifying for MPC. A vertical green dotted line separates results for devices without a specific MPC level, which kept the previous UI.

With these results in hand, Google Maps started their rollout again, but this time only launching the feature on devices that report an MPC level. As devices continue to get updated and meet the bar for MPC, the updated Google Maps UI will be available to them as well.

The new Play Services module

MPC level requirements are defined in the Android Compatibility Definition Document (CDD), then devices and Android builds are validated against these requirements by the Android Compatibility Test Suite (CTS). The Play Services module of the Jetpack Core Performance library leverages these test results to continually update a device’s reported MPC level without any additional effort on your end. This also means that you’ll immediately have access to the MPC level for new device launches without needing to acquire and test each device yourself, since it already passed CTS. If the MPC level is not available from Google Play Services, the library will fall back to the MPC level declared by the OEM as a build constant.

A flowchart depicts the process of determining Performance Class levels for Android devices, involving manufacturers, CTS tests, a Grader, the Play Services module, and the CDD.

As of writing, more than 190M in-market devices covering over 500 models across 40+ brands report an MPC level. This coverage will continue to grow over time, as older devices update to newer builds, from Android 11 and up.

Using the Core Performance library

To use Jetpack Core Performance, start by adding a dependency for the relevant modules in your Gradle configuration, and create an instance of DevicePerformance. Initializing a DevicePerformance should only happen once in your app, as early as possible - for example, in the onCreate() lifecycle event of your Application. In this example, we’ll use the Google Play services implementation of DevicePerformance.

// Implementation of Jetpack Core library.
implementation("androidx.core:core-ktx:1.12.0")
// Enable APIs to query for device-reported performance class.
implementation("androidx.core:core-performance:1.0.0")
// Enable APIs to query Google Play Services for performance class.
implementation("androidx.core:core-performance-play-services:1.0.0")

import androidx.core.performance.play.services.PlayServicesDevicePerformance

class MyApplication : Application() {
  lateinit var devicePerformance: DevicePerformance

  override fun onCreate() {
    // Use a class derived from the DevicePerformance interface
    devicePerformance = PlayServicesDevicePerformance(applicationContext)
  }
}

Then, later in your app when you want to retrieve the device’s MPC level, you can call getMediaPerformanceClass():

class MyActivity : Activity() {
  private lateinit var devicePerformance: DevicePerformance
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Note: Good app architecture is to use a dependency framework. See
    // https://developer.android.com/training/dependency-injection for more
    // information.
    devicePerformance = (application as MyApplication).devicePerformance
  }

  override fun onResume() {
    super.onResume()
    when {
      devicePerformance.mediaPerformanceClass >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> {
        // MPC level 34 and later.
        // Provide the most premium experience for the highest performing devices.
      }
      devicePerformance.mediaPerformanceClass == Build.VERSION_CODES.TIRAMISU -> {
        // MPC level 33.
        // Provide a high quality experience.
      }
      else -> {
        // MPC level 31, 30, or undefined.
        // Remove extras to keep experience functional.
      }
    }
  }
}

Strategies for using Performance Class

MPC is intended to identify high-end devices, so you can expect to see MPC levels for the top devices from each year, which are the devices you’re likely to want to be able to support for the longest time. For example, the Pixel 9 Pro released with Android 14 and reports an MPC level of 34, the latest level definition when it launched.

You should use MPC as a complement to any existing Device Clustering solutions you already use, such as querying a device’s static specs or manually blocklisting problematic devices. An area where MPC can be a particularly helpful tool is for new device launches. New devices should be included at launch, so you can use MPC to gauge new devices’ capabilities right from the start, without needing to acquire the hardware yourself or manually test each device.

A great first step to get involved is to include MPC levels in your telemetry. This can help you identify patterns in error reports or generally get a better sense of the devices your user base uses if you segment key metrics by MPC level. From there, you might consider using MPC as a dimension in your experimentation pipeline, for example by setting up A/B testing groups based on MPC level, or by starting a feature rollout with the highest MPC level and working your way down. As discussed previously, this is the approach that Google Maps took.

You could further use MPC to tune a user-facing feature, for example by adjusting the number of concurrent video playbacks your app attempts based on the MPC level’s concurrent codec guarantees. However, make sure to still query a device’s runtime capabilities when using this approach, as they may differ depending on the environment and state the device is in.

Get in touch!

If MPC sounds like it could be useful for your app, please give it a try! You can get started by taking a look at our sample code or documentation. We welcome you to share any questions or feedback you have in this short form.


This blog post is a part of Camera and Media Spotlight Week. We're providing resources – blog posts, videos, sample code, and more – all designed to help you uplevel the media experiences in your app.

To learn more about what Spotlight Week has to offer and how it can benefit you, be sure to read our overview blog post.

Accurately Measure Android App Performance with Profileable Builds


Posted by Yi Yang (Software Engineer)
It’s important to stay on top of your app performance to make sure your users can easily use your app. When an app experiences issues such as animation jank, frozen frames, and high memory usage, it negatively impacts the user experience which could lead to lower ratings or app deletion. To fix these performance issues, we first need the right tools to measure app performance correctly.
This is where profiling comes in. Profiling helps you find where CPU cycles and memory are spent at the time of inspection which makes it easier for you to pinpoint performance bottlenecks in your app. Android Studio offers a suite of profilers to help with the inspection.
Screenshot of Android Studio profilers
Historically, profiling an Android app required a debug build.
Screenshot of the debug/release build variants in Android Studio

The debug build allows you to use features useful for development, like Apply Changes, working with the debugger, or the Database Inspector. In addition, it also enables profiling tools to inspect the state of a running app unavailable to the release build.

Under the hood, the debug build sets the debuggable flag in the Android Manifest to true.

AndroidManifest.xml


<application android:debuggable="true">

  ...

</application>


While useful, the debug build is meant to provide more information at the cost of performance. That’s because when debuggable is true, a lot of compiler optimizations are turned off.
Screenshot of the Profile HWUI rendering setting in Developer Options. The option is in Developer Options > Monitoring > Profile HWUI rendering > On screen as bars
To show you the performance difference between the debug and release builds, we recorded an app running on the same device but in these two build variants. To visualize the frame rendering time, we turned on Profile GPU Rendering (or Profile HWUI rendering in some Android versions) in Developer Options when recording the screen. Each vertical bar on the bottom of the screen represents how long each frame takes to render. The shorter these bars are, the smoother the animation is.

The screen recording below shows the same app running on the same device. The left-hand side is on a debug build, the right-hand side a release build. The debug version has more stuttering frames, also known as UI jank. This means when you profile the debug build, you may see timing measurements significantly different from what your users see in the release build, and you may end up optimizing something that is not the problem.
GIF showing the performance difference between debug and release builds












To address that issue, the Android platform introduced a tag called profileable. It enables many profiling tools that measure timing information, without the performance overhead of the debug build. Profileable is available on devices running Android 10 or higher.

AndroidManifest.xml

<application>
    <profileable android:shell=["true" | "false"] />
</application>

Let’s look at another screen recording. This time, the left side shows a profileable release app and the right side an unmodified release app. There’s little performance difference between the two.

GIF showing the performance difference between profileable and release builds


With profileable, you can now measure the timing information much more accurately than the debug build.

This feature is designed to be used in production where app security is paramount. Therefore we decided to only support profiling features such as Callstack Sampling and System Trace, where timing measurement is critical. The Memory Profiler only supports Native Memory Profiling. The Energy Profiler and Event Timeline are not available. The complete list of disabled features can be found here. All these restrictions are put in place to keep your app's data safe.

Now that you know what the profileable tag does, let me show you how to use it. There are two options: automatically and manually.


Option 1: Use the option in Android Studio.

With Android Studio Flamingo and Android Gradle Plugin 8.0, all you need to do is just select this option from the Profile dropdown menu in the Run toolbar: “Profile with low overhead”. Then Android Studio will automatically build a profileable app of your current build type and attach the profiler. It works for any build type, but we highly recommend you to profile a release build, which is what your users see.

Screenshot of the one-click profileable builds feature in Android Studio Flamingo Canary
When a profileable app is being profiled, there is a visual indicator along with a banner message. Only the CPU and Memory profilers are available.
Screenshot of Android Studio profiler profiling a profileable build
In the Memory Profiler, only the native allocation recording feature is available due to security reasons.
Screenshot showing Android Studio memory profiler features when profiling a profileable build











This feature is great for simplifying the process of local profiling but it only applies when you profile with Android Studio. Therefore, it can still be beneficial to manually configure your app in case you want to diagnose performance issues in production or if you’re not ready to use the latest version of Android Studio or Android Gradle plugin yet.


Option 2: Manual configuration.

It takes 4 steps to manually enable profileable.

1.    Add this line to your AndroidManifest.xml.

 AndroidManifest.xml

<application>
  <profileable android:shell="true" />
</application>

2.    Switch to the release build type (or any build type that’s not debuggable).
Screenshot of selecting the active build variant in Android Studio
















3.    Make sure you have a signing key configured. To prevent compromising your release signing key, you can temporarily use your debug signing key, or configure a new key just for profiling.




 








 




  

 
4.    Build and run the app on a device running Android 10 or higher. You now have a profileable app. You can then attach the Android Studio profiler by launching the Profiler tool window and selecting the app process from the dropdown list.
Screenshot of process selection in Android Studio profilersMany of you may wonder if it is safe to leave the profileable manifest tag in production and the answer is yes. This tag is designed to be usable in release builds to enable local profiling. No memory data is readable by the host profiling tools and the shell process. Only stack traces are readable, which are typically obfuscated or lacking symbols in release builds.

In fact, many first-party Google apps such as Google Maps ship their app to the Play Store as profileable apps.

Screenshot showing Google Maps as a profileable process in the profiler process dropdownIn summary, profiling the debug build may skew the performance and therefore it’s better to analyze the release build with the profileable tag enabled.

Here’s a table that shows which build type should be used:

ReleaseProfileable ReleaseDebug
ProductionProfiling CPU timingDebugger, Inspectors, etc.

Profiling memory, energy, etc.

To learn more about profilable builds, start by reading the documentation and the the user guide.

With these tools provided by the Android team, we hope you can make your app run faster and smoother.