[MUSIC PLAYING]
TOMAS MLYNARIC: Welcome back to another episode of MAD Skills
and Performance.
This time, we'll take a look at how to improve jank and app
startup.
There are many ways to improve performance
based on what type of problem you may
have found while inspecting.
In this video, we're focusing on one particular way
to improve performance regardless
of a problem you may have found during inspection using
baseline profiles.
We've seen huge app startup and runtime improvements
with many apps.
For example, Google Maps for Android
improved the cold startup time by 30%
after introducing baseline profiles.
This resulted in a positive shift in business metrics
as well.
Searches increased by over 2% as a result of that.
In the Google Play app, baseline and profiles
were introduced as well.
This is especially interesting because the Google Play
app uses Jetpack Compose.
Compose is distributed as a library instead of being
part of the Android platform.
This allows supporting older Android versions
and updating it frequently.
But it imposes a cost.
Libraries need to be loaded when the app launches and need
to be interpreted just in time when the functionality is
needed.
Baseline profiles effectively eliminate this cost,
which, in the case of the Google Play app,
resulted in the improvements of cold startup by 40%.
So what are baseline profiles, actually?
Baseline profiles are a list of classes and methods
that are ahead of time compiled at up installation
on your device.
This lets applications optimize startup, reduce jank,
and improve performance for end users.
You can generate a baseline profile for your application,
or, if you're a library developer,
you can generate, and distribute the rules with your library,
providing performance improvements
to all its clients.
For example, Jetpack Compose does
just that to minimize the impact of being an unbundled UI
toolkit.
When generating the baseline profiles,
you should cover the most important user
journeys in your app.
And we recommend using the Jetpack Macrobenchmark
library to generate it for you.
We've introduced the library in the previous episode
on Inspecting Performance, and we will
build on what we did there.
So if you haven't seen the episode yet,
we encourage you to do so now.
OK, let's get back to the nowinandroid sample app
and generate a baseline profile.
First, when generating a profile,
your app should not be obfuscated.
We recommend creating a separate proguard rule, which contains
don't obfuscate, just for the build type
that generates the profile.
Now, let's create an instrumentation test class
that will take care of generating the profile.
We need to annotate this class with the RunWith
to be a proper instrumentation test.
This time, we'll use a different rule from the Macrobenchmark
library--
the BaselineProfile rule.
Now, we'll write a test method.
Let's call it generate.
Use the rule and call it collectBaselineProfile method.
This method requires two parameters-- first,
the package name of the app to generate a profile for;
and then the lambda parameter in which you write interactions
with your app, similar to benchmarks.
The collectBaselineProfile method
will run the defined interactions several times
during which it will collect the executed methods.
This will be saved into a text file on the device
and copied onto your hosting machine.
Let's start with the most basic baseline profile, app startup.
For that, you can use the startActivityAndWait helper
function that will start your default activity
and wait until the first frame is drawn.
At this time, you will already see benefits
of startup improvements.
You can extend it more by waiting for the fully
drawn state of your app.
We will use the device.wait methods to wait until the text,
"What are you interested in?" is shown on screen--
because that's when users can start interacting with our app.
To create an even more advanced baseline profile,
you can cover other most important user journeys.
In our case, let's include scrolling the content feed.
First, we need to select some authors or topics
as a source of our feed.
We can do it with the device.find object function
and pass by resource Selector.
This selector needs to match a modifier.testTag in the app.
Then we can find our feed list and scroll it down
with the fling or other gesture.
Now, our generator class is ready to run.
Because baseline profiles are generated
in a system folder that is not accessible by default
without root access, generators require a rooted device
to be run on.
This can either be a physical device,
if you have one, or even an Android emulator,
because we're not measuring performance but indexing
classes and method calls.
One way you could run it is manually starting the emulator,
switching to root mode with adb root command,
and running the tests as any instrumentation test.
But this requires several manual steps.
Instead, let me introduce you to a new Android Gradle plugin
feature--
Gradle Managed Devices.
With Gradle Managed Devices, you can
configure an emulator in your build script.
This emulator configuration can be checked into version control
and easily be shared across development environments.
Gradle enables you to run instrumentation tests
without the need to manually handle
the emulator they run on, making builds even more reproducible.
When you run tests, Gradle will automatically
take care of downloading the emulator image, if needed,
and starting it up.
Then it will run the tests you specify and copy the results
into your host machine.
Afterwards, it will tear down the emulator,
clearing the device state to be ready for next use.
Let's set up a Gradle Managed Device now.
All you need to do is open your build.Gradle.KTS file,
and within the test options, configuration block.
Add managedDevices, devices, and create
your definition of an emulator.
So in our case, let's name it pixel2Api31.
We need to use the ManagedVirtualDevice class here
and specify the details.
So the device is Pixel 2 running API level 31.
You can have either an AOSP emulator, which has root access
and doesn't come with Google Play,
or one that comes with Google Play but can't be rooted.
We pick AOSP because we need root access
for the baseline profile generator.
After a Gradle sync, we can run the generator.
You can run the tests with a Gradle command
either from the terminal or from the Gradle pane
in Android Studio.
The task consists of benchmark, as the module name
we want to run the tests from; Pixel2Api31,
which is the device name we've defined earlier; Benchmark--
that is the build type we use.
If you use build flavors in your benchmarks,
you need to specify it here.
And we've finished with Android test.
If we'd hit Enter now, Gradle would run all the tests
from the benchmark module.
But we only want to run the baseline profile generator,
as benchmarks need to run on a physical device.
For that, we can filter generator to be run only.
We can use android.testInstrumentRunner
arguments with a custom parameter
from the library, which is androidx.benchmark.enabledRules
with the value BaselineProfile.
And now we can run the Gradle task.
Once the generator is done, we need
to add the profile to the app.
You can find the file in the benchmark module's build
folder.
From here, you need to copy the file
and paste it into the src/main folder next to the Android
manifest.
And rename it to baseline-prof.txt.
Now, you need to add the profile installer dependency
to your app.
This library allows picking up the compiled baseline profile
locally for benchmarking.
But also, it enables the benefits of baseline profiles
on older Android platform versions
and even on devices where Google Play services are not
available.
At this point, we have correctly applied a baseline profile.
From now on, when you build your prediction application,
it will automatically pick the generated file,
include it in your app, and speed up the defined journeys.
Your users will see the improved experience immediately
after installing the new version of your app.
We created a baseline profile for our app.
Now, let's verify that there are performance improvements thanks
to this.
We can measure the effectiveness of baseline profiles
with the same library with Jetpack Macrobenchmark.
You can do it by adding another parameter
to measure repeated function, namely CompilationMode.
The default value of this parameter
tries to apply an existing baseline profile that may
come from a library you use.
If it doesn't find any, it will just ignore it
and continue benchmarking.
You can completely disable pre-compilation and baseline
profiles by passing compilation mode.none.
Partial uses the information from baseline profile
and pre-compiles those parts of your app.
This is similar to a regular app installation on newer devices.
The third option is to use compilation mode.full, which
pre-compiles the whole app into machine code.
Be aware that on Android 6 and 5,
the full compilation is the only option
because all apps are fully pre-compiled on those systems.
In the previous episode, we wrote a benchmark
that measures app startup.
Now, let's tweak this benchmark and add
the compilationMode parameter to compare the difference
when your app is not using the baseline profile
and when it is.
Now, we can run those tests, this time on a physical device
because we're running the benchmarks
and measuring performance.
Once we have the results, we can compare the performance gains
of using baseline profiles for each metric we have in our app.
We can see that results on the physical device we're
using clearly show much better startup performance
with the baseline profiles.
Similarly, we can tweak the feed scroll benchmark.
We'll add the compilationMode parameter
and write two different tests.
After the tests finish successfully,
we can see that the frames per second using baseline profiles
are better.
So users should see smoother scrolling experience.
By generating and applying baseline profile,
we moved work from the resource-limited Android device
onto a development machine.
This way, every user saves battery, computing cycles,
and gets a better experience.
And that's a wrap for improving performance
with baseline profiles.
I hope you'll see big improvements as we do.
If you want to try it at your own pace,
we have a Codelab you can follow.
Otherwise, check our documentation
for more information and check our GitHub samples--
a collection of all samples on performance.
If you like the video, hit the Subscribe button
to get more information in the future.
Also, let us know in the comments
how large a performance improvement you achieved.
In the next episode, Ben will go over
the third and final pillar, how to monitor the performance.
See you there.
[MUSIC PLAYING]