diff --git a/.github/workflows/android.yml b/.github/workflows/android_snapshot.yml
similarity index 69%
rename from .github/workflows/android.yml
rename to .github/workflows/android_snapshot.yml
index 01bb16313..bdd6e886c 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android_snapshot.yml
@@ -1,4 +1,4 @@
-name: Android CI
+name: Android SNAPSHOT CI
on:
push:
@@ -11,7 +11,7 @@ on:
- 'fastlane/**'
- '!.github/workflows/**'
pull_request:
- branches: [ "master", "ci-1.14.0-rc04-benchmark" ]
+ branches: [ "master" ]
workflow_dispatch:
jobs:
@@ -32,10 +32,7 @@ jobs:
run: chmod +x gradlew
- name: Generate baseline profile
- run: ./gradlew :benchmark:Pixel5Api31StableChannelRichCodecBenchmarkAndroidTest --rerun-tasks -P android.testInstrumentationRunnerArguments.class=com.m3u.benchmark.BaselineProfileGenerator
-
- - name: Copy and Rename baseline profile
- run: cp benchmark/build/outputs/managed_device_android_test_additional_output/benchmark/flavours/stableChannelRichCodec/Pixel5Api31/BaselineProfileGenerator_generateBaselineProfile-baseline-prof.txt androidApp/src/main/baseline-prof.txt
+ run: ./gradlew :androidApp:generateSnapshotChannelRichCodecReleaseBaselineProfile
- name: Build with Gradle
run: ./gradlew :androidApp:assembleSnapshotChannelRichCodecRelease
diff --git a/.idea/runConfigurations/M3UAndroid___benchmark_Pixel5Api31BenchmarkAndroidTest_.xml b/.idea/runConfigurations/M3UAndroid___benchmark_Pixel5Api31BenchmarkAndroidTest_.xml
deleted file mode 100644
index eaa7eb42c..000000000
--- a/.idea/runConfigurations/M3UAndroid___benchmark_Pixel5Api31BenchmarkAndroidTest_.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- true
- true
- false
- false
-
-
-
\ No newline at end of file
diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts
index 4c3619b62..6bf20edd1 100644
--- a/androidApp/build.gradle.kts
+++ b/androidApp/build.gradle.kts
@@ -21,7 +21,6 @@ android {
versionName = "1.14.0-rc03"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- testInstrumentationRunnerArguments["androidx.benchmark.profiling.mode"] = "MethodTracing"
}
flavorDimensions += setOf("channel", "codec")
productFlavors {
@@ -131,6 +130,9 @@ dependencies {
implementation(project(":feature:playlist-configuration"))
implementation(project(":feature:crash"))
+ implementation(libs.androidx.profileinstaller)
+ baselineProfile(project(":baselineprofile"))
+
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.activity.compose)
@@ -143,6 +145,7 @@ dependencies {
implementation(libs.androidx.core.splashscreen)
implementation(libs.google.dagger.hilt)
+ baselineProfile(project(":baselineprofile"))
ksp(libs.google.dagger.hilt.compiler)
implementation(libs.androidx.hilt.navigation.compose)
diff --git a/annotation/build.gradle.kts b/annotation/build.gradle.kts
index 5bd54b44a..4d31c0bb5 100644
--- a/annotation/build.gradle.kts
+++ b/annotation/build.gradle.kts
@@ -1,5 +1,18 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
plugins {
- kotlin("jvm")
+ alias(libs.plugins.org.jetbrains.kotlin.jvm)
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+}
+
+kotlin {
+ compilerOptions {
+ jvmTarget = JvmTarget.JVM_17
+ }
}
dependencies {
diff --git a/benchmark/.gitignore b/baselineprofile/.gitignore
similarity index 100%
rename from benchmark/.gitignore
rename to baselineprofile/.gitignore
diff --git a/baselineprofile/build.gradle.kts b/baselineprofile/build.gradle.kts
new file mode 100644
index 000000000..9661d0829
--- /dev/null
+++ b/baselineprofile/build.gradle.kts
@@ -0,0 +1,61 @@
+@file:Suppress("UnstableApiUsage")
+
+import com.android.build.api.dsl.ManagedVirtualDevice
+
+plugins {
+ alias(libs.plugins.com.android.test)
+ alias(libs.plugins.org.jetbrains.kotlin.android)
+ alias(libs.plugins.androidx.baselineprofile)
+}
+
+android {
+ namespace = "com.m3u.baselineprofile"
+ compileSdk = 34
+ defaultConfig {
+ minSdk = 28
+ targetSdk = 34
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+ kotlinOptions {
+ jvmTarget = "17"
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ targetProjectPath = ":androidApp"
+
+ flavorDimensions += listOf("channel", "codec")
+ productFlavors {
+ create("stableChannel") { dimension = "channel" }
+ create("snapshotChannel") { dimension = "channel" }
+ create("richCodec") { dimension = "codec" }
+ create("liteCodec") { dimension = "codec" }
+ }
+
+ // This code creates the gradle managed device used to generate baseline profiles.
+ // To use GMD please invoke generation through the command line:
+ // ./gradlew :androidApp:generateBaselineProfile
+ testOptions.managedDevices.devices {
+ create("pixel6Api34") {
+ device = "Pixel 6"
+ apiLevel = 34
+ systemImageSource = "aosp"
+ }
+ }
+}
+
+// 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.test.ext.junit)
+ implementation(libs.androidx.test.espresso.espresso.core)
+ implementation(libs.androidx.test.uiautomator.uiautomator)
+ implementation(libs.androidx.benchmark.benchmark.macro.junit4)
+}
diff --git a/benchmark/src/main/AndroidManifest.xml b/baselineprofile/src/main/AndroidManifest.xml
similarity index 100%
rename from benchmark/src/main/AndroidManifest.xml
rename to baselineprofile/src/main/AndroidManifest.xml
diff --git a/baselineprofile/src/main/java/com/m3u/baselineprofile/BaselineProfileGenerator.kt b/baselineprofile/src/main/java/com/m3u/baselineprofile/BaselineProfileGenerator.kt
new file mode 100644
index 000000000..19cc3029d
--- /dev/null
+++ b/baselineprofile/src/main/java/com/m3u/baselineprofile/BaselineProfileGenerator.kt
@@ -0,0 +1,67 @@
+package com.m3u.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 in Android Studio or
+ * the equivalent `generateBaselineProfile` gradle task:
+ * ```
+ * ./gradlew :androidApp: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() {
+ // This example works only with the variant with application id `com.m3u.androidApp.snapshot`."
+ rule.collect(
+ packageName = "com.m3u.androidApp",
+
+ // 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/com/m3u/baselineprofile/StartupBenchmarks.kt b/baselineprofile/src/main/java/com/m3u/baselineprofile/StartupBenchmarks.kt
new file mode 100644
index 000000000..6c09aa317
--- /dev/null
+++ b/baselineprofile/src/main/java/com/m3u/baselineprofile/StartupBenchmarks.kt
@@ -0,0 +1,75 @@
+package com.m3u.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 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) {
+ // This example works only with the variant with application id `com.m3u.androidApp.snapshot`."
+ rule.measureRepeated(
+ packageName = "com.m3u.androidApp.snapshot",
+ 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/benchmark/build.gradle.kts b/benchmark/build.gradle.kts
deleted file mode 100644
index 508d4c087..000000000
--- a/benchmark/build.gradle.kts
+++ /dev/null
@@ -1,87 +0,0 @@
-@file:Suppress("UnstableApiUsage")
-
-import com.android.build.api.dsl.ManagedVirtualDevice
-
-plugins {
- alias(libs.plugins.com.android.test)
- alias(libs.plugins.org.jetbrains.kotlin.android)
-}
-
-android {
- namespace = "com.m3u.benchmark"
- compileSdk = 34
-
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
- }
-
- kotlinOptions {
- jvmTarget = "17"
- }
-
- defaultConfig {
- minSdk = 26
- targetSdk = 33
-
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- }
-
- flavorDimensions += setOf("channel", "codec")
- productFlavors {
- create("stableChannel") {
- dimension = "channel"
- }
- create("snapshotChannel") {
- dimension = "channel"
- }
- create("richCodec") {
- dimension = "codec"
- }
- create("liteCodec") {
- dimension = "codec"
- }
- }
-
- testOptions {
- managedDevices {
- allDevices {
- // Add Gradle task
- // :benchmark:Pixel5Api31BenchmarkAndroidTest --rerun-tasks -P
- // android.testInstrumentationRunnerArguments.class=com.m3u.benchmark.BaselineProfileGenerator
- create("Pixel5Api31", ManagedVirtualDevice::class) {
- device = "Pixel 5"
- apiLevel = 31
- systemImageSource = "aosp"
- }
- }
- }
- }
-
- buildTypes {
- // This benchmark buildType is used for benchmarking, and should function like your
- // release build (for example, with minification on). It"s signed with a debug key
- // for easy local/CI testing.
- create("benchmark") {
- isDebuggable = true
- signingConfig = getByName("debug").signingConfig
- matchingFallbacks += listOf("release")
- }
- }
-
- targetProjectPath = ":androidApp"
- experimentalProperties["android.experimental.self-instrumenting"] = true
-}
-
-dependencies {
- implementation(libs.androidx.test.ext.junit)
- implementation(libs.androidx.test.espresso.espresso.core)
- implementation(libs.androidx.test.uiautomator.uiautomator)
- implementation(libs.androidx.benchmark.benchmark.macro.junit4)
-}
-
-androidComponents {
- beforeVariants(selector().all()) {
- it.enable = it.buildType == "benchmark"
- }
-}
\ No newline at end of file
diff --git a/benchmark/src/main/java/com/m3u/benchmark/BaselineProfileGenerator.kt b/benchmark/src/main/java/com/m3u/benchmark/BaselineProfileGenerator.kt
deleted file mode 100644
index b90e65460..000000000
--- a/benchmark/src/main/java/com/m3u/benchmark/BaselineProfileGenerator.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.m3u.benchmark
-
-import androidx.annotation.RequiresApi
-import androidx.benchmark.macro.junit4.BaselineProfileRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-@RequiresApi(28)
-class BaselineProfileGenerator {
- @get:Rule
- val baselineRule = BaselineProfileRule()
-
- @Test
- fun generateBaselineProfile() = baselineRule.collect(
- packageName = "com.m3u.androidApp"
- ) {
- pressHome()
- startActivityAndWait()
- }
-}
\ No newline at end of file
diff --git a/benchmark/src/main/java/com/m3u/benchmark/ExampleStartupBenchmark.kt b/benchmark/src/main/java/com/m3u/benchmark/ExampleStartupBenchmark.kt
deleted file mode 100644
index b492b51f9..000000000
--- a/benchmark/src/main/java/com/m3u/benchmark/ExampleStartupBenchmark.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.m3u.benchmark
-
-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 org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class ExampleStartupBenchmark {
- @get:Rule
- val benchmarkRule = MacrobenchmarkRule()
-
- @Test
- fun startUpWithBaseline() = startUp(CompilationMode.Partial())
-
- @Test
- fun startUpWithoutBaseline() = startUp(CompilationMode.None())
-
- private fun startUp(mode: CompilationMode) = benchmarkRule.measureRepeated(
- packageName = "com.m3u.androidApp",
- metrics = listOf(StartupTimingMetric()),
- iterations = 3,
- startupMode = StartupMode.COLD,
- compilationMode = mode
- ) {
- pressHome()
- startActivityAndWait()
- }
-}
diff --git a/build.gradle.kts b/build.gradle.kts
index 977b9ee7c..e80827a66 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -13,6 +13,7 @@ plugins {
alias(libs.plugins.com.android.test) apply false
alias(libs.plugins.org.jetbrains.kotlin.serialization) apply false
alias(libs.plugins.org.jetbrains.kotlin.jvm) apply false
+ alias(libs.plugins.androidx.baselineprofile) apply false
}
subprojects {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 6aaafb276..8689e597d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -34,6 +34,7 @@ chucker = "4.0.0"
logback = "3.0.0"
lottie-compose = "6.4.0"
nextLib = "0.7.1"
+profileinstaller = "1.4.1"
slf4j-api = "2.0.13"
squareup-retrofit2 = "2.11.0"
@@ -47,7 +48,7 @@ androidx-test-ext-junit = "1.2.1"
espresso-core = "3.6.1"
com-google-android-material = "1.12.0"
androidx-test-uiautomator = "2.3.0"
-androidx-benchmark = "1.3.0"
+androidx-benchmark = "1.3.3"
androidx-graphics-shapes = "1.0.1"
minabox = "1.7.1"
@@ -89,6 +90,7 @@ androidx-constraintlayout-compose = { group = "androidx.constraintlayout", name
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "androidx-paging" }
androidx-paging-runtime-ktx = { module = "androidx.paging:paging-runtime-ktx", version.ref = "androidx-paging" }
+androidx-profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version.ref = "profileinstaller" }
androidx-tv-material = { group = "androidx.tv", name = "tv-material", version.ref = "androidx-tv" }
androidx-startup-runtime = { group = "androidx.startup", name = "startup-runtime", version.ref = "androidx-startup" }
@@ -170,11 +172,11 @@ symbol-processing-api = { module = "com.google.devtools.ksp:symbol-processing-ap
[plugins]
com-android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
com-android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" }
+com-android-test = { id = "com.android.test", version.ref = "android-gradle-plugin" }
org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
org-jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
com-google-dagger-hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "google-dagger" }
com-google-devtools-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp-plugin" }
-com-android-test = { id = "com.android.test", version.ref = "android-gradle-plugin" }
androidx-baselineprofile = { id = "androidx.baselineprofile", version.ref = "androidx-benchmark" }
org-jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
\ No newline at end of file
diff --git a/processor/build.gradle.kts b/processor/build.gradle.kts
index dc27070ee..34e00dd67 100644
--- a/processor/build.gradle.kts
+++ b/processor/build.gradle.kts
@@ -1,8 +1,21 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
plugins {
- kotlin("jvm")
+ alias(libs.plugins.org.jetbrains.kotlin.jvm)
alias(libs.plugins.com.google.devtools.ksp)
}
+java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+}
+
+kotlin {
+ compilerOptions {
+ jvmTarget = JvmTarget.JVM_17
+ }
+}
+
ksp {
arg("autoserviceKsp.verify", "true")
arg("autoserviceKsp.verbose", "true")
diff --git a/settings.gradle.kts b/settings.gradle.kts
index ca1f55403..36b4c1636 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -29,8 +29,7 @@ include(
":feature:channel",
":feature:crash"
)
-include(":benchmark")
+include(":baselineprofile")
include(":i18n")
include(":codec:lite", ":codec:rich")
-include(":annotation")
-include(":processor")
+include(":annotation", ":processor")