Skip to content

Commit 403bbd0

Browse files
authored
Merge pull request #187 from aubynsamuel/dev
feat: Add Baseline Profile for Smoother UI, Faster Animations, and Op…
2 parents 4e951c8 + d1a96a7 commit 403bbd0

File tree

9 files changed

+207
-1
lines changed

9 files changed

+207
-1
lines changed

app/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ plugins {
33
alias(libs.plugins.jetbrains.kotlin.android)
44
alias(libs.plugins.jetbrains.compose.compiler)
55
alias(libs.plugins.jetbrains.kotlin.serialization)
6+
alias(libs.plugins.baselineprofile)
67
}
78

89
android {
@@ -85,12 +86,14 @@ dependencies {
8586
implementation(libs.retrofit2.kotlinx.serialization.converter)
8687
implementation(libs.retrofit2.retrofit)
8788
implementation(libs.latex2unicode.x.x3)
89+
implementation(libs.androidx.profileinstaller)
8890

8991
testImplementation(libs.junit)
9092
androidTestImplementation(libs.androidx.junit)
9193
androidTestImplementation(libs.androidx.espresso.core)
9294
androidTestImplementation(platform(libs.androidx.compose.bom))
9395
androidTestImplementation(libs.androidx.ui.test.junit4)
96+
"baselineProfile"(project(":baselineprofile"))
9497
debugImplementation(libs.androidx.ui.tooling)
9598
debugImplementation(libs.androidx.ui.test.manifest)
9699
}

baselineprofile/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

baselineprofile/build.gradle.kts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
plugins {
2+
alias(libs.plugins.android.test)
3+
alias(libs.plugins.jetbrains.kotlin.android)
4+
alias(libs.plugins.baselineprofile)
5+
}
6+
7+
android {
8+
namespace = "org.nsh07.baselineprofile"
9+
compileSdk = 35
10+
11+
compileOptions {
12+
sourceCompatibility = JavaVersion.VERSION_11
13+
targetCompatibility = JavaVersion.VERSION_11
14+
}
15+
16+
kotlinOptions {
17+
jvmTarget = "11"
18+
}
19+
20+
defaultConfig {
21+
minSdk = 28
22+
targetSdk = 35
23+
24+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
25+
}
26+
27+
targetProjectPath = ":app"
28+
29+
}
30+
31+
// This is the configuration block for the Baseline Profile plugin.
32+
// You can specify to run the generators on a managed devices or connected devices.
33+
baselineProfile {
34+
useConnectedDevices = true
35+
}
36+
37+
dependencies {
38+
implementation(libs.androidx.junit)
39+
implementation(libs.androidx.espresso.core)
40+
implementation(libs.androidx.uiautomator)
41+
implementation(libs.androidx.benchmark.macro.junit4)
42+
}
43+
44+
androidComponents {
45+
onVariants { v ->
46+
val artifactsLoader = v.artifacts.getBuiltArtifactsLoader()
47+
v.instrumentationRunnerArguments.put(
48+
"targetAppId",
49+
v.testedApks.map { artifactsLoader.load(it)?.applicationId.orEmpty() }
50+
)
51+
}
52+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<manifest />
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.nsh07.baselineprofile
2+
3+
import androidx.benchmark.macro.junit4.BaselineProfileRule
4+
import androidx.test.ext.junit.runners.AndroidJUnit4
5+
import androidx.test.filters.LargeTest
6+
import androidx.test.platform.app.InstrumentationRegistry
7+
import org.junit.Rule
8+
import org.junit.Test
9+
import org.junit.runner.RunWith
10+
11+
/**
12+
* This test class generates a basic startup baseline profile for the target package.
13+
*
14+
* We recommend you start with this but add important user flows to the profile to improve their performance.
15+
* Refer to the [baseline profile documentation](https://d.android.com/topic/performance/baselineprofiles)
16+
* for more information.
17+
*
18+
* You can run the generator with the "Generate Baseline Profile" run configuration in Android Studio or
19+
* the equivalent `generateBaselineProfile` gradle task:
20+
* ```
21+
* ./gradlew :app:generateReleaseBaselineProfile
22+
* ```
23+
* The run configuration runs the Gradle task and applies filtering to run only the generators.
24+
*
25+
* Check [documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args)
26+
* for more information about available instrumentation arguments.
27+
*
28+
* After you run the generator, you can verify the improvements running the [StartupBenchmarks] benchmark.
29+
*
30+
* When using this class to generate a baseline profile, only API 33+ or rooted API 28+ are supported.
31+
*
32+
* The minimum required version of androidx.benchmark to generate a baseline profile is 1.2.0.
33+
**/
34+
@RunWith(AndroidJUnit4::class)
35+
@LargeTest
36+
class BaselineProfileGenerator {
37+
38+
@get:Rule
39+
val rule = BaselineProfileRule()
40+
41+
@Test
42+
fun generate() {
43+
// The application id for the running build variant is read from the instrumentation arguments.
44+
rule.collect(
45+
packageName = InstrumentationRegistry.getArguments().getString("targetAppId")
46+
?: throw Exception("targetAppId not passed as instrumentation runner arg"),
47+
48+
// See: https://d.android.com/topic/performance/baselineprofiles/dex-layout-optimizations
49+
includeInStartupProfile = true
50+
) {
51+
// This block defines the app's critical user journey. Here we are interested in
52+
// optimizing for app startup. But you can also navigate and scroll through your most important UI.
53+
54+
// Start default activity for your app
55+
pressHome()
56+
startActivityAndWait()
57+
58+
Thread.sleep(5000)
59+
}
60+
}
61+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package org.nsh07.baselineprofile
2+
3+
import androidx.benchmark.macro.BaselineProfileMode
4+
import androidx.benchmark.macro.CompilationMode
5+
import androidx.benchmark.macro.StartupMode
6+
import androidx.benchmark.macro.StartupTimingMetric
7+
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
8+
import androidx.test.ext.junit.runners.AndroidJUnit4
9+
import androidx.test.filters.LargeTest
10+
import androidx.test.platform.app.InstrumentationRegistry
11+
import org.junit.Rule
12+
import org.junit.Test
13+
import org.junit.runner.RunWith
14+
15+
/**
16+
* This test class benchmarks the speed of app startup.
17+
* Run this benchmark to verify how effective a Baseline Profile is.
18+
* It does this by comparing [CompilationMode.None], which represents the app with no Baseline
19+
* Profiles optimizations, and [CompilationMode.Partial], which uses Baseline Profiles.
20+
*
21+
* Run this benchmark to see startup measurements and captured system traces for verifying
22+
* the effectiveness of your Baseline Profiles. You can run it directly from Android
23+
* Studio as an instrumentation test, or run all benchmarks for a variant, for example benchmarkRelease,
24+
* with this Gradle task:
25+
* ```
26+
* ./gradlew :baselineprofile:connectedBenchmarkReleaseAndroidTest
27+
* ```
28+
*
29+
* You should run the benchmarks on a physical device, not an Android emulator, because the
30+
* emulator doesn't represent real world performance and shares system resources with its host.
31+
*
32+
* For more information, see the [Macrobenchmark documentation](https://d.android.com/macrobenchmark#create-macrobenchmark)
33+
* and the [instrumentation arguments documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args).
34+
**/
35+
@RunWith(AndroidJUnit4::class)
36+
@LargeTest
37+
class StartupBenchmarks {
38+
39+
@get:Rule
40+
val rule = MacrobenchmarkRule()
41+
42+
@Test
43+
fun startupCompilationNone() =
44+
benchmark(CompilationMode.None())
45+
46+
@Test
47+
fun startupCompilationBaselineProfiles() =
48+
benchmark(CompilationMode.Partial(BaselineProfileMode.Require))
49+
50+
private fun benchmark(compilationMode: CompilationMode) {
51+
// The application id for the running build variant is read from the instrumentation arguments.
52+
rule.measureRepeated(
53+
packageName = InstrumentationRegistry.getArguments().getString("targetAppId")
54+
?: throw Exception("targetAppId not passed as instrumentation runner arg"),
55+
metrics = listOf(StartupTimingMetric()),
56+
compilationMode = compilationMode,
57+
startupMode = StartupMode.COLD,
58+
iterations = 10,
59+
setupBlock = {
60+
pressHome()
61+
},
62+
measureBlock = {
63+
startActivityAndWait()
64+
65+
// TODO Add interactions to wait for when your app is fully drawn.
66+
// The app is fully drawn when Activity.reportFullyDrawn is called.
67+
// For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
68+
// from the AndroidX Activity library.
69+
70+
// Check the UiAutomator documentation for more information on how to
71+
// interact with the app.
72+
// https://d.android.com/training/testing/other-components/ui-automator
73+
}
74+
)
75+
}
76+
}

build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ plugins {
44
alias(libs.plugins.jetbrains.compose.compiler) apply false
55
alias(libs.plugins.jetbrains.kotlin.android) apply false
66
alias(libs.plugins.jetbrains.kotlin.serialization) apply false
7+
alias(libs.plugins.android.test) apply false
8+
alias(libs.plugins.baselineprofile) apply false
79
}

gradle/libs.versions.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[versions]
22
activityCompose = "1.10.1"
33
adaptive = "1.1.0"
4-
agp = "8.10.0"
4+
agp = "8.10.1"
55
coil = "3.2.0"
66
coilGif = "3.2.0"
77
composeBom = "2025.05.01"
@@ -23,6 +23,10 @@ navigationCompose = "2.9.0"
2323
okhttp = "4.12.0"
2424
retrofit = "3.0.0"
2525
retrofit2KotlinxSerializationConverter = "1.0.0"
26+
uiautomator = "2.3.0"
27+
benchmarkMacroJunit4 = "1.3.4"
28+
baselineprofile = "1.3.4"
29+
profileinstaller = "1.4.1"
2630

2731
[libraries]
2832
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
@@ -58,9 +62,14 @@ okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
5862
retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" }
5963
retrofit2-retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit"}
6064
retrofit2-converter-scalars = { module = "com.squareup.retrofit2:converter-scalars", version.ref = "retrofit" }
65+
androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
66+
androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmarkMacroJunit4" }
67+
androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "profileinstaller" }
6168

6269
[plugins]
6370
android-application = { id = "com.android.application", version.ref = "agp" }
6471
jetbrains-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
6572
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
6673
jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
74+
android-test = { id = "com.android.test", version.ref = "agp" }
75+
baselineprofile = { id = "androidx.baselineprofile", version.ref = "baselineprofile" }

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ dependencyResolutionManagement {
2121

2222
rootProject.name = "WikiReader"
2323
include(":app")
24+
include(":baselineprofile")

0 commit comments

Comments
 (0)