diff --git a/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/baselineprofile/BaselineProfileGenerator.kt b/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/baselineprofile/BaselineProfileGenerator.kt index 5abf7db4a..b544fbde1 100644 --- a/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/baselineprofile/BaselineProfileGenerator.kt +++ b/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/baselineprofile/BaselineProfileGenerator.kt @@ -16,7 +16,6 @@ package com.google.samples.apps.nowinandroid.baselineprofile -import androidx.benchmark.macro.ExperimentalBaselineProfilesApi import androidx.benchmark.macro.junit4.BaselineProfileRule import com.google.samples.apps.nowinandroid.PACKAGE_NAME import com.google.samples.apps.nowinandroid.bookmarks.goToBookmarksScreen @@ -31,13 +30,12 @@ import org.junit.Test /** * Generates a baseline profile which can be copied to `app/src/main/baseline-prof.txt`. */ -@ExperimentalBaselineProfilesApi class BaselineProfileGenerator { @get:Rule val baselineProfileRule = BaselineProfileRule() @Test fun generate() = - baselineProfileRule.collectBaselineProfile(PACKAGE_NAME) { + baselineProfileRule.collect(PACKAGE_NAME) { // 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. diff --git a/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/foryou/ForYouActions.kt b/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/foryou/ForYouActions.kt index 5e4952fbb..2b6b7794a 100644 --- a/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/foryou/ForYouActions.kt +++ b/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/foryou/ForYouActions.kt @@ -18,10 +18,14 @@ package com.google.samples.apps.nowinandroid.foryou import androidx.benchmark.macro.MacrobenchmarkScope import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until import androidx.test.uiautomator.untilHasChildren import com.google.samples.apps.nowinandroid.flingElementDownUp +val topicSelectionRes: BySelector = By.res("forYou:topicSelection") + fun MacrobenchmarkScope.forYouWaitForContent() { // Wait until content is loaded by checking if topics are loaded device.wait(Until.gone(By.res("loadingWheel")), 5_000) @@ -32,44 +36,63 @@ fun MacrobenchmarkScope.forYouWaitForContent() { obj.wait(untilHasChildren(), 60_000) } +fun MacrobenchmarkScope.forYouGetSelectedTopics(): List { + val topics = device.findObject(topicSelectionRes) ?: return emptyList() + return topics.findObjects(By.checked(true)) +} + +fun MacrobenchmarkScope.hasTopicSelected(): Boolean { + return forYouGetSelectedTopics().isNotEmpty() +} + +fun MacrobenchmarkScope.forYouClearSelectedTopics() { + forYouGetSelectedTopics().forEach { + it.click() + device.waitForIdle() + } +} + /** * Selects some topics, which will show the feed content for them. * [recheckTopicsIfChecked] Topics may be already checked from the previous iteration. */ -fun MacrobenchmarkScope.forYouSelectTopics(recheckTopicsIfChecked: Boolean = false) { - val topics = device.findObject(By.res("forYou:topicSelection")) +fun MacrobenchmarkScope.forYouSelectTopics( + recheckTopicsIfChecked: Boolean = false, + howManyToClick: Int = 3, + skip: Int = 0 +) { + val topics = device.findObject(topicSelectionRes) + val topicsItems = topics.findObjects(By.checkable(true)) // Set gesture margin from sides not to trigger system gesture navigation val horizontalMargin = 10 * topics.visibleBounds.width() / 100 topics.setGestureMargins(horizontalMargin, 0, horizontalMargin, 0) // Select some topics to show some feed content - var index = 0 + var index = skip var visited = 0 - while (visited < 3) { + while (visited < howManyToClick) { // Selecting some topics, which will populate items in the feed. - val topic = topics.children[index % topics.childCount] - // Find the checkable element to figure out whether it's checked or not - val topicCheckIcon = topic.findObject(By.checkable(true)) + val topicItem = topicsItems[index % topics.childCount] // Topic icon may not be visible if it's out of the screen boundaries // If that's the case, let's try another index - if (topicCheckIcon == null) { + if (topicItem == null) { index++ continue } when { // Topic wasn't checked, so just do that - !topicCheckIcon.isChecked -> { - topic.click() + !topicItem.isChecked -> { + topicItem.click() device.waitForIdle() } // Topic was checked already and we want to recheck it, so just do it twice recheckTopicsIfChecked -> { repeat(2) { - topic.click() + topicItem.click() device.waitForIdle() } } diff --git a/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/foryou/SelectTopicBenchmark.kt b/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/foryou/SelectTopicBenchmark.kt new file mode 100644 index 000000000..e9544a328 --- /dev/null +++ b/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/foryou/SelectTopicBenchmark.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.foryou + +import androidx.benchmark.macro.CompilationMode +import androidx.benchmark.macro.ExperimentalMetricApi +import androidx.benchmark.macro.FrameTimingMetric +import androidx.benchmark.macro.MemoryUsageMetric +import androidx.benchmark.macro.MemoryUsageMetric.Mode.Max +import androidx.benchmark.macro.TraceSectionMetric +import androidx.benchmark.macro.TraceSectionMetric.Mode.Sum +import androidx.benchmark.macro.junit4.MacrobenchmarkRule +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import com.google.samples.apps.nowinandroid.PACKAGE_NAME +import com.google.samples.apps.nowinandroid.allowNotifications +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +@OptIn(ExperimentalMetricApi::class) +class SelectTopicBenchmark { + @get:Rule + val benchmarkRule = MacrobenchmarkRule() + + @Test + fun benchmarkCompilationNone() = benchmark(CompilationMode.None()) + + @Test + fun benchmarkCompilationBaselineProfile() = benchmark(CompilationMode.Partial()) + + @Test + fun benchmarkCompilationFull() = benchmark(CompilationMode.Full()) + + private fun benchmark(compilationMode: CompilationMode) = benchmarkRule.measureRepeated( + packageName = PACKAGE_NAME, + metrics = listOf( + FrameTimingMetric(), + MemoryUsageMetric(Max), + TraceSectionMetric("SingleTopicButton", Sum), + ), + compilationMode = compilationMode, + iterations = 10, + setupBlock = { + // kill process to simulate COLD startup. [StartupMode.COLD] should be just used for startup benchmarks. + killProcess() + startActivityAndWait() + allowNotifications() + forYouWaitForContent() + // Clear any previously selected topics in setupBlock, so we have stable number of topics. + forYouClearSelectedTopics() + forYouSelectTopics(false, 1) + }, + ) { + // Select just one topic to see what recomposes and what's the impact + forYouSelectTopics(true, 1, 1) + } +} diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt index 5cdf2f593..8c510c017 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt @@ -33,7 +33,7 @@ internal fun Project.configureKotlinAndroid( commonExtension: CommonExtension<*, *, *, *>, ) { commonExtension.apply { - compileSdk = 33 + compileSdk = 34 defaultConfig { minSdk = 21 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fd37438cb..1816b3679 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ androidxDataStore = "1.0.0" androidxEspresso = "3.5.0" androidxHiltNavigationCompose = "1.0.0" androidxLifecycle = "2.6.0-alpha05" -androidxMacroBenchmark = "1.1.1" +androidxMacroBenchmark = "1.2.0-SNAPSHOT" androidxMetrics = "1.0.0-alpha03" androidxNavigation = "2.5.3" androidxProfileinstaller = "1.2.1" diff --git a/settings.gradle.kts b/settings.gradle.kts index d0c477b3d..199a41d5c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven { url = uri("https://androidx.dev/snapshots/latest/artifacts/repository") } } } rootProject.name = "nowinandroid"