1

Faster Jetpack Compose <-> View interop with App Startup and baseline prof...

 1 year ago
source link: https://medium.com/androiddevelopers/faster-jetpack-compose-view-interop-with-app-startup-and-baseline-profile-8a615e061d14
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Faster Jetpack Compose <-> View interop with App Startup and baseline profile

1*IwQ-gLX4-1X3T2Cf_LhgGQ.png

Jetpack Compose is designed to be interoperable with an existing View-based app. This enables you to take an incremental approach to migrating your existing app’s UI to Compose.

While your app is in the middle of migration, and doesn’t yet have a Composable on the home or landing screen, users might notice jank when they navigate to a screen built with Compose. This is because the Compose library is loaded only when it is used for the first time. There is one workaround that has been floating around to add a Composable pixel on the home/landing screen. This workaround ensures that Compose components are warmed up before they are used. We don’t recommend taking this approach since enforcing it in your app results in a code that doesn’t contribute to your feature implementation and may lead to unforeseen bugs. Rather, we recommend a combination of the App Startup library and custom baseline profiles to solve the jank issue. We will benchmark and visualize how the combination can help to improve the app’s performance.

Using Baseline Profiles

Baseline Profiles help improve app startup and runtime performance of Android applications. Baseline Profiles are a list of classes and methods included in an APK. They are used by Android Runtime (ART) during app installation to pre-compile critical paths into machine code. This is a form of profile-guided optimization (PGO) that lets apps optimize startup, reduce jank, and improve performance for end users by reducing the amount of code that has to be Just-In-Time (JIT) compiled.

Jetpack Compose is distributed as a library. This enables frequent updates to Compose and backward compatibility to older Android versions. But distributing as an unbundled library comes with a cost; libraries need to be loaded when the app launches, and interpreted just-in-time when the functionality is needed (for more details checkout Why should you always test Compose performance in release?). This can lead to longer app startup time or jank whenever the app uses a library feature for the first time. To solve this issue, Compose provides a Baseline Profile to ahead-of-time compile Compose when the app is installed. In most cases, the default Baseline Profile provided by Compose is enough to deliver great performance. But customizing the profile based on your app’s common user interactions often produces better results.

Performance analysis

To analyze this performance improvement, we wrote some Macrobenchmark tests and measured the performance on the Sunflower sample app. The app is partially migrated to Compose and doesn’t have any Composable on the landing/home screen.

Screenshots from the sunflower app showcasing the flow. One of the screens is built with RecyclerView with each item built in Compose. Another screen is built in Compose and uses AndroidViewBinding to interact with the View.

For this performance analysis, we will be interacting with three screens from the app:

  • The Home screenthat is built with View based design and doesn’t have any Composables in it.
  • The Plant List screen is built with RecyclerView and each item inside the view is built with Compose. It uses the ComposeView View to host the Compose content.
  • The Plant details screen that is built with Compose and uses AndroidViewBinding to create Android layout resources and interact with it.

The Sunflower app code has a BaselineProfileGenerator class that creates a baseline profile. We executed the test and created a custom baseline profile focused on these critical user journeys (CUJs). This profile is available inside the baseline-prof.txt file under the main source-set. Compared to the default baseline profile provided by Compose, the custom baseline profile does show performance improvements. Let’s take a look at the test results for a couple of scenarios:

  • [Scenario 1]From the Home screen, the user navigates to the Plant List screen that is partially built in Compose. The benchmark test is available in the PlantListBenchmarks class.

Macrobenchmark runs your test multiple times and outputs the results as a statistical distribution. The distribution is represented in percentile P50P99. Following were the results when we ran the benchmark tests:

1*PvEcNbJh7rFvR10eYh2OhA.png

For the data shown in the table above, P50 for CompilationMode.None means, 50% of frames were rendered faster than 7.1 milliseconds.

  • [Scenario 2] User navigates to a screen fully built with Compose and uses AndroidViewBinding to interact with TextView. The benchmark test is available in the PlantDetailBenchmarks class. Following were the results when we ran the benchmark tests:
1*6fSC9w90LrK8UPuJ-nbigA.png

Note: The benchmark tests were executed on Samsung Galaxy Fold, your benchmark results may show different numbers but overall performance impact should be similar.

When you take a close look at P99 numbers, you will notice significant improvements. These outliers are usually the ones that cause jank.

To further improve app performance we can use the App Startup library to warm up the Compose components before they are actually used. Let’s give it a try and see what we discover.

Warm up Compose library with App Startup library

The App Startup library provides a structured way to initialize components at application startup. Using the App Startup library, you can warm up the Compose library during app startup.

Follow these steps to add the Compose initializer using App Startup library:

  • To use Jetpack startup in your app, add the dependency on startup-runtime in your app’s build.gradle file.

To warm up the Compose component, create a new ComposeInitializer class as shown in the following code snippet.

​​ProcessLifecycleInitializer ensures that the Compose library is warmed up only when at least one activity is going to be visible.

Initializing the ComposeView this way won’t add the content to the view hierarchy, but it still helps to initialize the Compose component.

  • To make the ComposeInitializer discoverable by App Startup, navigate to AndroidManifest.xml file in your project. Add a <meta-data> entry under InitializationProvider manifest entry as shown in the code below:

InitializationProvider is a special content provider that helps to discover and call the component initializer you have just defined.

  • Re-generate and update the baseline profile to take into account the changes to startup. The baseline profile generator is part of macrobenchmark module. You just need to execute the startPlantListPlantDetail test from BaselineProfileGenerator class.
  • Re-run the macrobenchmark tests from the PlantListBenchmarks and PlantDetailBenchmarks class.

Here’s what we discovered for each of the previous scenarios:

  • [Scenario 1]From HomeScreen, the user navigates to the Plant list screen.
1*5gGPDMSeGSJBTLoMKgoY9w.png

When you take a close look, for the screen that is partially built with Compose, the outliers seem to have an improvement of ~21%. It is often the outlier frames that cause jank.

  • [Scenario 2] User navigates to the Plant Details screen.
1*o8EziAii03kepkVD3sNJ5w.png

There is ~27% percent improvement in the outliers.

In both the cases, adding App Startup initializer improved the performance, especially for the outliers in P99.

Here is the link to access the code on GitHub.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK