diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 46f44feb..3e19cd9e 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -3,6 +3,7 @@ + diff --git a/.idea/gradle.xml b/.idea/gradle.xml index f1af9d7f..f64c2ef8 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -21,6 +21,7 @@ diff --git a/.idea/runConfigurations/Generate_Baseline_Profile.xml b/.idea/runConfigurations/Generate_Baseline_Profile.xml new file mode 100644 index 00000000..18b10cd1 --- /dev/null +++ b/.idea/runConfigurations/Generate_Baseline_Profile.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3d09539b..925a0efe 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,6 +8,7 @@ plugins { id("io.gitlab.arturbosch.detekt") id("dagger.hilt.android.plugin") id("de.mannodermaus.android-junit5") + id("androidx.baselineprofile") } android { @@ -89,8 +90,10 @@ dependencies { implementation(compose.preview) implementation(compose.windowsize) implementation(accompanist.systemuicontroller) + implementation("androidx.profileinstaller:profileinstaller:1.3.0") androidTestImplementation(compose.testing) androidTestImplementation(platform(compose.bom)) + "baselineProfile"(project(":baselineprofile")) debugImplementation(compose.testing.manifest) debugImplementation(compose.tooling) implementation(androidx.paging.compose) diff --git a/baselineprofile/.gitignore b/baselineprofile/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/baselineprofile/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/baselineprofile/build.gradle.kts b/baselineprofile/build.gradle.kts new file mode 100644 index 00000000..43e51c29 --- /dev/null +++ b/baselineprofile/build.gradle.kts @@ -0,0 +1,52 @@ +import com.android.build.api.dsl.ManagedVirtualDevice + +plugins { + id("com.android.test") + id("org.jetbrains.kotlin.android") + id("androidx.baselineprofile") +} + +android { + namespace = "dev.fobo66.baselineprofile" + compileSdk = 34 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } + + defaultConfig { + minSdk = 28 + targetSdk = 34 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + targetProjectPath = ":app" + + testOptions.managedDevices.devices { + create("pixel6Api34") { + device = "Pixel 6" + apiLevel = 34 + systemImageSource = "google" + } + } +} + +// This is the configuration block for the Baseline Profile plugin. +// You can specify to run the generators on a managed devices or connected devices. +baselineProfile { + managedDevices += "pixel6Api34" + useConnectedDevices = false +} + +dependencies { + implementation("androidx.test.ext:junit:1.1.5") + implementation("androidx.test.espresso:espresso-core:3.5.1") + implementation("androidx.test.uiautomator:uiautomator:2.2.0") + implementation("androidx.benchmark:benchmark-macro-junit4:1.2.0-beta04") +} diff --git a/baselineprofile/src/main/AndroidManifest.xml b/baselineprofile/src/main/AndroidManifest.xml new file mode 100644 index 00000000..227314ee --- /dev/null +++ b/baselineprofile/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/baselineprofile/src/main/java/dev/fobo66/baselineprofile/BaselineProfileGenerator.kt b/baselineprofile/src/main/java/dev/fobo66/baselineprofile/BaselineProfileGenerator.kt new file mode 100644 index 00000000..56d5aead --- /dev/null +++ b/baselineprofile/src/main/java/dev/fobo66/baselineprofile/BaselineProfileGenerator.kt @@ -0,0 +1,57 @@ +package dev.fobo66.baselineprofile + +import androidx.benchmark.macro.junit4.BaselineProfileRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * This test class generates a basic startup baseline profile for the target package. + * + * We recommend you start with this but add important user flows to the profile to improve their performance. + * Refer to the [baseline profile documentation](https://d.android.com/topic/performance/baselineprofiles) + * for more information. + * + * You can run the generator with the Generate Baseline Profile run configuration, + * or directly with `generateBaselineProfile` Gradle task: + * ``` + * ./gradlew :Fact Checker Assistant.Fact_Checker_Assistant.app:generateReleaseBaselineProfile -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile + * ``` + * The run configuration runs the Gradle task and applies filtering to run only the generators. + * + * Check [documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args) + * for more information about available instrumentation arguments. + * + * After you run the generator, you can verify the improvements running the [StartupBenchmarks] benchmark. + **/ +@RunWith(AndroidJUnit4::class) +@LargeTest +class BaselineProfileGenerator { + + @get:Rule + val rule = BaselineProfileRule() + + @Test + fun generate() { + rule.collect("dev.fobo66.factcheckerassistant") { + // This block defines the app's critical user journey. Here we are interested in + // optimizing for app startup. But you can also navigate and scroll + // through your most important UI. + + // Start default activity for your app + pressHome() + startActivityAndWait() + + // TODO Write more interactions to optimize advanced journeys of your app. + // For example: + // 1. Wait until the content is asynchronously loaded + // 2. Scroll the feed content + // 3. Navigate to detail screen + + // Check UiAutomator documentation for more information how to interact with the app. + // https://d.android.com/training/testing/other-components/ui-automator + } + } +} \ No newline at end of file diff --git a/baselineprofile/src/main/java/dev/fobo66/baselineprofile/StartupBenchmarks.kt b/baselineprofile/src/main/java/dev/fobo66/baselineprofile/StartupBenchmarks.kt new file mode 100644 index 00000000..c1f9a652 --- /dev/null +++ b/baselineprofile/src/main/java/dev/fobo66/baselineprofile/StartupBenchmarks.kt @@ -0,0 +1,72 @@ +package dev.fobo66.baselineprofile + +import androidx.benchmark.macro.BaselineProfileMode +import androidx.benchmark.macro.CompilationMode +import androidx.benchmark.macro.StartupMode +import androidx.benchmark.macro.StartupTimingMetric +import androidx.benchmark.macro.junit4.MacrobenchmarkRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * This test class benchmarks the speed of app startup. + * Run this benchmark to verify how effective a Baseline Profile is. + * It does this by comparing [CompilationMode.None], which represents the app with no Baseline + * Profiles optimizations, and [CompilationMode.Partial], which uses Baseline Profiles. + * + * Run this benchmark to see startup measurements and captured system traces for verifying + * the effectiveness of your Baseline Profiles. You can run it directly from Android + * Studio as an instrumentation test, or run all benchmarks with this Gradle task: + * ``` + * ./gradlew :baselineprofile:connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=Macrobenchmark + * ``` + * + * You should run the benchmarks on a physical device, not an Android emulator, because the + * emulator doesn't represent real world performance and shares system resources with its host. + * + * For more information, see the [Macrobenchmark documentation](https://d.android.com/macrobenchmark#create-macrobenchmark) + * and the [instrumentation arguments documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args). + **/ +@RunWith(AndroidJUnit4::class) +@LargeTest +class StartupBenchmarks { + + @get:Rule + val rule = MacrobenchmarkRule() + + @Test + fun startupCompilationNone() = + benchmark(CompilationMode.None()) + + @Test + fun startupCompilationBaselineProfiles() = + benchmark(CompilationMode.Partial(BaselineProfileMode.Require)) + + private fun benchmark(compilationMode: CompilationMode) { + rule.measureRepeated( + packageName = "dev.fobo66.factcheckerassistant", + metrics = listOf(StartupTimingMetric()), + compilationMode = compilationMode, + startupMode = StartupMode.COLD, + iterations = 10, + setupBlock = { + pressHome() + }, + measureBlock = { + startActivityAndWait() + + // TODO Add interactions to wait for when your app is fully drawn. + // The app is fully drawn when Activity.reportFullyDrawn is called. + // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter + // from the AndroidX Activity library. + + // Check the UiAutomator documentation for more information on how to + // interact with the app. + // https://d.android.com/training/testing/other-components/ui-automator + } + ) + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index f61d48cb..89c0febd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,11 +1,13 @@ plugins { alias(androidx.plugins.application) apply false alias(androidx.plugins.library) apply false + alias(androidx.plugins.test) apply false kotlin("android") version libs.versions.kotlin apply false kotlin("kapt") version libs.versions.kotlin apply false alias(di.plugins.hilt) apply false alias(analysis.plugins.detekt) apply false alias(testing.plugins.junit) apply false + alias(androidx.plugins.baseline.profile) apply false } tasks { diff --git a/gradle.properties b/gradle.properties index 78273444..c65a9074 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx16g -XX:+UseParallelGC -XX:CompileCommand=exclude,com/android/tools/r8/internal/fb,a +org.gradle.jvmargs=-Xmx16g -XX:CompileCommand=exclude,com/android/tools/r8/internal/fb,a android.useAndroidX=true android.enableJetifier=false android.disableResourceValidation=true @@ -9,5 +9,5 @@ org.gradle.parallel=true org.gradle.caching=true org.gradle.warning.mode=all kotlin.incremental.useClasspathSnapshot=true -org.gradle.configuration-cache=true +org.gradle.configuration-cache=false android.nonFinalResIds=true diff --git a/settings.gradle.kts b/settings.gradle.kts index 211a601e..74cb351e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -71,6 +71,8 @@ dependencyResolutionManagement { version("benchmark", "1.2.0-alpha15") plugin("application", "com.android.application").versionRef("plugin") plugin("library", "com.android.library").versionRef("plugin") + plugin("test", "com.android.test").versionRef("plugin") + plugin("baseline-profile", "androidx.baselineprofile").version("1.2.0-beta04") library("core", "androidx.core:core-ktx:1.12.0-rc01") library("annotations", "androidx.annotation:annotation:1.7.0-beta01") library("activity", "androidx.activity:activity-compose:1.8.0-alpha06") @@ -221,3 +223,4 @@ dependencyResolutionManagement { rootProject.name = "Fact Checker Assistant" include(":app", ":composemd") +include(":baselineprofile")