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
700
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
730
Other Decks in Technology
See All in Technology
BigQuery を検索ソースとした AI Agent の作り方って 〇〇 通りあんねん
satohjohn
0
150
PLaMo 3.0 Primeの構造化出力サポート
pfn
PRO
0
160
CloudWatchから始めるAWS監視
butadora
0
310
AIがAPIを書く時代に、私たちは何を設計すべきか
nagix
0
200
実践が先生だった— 新卒サーバーエンジニア1年目のリアル
mixi_engineers
PRO
0
230
事業成長とAI活用を止めないデータ基盤アーキテクチャの設計思想
hiracky16
1
790
クラウドセキュリティ入門 ~安全なクラウド利用のための基礎知識~
lhazy
7
6.5k
脱Jenkins、インターン生が挑んだCIツールGitHubActions移行
mixi_engineers
PRO
1
300
システム監視を 「システムを監視するだけ」で 終わらせないために
seiud
0
160
Data Hubグループ 紹介資料
sansan33
PRO
0
3.1k
オートロックマンションなのに、各部屋は施錠なし!? 攻撃者が組織内ネットワークで大暴れする理由 / The Front Door Is Locked, but the Rooms Are Wide Open: Why Attackers Move Freely Inside Enterprise Networks
nttcom
0
1k
[しろおび夏祭り2026] チャットするAIから、作業するAIへ - 使われ方の変化と、その裏側で起きていること
kk0n
0
1.1k
Featured
See All Featured
A Soul's Torment
seathinner
6
3.1k
The browser strikes back
jonoalderson
0
1.4k
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
280
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
270
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1.3k
Marketing to machines
jonoalderson
1
5.6k
Evolving SEO for Evolving Search Engines
ryanjones
0
250
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.3k
Site-Speed That Sticks
csswizardry
13
1.4k
Ruling the World: When Life Gets Gamed
codingconduct
0
290
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
470
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
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で作られている部分