Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Jetpack Composeのパフォーマンスの基本
Search
松田
June 30, 2022
Technology
690
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Jetpack Composeのパフォーマンスの基本
以下で話したJetpack Composeのパフォーマンスの基本についてのスライドです
https://andpad.connpass.com/event/249842/
松田
June 30, 2022
More Decks by 松田
See All by 松田
GraphQL導入への技術選定
matsudamper
2
720
Other Decks in Technology
See All in Technology
失敗を資産に変えるClaude Code
shinyasaita
0
620
エラーバジェットのアラートのタイミングを考える.pdf
kairim0
0
140
LLMにもCAP定理があるという話
harukasakihara
0
330
Claude Code×Terraform IaC テンプレート駆動開発
itouhi
1
520
NAB Show 2026 動画技術関連レポート / NAB Show 2026 Report
cyberagentdevelopers
PRO
0
190
現地で盛り上がった WWDC26 Keynote
zozotech
PRO
1
230
チームで進めるAI駆動アジャイル×ウォーターフォール
kumaiu
0
160
protovalidate-es を導入してみた
bengo4com
0
180
2026TECHFRESH畢業分享會 - Lightning Talk - 打造精準高效的 MCP 設計模式與測試實務
line_developers_tw
PRO
0
960
Oracle AI Database@Google Cloud:サービス概要のご紹介
oracle4engineer
PRO
6
1.5k
Android の公式 Skill / Android skills
yanzm
0
140
なぜ Platform Engineering の土台に Kubernetes を選ぶのか
r4ynode
2
630
Featured
See All Featured
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
5.9k
Making Projects Easy
brettharned
120
6.7k
Between Models and Reality
mayunak
4
330
Navigating Team Friction
lara
192
16k
The SEO Collaboration Effect
kristinabergwall1
1
480
Visualization
eitanlees
152
17k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
200
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
62
54k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
Everyday Curiosity
cassininazir
0
230
How to Build an AI Search Optimization Roadmap - Criteria and Steps to Take #SEOIRL
aleyda
1
2.1k
Transcript
Jetpack Composeの パフォーマンスの基本 Retty アプリチーム 松田 2022/06/30
自己紹介 Retty株式会社 アプリチーム所属 松田 • 2020年入社 3年目 • やっていること
◦ 主にAndroid ◦ たまにiOS ◦ バックエンドサーバー(Kotlin) アプリエンジニア 特にAndroid / Kotlinバックエンドサーバー に興味があるエンジニア募集中
Rettyとは
1.Jetpack Composeの基本の動き方 https://developer.android.com/jetpack/compose/phases 例外) ・BoxWithConstraints ・LazyColumn ・LazyRow 単方向データフローで動 いている
1.Jetpack Composeの基本の動き方 https://developer.android.com/jetpack/compose/phases Treeの作成
1.Jetpack Composeの基本の動き方 https://developer.android.com/jetpack/compose/phases ・サイズ計算 ・座標計算
1.Jetpack Composeの基本の動き方 https://developer.android.com/jetpack/compose/phases いかに効率化するか いかにReCompositionを行わずに Layoutフェーズだけ実行できるか
目次 1. Jetpack Composeの動作フェーズ 2. Composable 2.1. Compositionをskipする 2.2. Rettyで使用している設計例
2.3. Restart不可能なComposable 3. Layout 3.1. Layoutフェーズだけを実行する
2.StableとSkippable @Composable public fun MainContent( title: String, color: Color, items:
List<String>, ) { // ~~~~ } 引数がequalsで比較して同一 であればそのComposable内 のCompositionはスキップされ る ※Composable関数がStable である場合に限る
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が行われる。
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
2.Stableか調べる 1. モジュールごとのSummary(*-module.json) 2. Composable関数の情報(*-composables.txt) 3. Composable関数の引数に与えるクラスの情報(*-classes.txt)
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可能でない
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でない
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
2.Stableにする UnstableをStableにしてskippableにするにはどうしたら良いかの前に 1. Composable関数がskippableで無いのは、必ずしも悪い事では無いと記述 がある https://github.com/androidx/androidx/blob/403b6d0032c289ed6e65a1c75f137c07aedd220f/compose /compiler/design/compiler-metrics.md 2. 公式ドキュメントのサンプルにも普通にunstableを引数に取っている
Rettyでは実装が難しく無い限り基本的に StableにしてSkippableにする
2.Stableにする @StableMarker 1. @Immutable ◦ publicな値は変更されない ◦ MutableStateを通しても値が変更されない 2.
@Stable ◦ publicな値は変更されない ◦ MutableStateを通して値が変更される https://developer.android.com/reference/kotlin/androidx/compose/runtime/StableMarker
2.Stableにする ListをStableにする @Immutable public class ImmutableList<E>(list: List<E>) : List<E>
by list.toList()
Rettyで使用している設計例
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 になるラッパー
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) } }
Restart不可能なComposable
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
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
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 ログ出力
2.@NonRestartableComposable Compose 1.1 での使用箇所
2.@NonRestartableComposable Compose 1.2.0 (beta2) での使用箇所
2.@NonRestartableComposable リスタート不可能にすると良い時 • 再コンポジションの「ルート」になることがないと 考えられる場合 • つまり、Composable関数が直接変数を読み込まない場 合 全て別のComposableに渡しているので、 Card側は再起動可能にする必要はない
Layoutフェーズだけを実行する
3.Layoutフェーズだけを実行する スクロールしても画像の位置は 変わっていないように見える CollapsingLayout
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の機能
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の回数
3.Layoutフェーズだけを実行する 公式の例では、offsetが、Lambda版と非Lambda版がある。 • 固定サイズ -> 非Lambda版 • アニメーションする ->
Lambda版
最後に: Composeで作られている部分