Skip to content

Commit

Permalink
jank stats added.
Browse files Browse the repository at this point in the history
  • Loading branch information
merttoptas committed Oct 16, 2023
1 parent 7a3c55e commit c6d5080
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.loodos.samplecomposeandroid.di

import android.app.Activity
import android.util.Log
import android.view.Window
import androidx.metrics.performance.JankStats
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent

@Module
@InstallIn(ActivityComponent::class)
object JankStatsModule {
@Provides
fun providesOnFrameListener(): JankStats.OnFrameListener {
return JankStats.OnFrameListener { frameData ->
// Make sure to only log janky frames.
if (frameData.isJank) {
// We're currently logging this but would better report it to a backend.
Log.v("SampleCompose Jank", frameData.toString())
}
}
}

@Provides
fun providesWindow(activity: Activity): Window {
return activity.window
}

@Provides
fun providesJankStats(
window: Window,
frameListener: JankStats.OnFrameListener,
): JankStats {
return JankStats.createAndTrack(window, frameListener)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavHostController
Expand All @@ -12,6 +13,7 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions
import com.loodos.data.util.NetworkMonitor
import com.loodos.samplecomposeandroid.navigation.TopLevelDestination
import com.loodos.ui.TrackDisposableJank
import com.merttoptas.category.navigation.navigateToCategory
import com.merttoptas.home.navigation.navigateToHome
import com.merttoptas.profile.navigation.navigateToProfile
Expand All @@ -30,6 +32,7 @@ fun rememberMainAppState(
coroutineScope: CoroutineScope = rememberCoroutineScope(),
navController: NavHostController = rememberNavController(),
): MainAppState {
NavigationTrackingSideEffect(navController)
return remember(navController, coroutineScope, networkMonitor) {
MainAppState(navController, coroutineScope, networkMonitor)
}
Expand Down Expand Up @@ -88,3 +91,21 @@ class MainAppState(
navController.popBackStack()
}
}

/**
* Stores information about navigation events to be used with JankStats
*/
@Composable
private fun NavigationTrackingSideEffect(navController: NavHostController) {
TrackDisposableJank(navController) { metricsHolder ->
val listener = NavController.OnDestinationChangedListener { _, destination, _ ->
metricsHolder.state?.putState("Navigation", destination.route.toString())
}

navController.addOnDestinationChangedListener(listener)

onDispose {
navController.removeOnDestinationChangedListener(listener)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.core.view.WindowCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.metrics.performance.JankStats
import com.loodos.data.util.NetworkMonitor
import com.loodos.samplecomposeandroid.feature.appstate.MainApp
import dagger.hilt.android.AndroidEntryPoint
Expand All @@ -29,7 +30,10 @@ import javax.inject.Inject
class MainActivity : ComponentActivity() {

@Inject
lateinit var networkMonitor: com.loodos.data.util.NetworkMonitor
lateinit var networkMonitor: NetworkMonitor

@Inject
lateinit var lazyStats: dagger.Lazy<JankStats>

companion object {
const val splashFadeDurationMillis = 1000L
Expand Down Expand Up @@ -88,6 +92,16 @@ class MainActivity : ComponentActivity() {
}
}
}

override fun onResume() {
super.onResume()
lazyStats.get().isTrackingEnabled = true
}

override fun onPause() {
super.onPause()
lazyStats.get().isTrackingEnabled = false
}
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ internal fun Project.configureAndroidCompose(
val bom = libs.findLibrary("compose-bom").get()
add("implementation", platform(bom))
add("androidTestImplementation", platform(bom))
// Add ComponentActivity to debug manifest
add("debugImplementation", libs.findLibrary("androidx.compose.ui.testManifest").get())

}
}

Expand All @@ -44,9 +47,12 @@ internal fun Project.configureAndroidCompose(
private fun Project.buildComposeMetricsParameters(): List<String> {
val metricParameters = mutableListOf<String>()
val enableMetricsProvider = project.providers.gradleProperty("enableComposeCompilerMetrics")
val relativePath = projectDir.relativeTo(rootDir)
val buildDir = layout.buildDirectory.get().asFile

val enableMetrics = (enableMetricsProvider.orNull == "true")
if (enableMetrics) {
val metricsFolder = File(project.buildDir, "compose-metrics")
val metricsFolder = buildDir.resolve("compose-metrics").resolve(relativePath)
metricParameters.add("-P")
metricParameters.add(
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + metricsFolder.absolutePath
Expand All @@ -56,7 +62,7 @@ private fun Project.buildComposeMetricsParameters(): List<String> {
val enableReportsProvider = project.providers.gradleProperty("enableComposeCompilerReports")
val enableReports = (enableReportsProvider.orNull == "true")
if (enableReports) {
val reportsFolder = File(project.buildDir, "compose-reports")
val reportsFolder = File(project.buildDir, "compose-reports").resolve(relativePath)
metricParameters.add("-P")
metricParameters.add(
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + reportsFolder.absolutePath
Expand Down
80 changes: 80 additions & 0 deletions core/ui/src/main/java/com/loodos/ui/JankStatsExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.loodos.ui

import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.DisposableEffectResult
import androidx.compose.runtime.DisposableEffectScope
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.platform.LocalView
import androidx.metrics.performance.PerformanceMetricsState
import kotlinx.coroutines.CoroutineScope

/**
* Retrieves [PerformanceMetricsState.Holder] from current [LocalView] and
* remembers it until the View changes.
* @see PerformanceMetricsState.getHolderForHierarchy
*/

@Composable
fun rememberMetricsStateHolder(): PerformanceMetricsState.Holder {
val localView = LocalView.current

return remember(localView) {
PerformanceMetricsState.getHolderForHierarchy(localView)
}
}

/**
* Convenience function to work with [PerformanceMetricsState] state. The side effect is
* re-launched if any of the [keys] value is not equal to the previous composition.
* @see TrackDisposableJank if you need to work with DisposableEffect to cleanup added state.
*/

@Composable
fun TrackJank(
vararg keys: Any?,
reportMetric: suspend CoroutineScope.(state: PerformanceMetricsState.Holder) -> Unit,
) {
val metrics = rememberMetricsStateHolder()
LaunchedEffect(metrics, *keys) {
reportMetric(metrics)
}
}

/**
* Convenience function to work with [PerformanceMetricsState] state that needs to be cleaned up.
* The side effect is re-launched if any of the [keys] value is not equal to the previous composition.
*/
@Composable
fun TrackDisposableJank(
vararg keys: Any?,
reportMetric: DisposableEffectScope.(state: PerformanceMetricsState.Holder) -> DisposableEffectResult,
) {
val metrics = rememberMetricsStateHolder()
DisposableEffect(metrics, *keys) {
reportMetric(this, metrics)
}
}



/**
* Track jank while scrolling anything that's scrollable.
*/
@Composable
fun TrackScrollJank(scrollableState: ScrollableState, stateName: String) {
TrackJank(scrollableState) { metricsHolder ->
snapshotFlow { scrollableState.isScrollInProgress }.collect { isScrollInProgress ->
metricsHolder.state?.apply {
if (isScrollInProgress) {
putState(stateName, "Scrolling=true")
} else {
removeState(stateName)
}
}
}
}
}
7 changes: 7 additions & 0 deletions feature/home/src/main/java/com/merttoptas/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.CircularProgressIndicator
Expand All @@ -32,6 +34,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.loodos.samplecomposeandroid.feature.home.R
import com.loodos.ui.TrackScrollJank
import de.palm.composestateevents.EventEffect

/**
Expand Down Expand Up @@ -89,13 +92,17 @@ fun Content(
onProductClick: (ProductItem) -> Unit,
modifier: Modifier = Modifier,
) {
val scrollableState = rememberLazyListState()
TrackScrollJank(scrollableState = scrollableState, stateName = "home:LazyList")

Column(
modifier = modifier
.fillMaxSize()
.imePadding(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
LazyColumn(
state = scrollableState,
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(vertical = 16.dp),
Expand Down

0 comments on commit c6d5080

Please sign in to comment.