From accd3afbbbe17f1dea14bea9751640b0ee45749d Mon Sep 17 00:00:00 2001 From: HapeLee <63206378+HapeLee@users.noreply.github.com> Date: Wed, 13 May 2026 18:14:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20Baseline=20Profile=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=86=B7=E5=90=AF=E5=8A=A8=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 3 + baselineProfile/.gitignore | 1 + baselineProfile/build.gradle.kts | 69 +++++++++++++++++ baselineProfile/src/main/AndroidManifest.xml | 1 + .../BaselineProfileGenerator.kt | 68 +++++++++++++++++ .../baselineprofile/StartupBenchmarks.kt | 76 +++++++++++++++++++ 6 files changed, 218 insertions(+) create mode 100644 baselineProfile/.gitignore create mode 100644 baselineProfile/build.gradle.kts create mode 100644 baselineProfile/src/main/AndroidManifest.xml create mode 100644 baselineProfile/src/main/java/io/legado/baselineprofile/BaselineProfileGenerator.kt create mode 100644 baselineProfile/src/main/java/io/legado/baselineprofile/StartupBenchmarks.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 694cbaf48..22e174e5a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,6 +9,7 @@ plugins { alias(libs.plugins.room) alias(libs.plugins.ksp) alias(libs.plugins.google.services) + alias(libs.plugins.baselineprofile) } apply(from = "download.gradle") @@ -168,6 +169,8 @@ ksp { } dependencies { + implementation(libs.androidx.profileinstaller) + "baselineProfile"(project(":baselineprofile")) coreLibraryDesugaring(libs.desugar) testImplementation(libs.junit) androidTestImplementation(libs.bundles.androidTest) diff --git a/baselineProfile/.gitignore b/baselineProfile/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /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 000000000..119b81186 --- /dev/null +++ b/baselineProfile/build.gradle.kts @@ -0,0 +1,69 @@ +import com.android.build.api.dsl.ManagedVirtualDevice + +plugins { + alias(libs.plugins.android.test) + alias(libs.plugins.baselineprofile) +} + +android { + namespace = "io.legado.baselineprofile" + compileSdk { + version = release(36) { + minorApiLevel = 1 + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + defaultConfig { + minSdk = 28 + targetSdk = 36 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + targetProjectPath = ":app" + + flavorDimensions += listOf("mode") + productFlavors { + create("app") { dimension = "mode" } + } + + // This code creates the gradle managed device used to generate baseline profiles. + // To use GMD please invoke generation through the command line: + // ./gradlew :app:generateBaselineProfile + testOptions.managedDevices.allDevices { + 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(libs.androidx.benchmark.macro.junit4) + implementation(libs.androidx.espresso.core) + implementation(libs.androidx.junit) + implementation(libs.androidx.uiautomator) +} + +androidComponents { + onVariants { v -> + val artifactsLoader = v.artifacts.getBuiltArtifactsLoader() + v.instrumentationRunnerArguments.put( + "targetAppId", + v.testedApks.map { artifactsLoader.load(it)?.applicationId } + ) + } +} \ No newline at end of file diff --git a/baselineProfile/src/main/AndroidManifest.xml b/baselineProfile/src/main/AndroidManifest.xml new file mode 100644 index 000000000..227314eeb --- /dev/null +++ b/baselineProfile/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/baselineProfile/src/main/java/io/legado/baselineprofile/BaselineProfileGenerator.kt b/baselineProfile/src/main/java/io/legado/baselineprofile/BaselineProfileGenerator.kt new file mode 100644 index 000000000..1d79bb340 --- /dev/null +++ b/baselineProfile/src/main/java/io/legado/baselineprofile/BaselineProfileGenerator.kt @@ -0,0 +1,68 @@ +package io.legado.baselineprofile + +import androidx.benchmark.macro.junit4.BaselineProfileRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.platform.app.InstrumentationRegistry +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 in Android Studio or + * the equivalent `generateBaselineProfile` gradle task: + * ``` + * ./gradlew :app:generateReleaseBaselineProfile + * ``` + * 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. + * + * When using this class to generate a baseline profile, only API 33+ or rooted API 28+ are supported. + * + * The minimum required version of androidx.benchmark to generate a baseline profile is 1.2.0. + **/ +@RunWith(AndroidJUnit4::class) +@LargeTest +class BaselineProfileGenerator { + + @get:Rule + val rule = BaselineProfileRule() + + @Test + fun generate() { + // The application id for the running build variant is read from the instrumentation arguments. + rule.collect( + packageName = InstrumentationRegistry.getArguments().getString("targetAppId") + ?: throw Exception("targetAppId not passed as instrumentation runner arg"), + + // See: https://d.android.com/topic/performance/baselineprofiles/dex-layout-optimizations + includeInStartupProfile = true + ) { + // 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/io/legado/baselineprofile/StartupBenchmarks.kt b/baselineProfile/src/main/java/io/legado/baselineprofile/StartupBenchmarks.kt new file mode 100644 index 000000000..51b8c0e62 --- /dev/null +++ b/baselineProfile/src/main/java/io/legado/baselineprofile/StartupBenchmarks.kt @@ -0,0 +1,76 @@ +package io.legado.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 androidx.test.platform.app.InstrumentationRegistry +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 for a variant, for example benchmarkRelease, + * with this Gradle task: + * ``` + * ./gradlew :baselineprofile:connectedBenchmarkReleaseAndroidTest + * ``` + * + * 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) { + // The application id for the running build variant is read from the instrumentation arguments. + rule.measureRepeated( + packageName = InstrumentationRegistry.getArguments().getString("targetAppId") + ?: throw Exception("targetAppId not passed as instrumentation runner arg"), + 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