Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Jetpack Composeのパフォーマンスの基本

松田
June 30, 2022

Jetpack Composeのパフォーマンスの基本

以下で話したJetpack Composeのパフォーマンスの基本についてのスライドです
https://andpad.connpass.com/event/249842/

松田

June 30, 2022
Tweet

Other Decks in Technology

Transcript

  1. 自己紹介
 Retty株式会社
 アプリチーム所属
 松田
 
 • 2020年入社 3年目
 • やっていること


    ◦ 主にAndroid
 ◦ たまにiOS
 ◦ バックエンドサーバー(Kotlin) 
 アプリエンジニア 特にAndroid / Kotlinバックエンドサーバー に興味があるエンジニア募集中
  2. 目次
 1. Jetpack Composeの動作フェーズ
 2. Composable
 2.1. Compositionをskipする
 2.2. Rettyで使用している設計例


    2.3. Restart不可能なComposable
 3. Layout
 3.1. Layoutフェーズだけを実行する

  3. 2.StableとSkippable
 @Composable public fun MainContent( title: String, color: Color, items:

    List<String>, ) { // ~~~~ } 引数がequalsで比較して同一 であればそのComposable内 のCompositionはスキップされ る ※Composable関数がStable である場合に限る
  4. 2.StableとSkippable
 val mutableItems = mutableListOf<String>() val items: List<String> = mutableItems

    mutableItems.add("1") println(items) // [1] 1. Listは外部から変更可能。 → これがStableではないという事 2. 外部から変更された事をComposeは検知できない。 3. なのでskipされずに毎回Compositionが行われる。
  5. 2.Stableか調べる
 allprojects { gradle.projectsEvaluated { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { def

    baseDir = "path/to/dir" def outPath = "${baseDir}${project.path.replace(":", "/")} " freeCompilerArgs += [ "-P", "plugin:androidx.compose.compiler.plugins.kotlin :metricsDestination= ${outPath}", "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination= ${outPath}" ] } } } } https://github.com/androidx/androidx/blob/403b6d0032c289ed6e65a1c75f137c07aedd220f/compose/compiler/design/compiler-metrics.md TopLevelの build.gradle
  6. 2.Stableか調べる
 
 { "skippableComposables": 81, "restartableComposables": 86, "readonlyComposables": 0, "totalComposables":

    86, "restartGroups": 86, "totalGroups": 129, "staticArguments": 129, "certainArguments": 52, "knownStableArguments": 961, "knownUnstableArguments": 20, "unknownStableArguments": 10, "totalArguments": 991, "markedStableClasses": 0, "inferredStableClasses": 16, "inferredUnstableClasses": 4, "inferredUncertainClasses": 1, "effectivelyStableClasses": 16, "totalClasses": 21, "memoizedLambdas": 68, "singletonLambdas": 32, "singletonComposableLambdas": 3, "composableLambdas": 12, "totalLambdas": 71 } { "skippableComposables": 81, "restartableComposables": 86, "totalComposables": 86 } *-module.json 5つはSkip可能でない
  7. 2.Stableか調べる
 
 *-composables.txt
 
 restartable fun ListItem( stable modifier: Modifier

    unstable uiState: ListItemUiState ) • 引数が全てstable • skippableである restartable skippable fun Label( stable drawableRes: Int stable colorRes: Int stable contentDescription: String ) • 引数にunstableがある • skippableでない
  8. 2.Stableか調べる
 
 *-classes.txt
 unstable class ListItemUiState{ stable val iconPath: String?

    stable val userName: String unstable val labels: List<String> stable val isDraft: Boolean stable val onClick: Function0<Unit> <runtime stability> = Unstable } • unstableな値があるとクラスも Unstableになる • Lambdaはstable
  9. 2.Stableにする
 
 @StableMarker 1. @Immutable ◦ publicな値は変更されない ◦ MutableStateを通しても値が変更されない 2.

    @Stable ◦ publicな値は変更されない ◦ MutableStateを通して値が変更される https://developer.android.com/reference/kotlin/androidx/compose/runtime/StableMarker
  10. 2.Rettyで使用している設計例
 Root @Immutable public data class NamesScreenUiState( public val names:

    ImmutableList<NameUiState> , ) @Immutable public data class NameUiState( public val name: String, public val onClick: ComposeEventWrapper <() -> Unit>, } Listの要素 比較時に必ずtrue になるラッパー
  11. 2.Rettyで使用している設計例
 @Composable public fun NamesScreen ( modifier: Modifier , uiState:

    NamesScreenUiState , ) { Column(modifier = modifier.fillMaxWidth()) { LazyColumn(modifier = Modifier.fillMaxWidth()) { items(uiState.names) { item -> Name( modifier = Modifier.fillMaxWidth(), uiState = item ) } } } } @Composable public fun Name( modifier: Modifier, uiState: NameUiState, ) { Column( modifier = modifier.clickable { uiState.onClick.event() } ) { Text(text = uiState.name) } }
  12. 2.Restart不可能なComposable
 
 
 { "skippableComposables": 81, "restartableComposables": 86, "readonlyComposables": 0,

    "totalComposables": 86, "restartGroups": 86, "totalGroups": 129, "staticArguments": 129, "certainArguments": 52, "knownStableArguments": 961, "knownUnstableArguments": 20, "unknownStableArguments": 10, "totalArguments": 991, "markedStableClasses": 0, "inferredStableClasses": 16, "inferredUnstableClasses": 4, "inferredUncertainClasses": 1, "effectivelyStableClasses": 16, "totalClasses": 21, "memoizedLambdas": 68, "singletonLambdas": 32, "singletonComposableLambdas": 3, "composableLambdas": 12, "totalLambdas": 71 } { "skippableComposables": 81, "restartableComposables": 86, "totalComposables": 86 } *-module.json
  13. 2.@NonRestartableComposable
 @Composable public fun Restartable(count: Int) { SideEffect { Log.d("Restartable",

    "SideEffect") } Text(text = "Restartable: ButtonCount=$count") } @Composable @NonRestartableComposable public fun NonRestartable(count: Int) { SideEffect { Log.d("NonRestartable", "SideEffect") } Text(text = "NonRestartable: ButtonCount=$count") } 1 2 2
  14. 2.@NonRestartableComposable
 var nonRestartable by remember { mutableStateOf(0) } var restartable

    by remember { mutableStateOf(0) } Column { Button(onClick = { restartable++ }) { Text(text = "Restartable") } Restartable(restartable) Button(onClick = { nonRestartable++ }) { Text(text = "NonRestartable") } NonRestartable(nonRestartable) } Restartable D SideEffect NonRestartable D SideEffect ログ出力
  15. 3.Layoutフェーズだけを実行する
 
 @Composable public fun CollapsingLayout ( modifier: Modifier =

    Modifier , scrollOffset: Int , content: @Composable () -> Unit ) { Layout( modifier = modifier, content = content ) { measurables, constraints -> TODO("scrollOffsetを使って計算") } } 1 2 Compositionの回数 ※ComposeRuntime1.2から使える LayoutInspectorの機能
  16. 3.Layoutフェーズだけを実行する
 
 @Composable public fun CollapsingLayout ( modifier: Modifier =

    Modifier , scrollOffsetProvider: () -> Int, content: @Composable () -> Unit ) { Layout( modifier = modifier, content = content ) { measurables, constraints -> val scrollOffset = scrollOffsetProvider() TODO("scrollOffsetを使って計算") } } 1 2 ※ComposeRuntime1.2から使える LayoutInspectorの機能 Compositionの回数