Skip to content

Commit

Permalink
update: benchmark & baseline profile
Browse files Browse the repository at this point in the history
  • Loading branch information
muedsa committed Sep 9, 2024
1 parent abd9617 commit 321c33b
Show file tree
Hide file tree
Showing 12 changed files with 71,754 additions and 21 deletions.
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ plugins {
alias(libs.plugins.firebaseCrashlytics)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.androidRoom)
alias(libs.plugins.baselineProfile)
}

val keystorePropertiesFile: File = rootProject.file("keystore.properties")
Expand Down Expand Up @@ -109,6 +110,8 @@ dependencies {
implementation(libs.lifecycle.runtime.compose)
implementation(libs.lifecycle.viewmodel.compose)
implementation(libs.hilt.android)
implementation(libs.profile.installer)
"baselineProfile"(project(":benchmark"))
ksp(libs.hilt.compiler)
implementation(libs.activity.compose)
implementation(platform(libs.compose.bom))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
Expand Down Expand Up @@ -380,6 +381,7 @@ fun AnimeDetailScreen(
// 剧集列表
item {
EpisodeListWidget(
modifier = Modifier.testTag("animeDetailScreen_episodeListWidget"),
episodeList = selectedPlaySourceList,
danEpisodeList = danAnimeInfoLD.data?.episodes ?: emptyList(),
episodeProgressMap = watchedEpisodeTitleMap,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ fun FavoritesScreen(
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.width(30.dp))
OutlinedButton(onClick = {
deleteMode = !deleteMode
}) {
OutlinedButton(
modifier = Modifier.testTag("favoritesScreen_deleteModeButton"),
onClick = { deleteMode = !deleteMode }
) {
Text(if (deleteMode) "退出" else "删除模式")
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
Icon(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.tv.material3.ButtonDefaults
Expand Down Expand Up @@ -87,6 +87,7 @@ fun SearchScreen(
) {
OutlinedTextField(
modifier = Modifier
.testTag("searchScreen_searchButton")
.fillMaxWidth(0.55f)
.background(
color = MaterialTheme.colorScheme.inverseOnSurface,
Expand Down Expand Up @@ -119,28 +120,13 @@ fun SearchScreen(

if (searchAnimeLP.list.isNotEmpty()) {

val gridFocusRequester = remember { FocusRequester() }

LazyVerticalGrid(
columns = GridCells.Adaptive(AgePosterSize.width + ImageCardRowCardPadding),
contentPadding = PaddingValues(
top = ImageCardRowCardPadding,
bottom = ImageCardRowCardPadding
),
modifier = Modifier
.focusRequester(gridFocusRequester)
.focusProperties {
exit = { gridFocusRequester.saveFocusedChild(); FocusRequester.Default }
enter = {
if (gridFocusRequester.restoreFocusedChild()) {
LogUtil.d("grid restoreFocusedChild")
FocusRequester.Cancel
} else {
LogUtil.d("grid focused default child")
FocusRequester.Default
}
}
}
modifier = Modifier.testTag("searchScreen_grid")
) {
itemsIndexed(
items = searchAnimeLP.list,
Expand Down
35,723 changes: 35,723 additions & 0 deletions app/src/release/generated/baselineProfiles/baseline-prof.txt

Large diffs are not rendered by default.

35,723 changes: 35,723 additions & 0 deletions app/src/release/generated/baselineProfiles/startup-prof.txt

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions benchmark/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
52 changes: 52 additions & 0 deletions benchmark/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
plugins {
alias(libs.plugins.androidTest)
alias(libs.plugins.kotlinAndroid)
alias(libs.plugins.baselineProfile)
}

android {
namespace = "com.muedsa.agetv.benchmark"
compileSdk = 34

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}

defaultConfig {
minSdk = 28
targetSdk = 34

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

targetProjectPath = ":app"

}

// 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 {
useConnectedDevices = true
}

dependencies {
implementation(libs.junit)
implementation(libs.espresso.core)
implementation(libs.ui.automator)
implementation(libs.benchmark.macro.junit4)
}

androidComponents {
onVariants { v ->
val artifactsLoader = v.artifacts.getBuiltArtifactsLoader()
v.instrumentationRunnerArguments.put(
"targetAppId",
v.testedApks.map { artifactsLoader.load(it)?.applicationId }
)
}
}
1 change: 1 addition & 0 deletions benchmark/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest />
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package com.muedsa.agetv.benchmark

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 androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
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()

device.waitForIdle()
device.run {
// 等待首页加载
wait(
Until.findObject(By.res("mainScreen_row_1")),
INITIAL_WAIT_TIMEOUT
)
// 浏览首页
repeat(2 + 7) {
pressDPadDown(); waitForIdle(WAIT_TIMEOUT)
repeat(8) { pressDPadRight(); waitForIdle(WAIT_TIMEOUT) }
repeat(8) { pressDPadLeft(); waitForIdle(WAIT_TIMEOUT) }
}
repeat(2 + 7 + 1) { pressDPadUp(); waitForIdle(WAIT_TIMEOUT) }

// 导航到排行
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
pressDPadCenter(); waitForIdle(WAIT_TIMEOUT)
// 等待排行页面加载完成
wait(
Until.findObject(By.res("rankScreen_col_week")),
INITIAL_WAIT_TIMEOUT
)
// 浏览排行
repeat(3) { pressDPadDown(); waitForIdle(WAIT_TIMEOUT) }
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
repeat(2) { pressDPadDown(); waitForIdle(WAIT_TIMEOUT) }
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
repeat(2) { pressDPadDown(); waitForIdle(WAIT_TIMEOUT) }
repeat(3 + 2 + 2) { pressDPadUp(); waitForIdle(WAIT_TIMEOUT) }

// 导航到更新
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
pressDPadCenter(); waitForIdle(WAIT_TIMEOUT)
// 等待更新页面加载完成
wait(
Until.findObject(By.res("latestUpdateScreen_grid")),
INITIAL_WAIT_TIMEOUT
)
// 浏览更新
repeat(3) { pressDPadDown(); waitForIdle(WAIT_TIMEOUT) }
repeat(3) { pressDPadUp(); waitForIdle(WAIT_TIMEOUT) }

// 导航到推荐
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
pressDPadCenter(); waitForIdle(WAIT_TIMEOUT)
// 等待推荐页面加载完成
wait(
Until.findObject(By.res("recommendScreen_grid")),
INITIAL_WAIT_TIMEOUT
)
// 浏览推荐
repeat(3) { pressDPadDown(); waitForIdle(WAIT_TIMEOUT) }
repeat(3) { pressDPadUp(); waitForIdle(WAIT_TIMEOUT) }


// 导航到搜索
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
pressDPadCenter(); waitForIdle(WAIT_TIMEOUT)
wait(
Until.findObject(By.res("searchScreen_searchButton")),
INITIAL_WAIT_TIMEOUT
)

// 导航到收藏
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
pressDPadCenter(); waitForIdle(WAIT_TIMEOUT)
// 等待收藏页面加载完成
wait(
Until.findObject(By.res("favoritesScreen_deleteModeButton")),
INITIAL_WAIT_TIMEOUT
)
// 浏览收藏
pressDPadDown(); waitForIdle(WAIT_TIMEOUT)
pressDPadUp(); waitForIdle(WAIT_TIMEOUT)

// 导航到目录
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
pressDPadCenter(); waitForIdle(WAIT_TIMEOUT)
// 等待目录加载完成
wait(
Until.findObject(By.res("catalogScreen_grid")),
INITIAL_WAIT_TIMEOUT
)
// 浏览目录
pressDPadDown(); waitForIdle(WAIT_TIMEOUT)
pressDPadDown(); waitForIdle(WAIT_TIMEOUT)
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
pressDPadLeft(); waitForIdle(WAIT_TIMEOUT)

// 进入视频详情
pressDPadCenter(); waitForIdle(WAIT_TIMEOUT)
// 等待视频详情页面加载完成
wait(
Until.findObject(By.res("animeDetailScreen_episodeListWidget")),
INITIAL_WAIT_TIMEOUT
)
repeat(2) { pressDPadDown(); waitForIdle(WAIT_TIMEOUT) }
repeat(2) { pressDPadUp(); waitForIdle(WAIT_TIMEOUT) }
}
}
}
}

private const val INITIAL_WAIT_TIMEOUT = 5000L
private const val WAIT_TIMEOUT = 2000L
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.muedsa.agetv.benchmark

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 :benchmark: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
}
)
}
}
Loading

0 comments on commit 321c33b

Please sign in to comment.