→ 리컴포지션은 Composable 함수의 상태가 변경될 때 Compsable 함수를 다시 호출하여, UI를 업데이트 하는 것
전체 UI 트리를 재구성하는 것은 컴퓨팅 성능과 배터리 수명을 사용하므로 계산 비용이 많이 들 수 있습니다. Compose는 SmartRecpomposition을 통해 이 문제를 해결합니다 .
공식 문서에 따르면 UI트리를 재구성하는 것은 비용이 많이 들 수 있다고 나와 있습니다. 아래의 요인에 따라 빈번한 리컴포지션 발생 시, 성능저하 이슈가 발생할 수도 있습니다.
경험적으로 일반적인 UI에서 리컴포지션이 몇 번 더 발생하더라도, 퍼포먼스가 떨어진다는 것은 체감하지 못했습니다. 하지만 그럼에도 최적화하는 방법에 대해서 생각해 볼 필요는 있을 것 같습니다. 그러면 불필요한 리컴포지션을 최소화하기 위해 우리가 할 수 있는 것은 무엇일까요? 컴포저블 파라미터의 안정성을 확인하는 것입니다. 불안정한 상태가 많이 포함되어 있으면, 성능 및 기타 문제가 발생할 수 있습니다.
컴포즈는 컴포저블 파라미터의 안정성을 사용하여, 리컴포지션을 스킵할 수 있을 지 결정합니다.
컴포즈는 타입을 안정적인 것 또는 불안정적인 것으로 간주합니다.
Stable
data class User(
val id: String
val name: String
)
User("1", "XXX") == User("1", "XXX")
!!
연산자와 유사합니다.Unstable
List, Set
, Map
와 같은 컬렉션 클래스를 항상 불안정한 것으로 간주합니다. 이는 해당 파일이 불변성이라고 보장할 수 없기 때문입니다. (MutableList)@Immutable
또는 @Stable
로 어노테이션을 달아서 안정적인 타입으로 만들 수 있습니다. data class Snack(
val id: Long,
val name: String,
val imageUrl: String,
val price: Long,
val tagline: String = "",
val tags: Set<String> = emptySet()
)
unstable class Snack {
stable val id: Long
stable val name: String
stable val imageUrl: String
stable val price: Long
stable val tagline: String
unstable val tags: Set<String>
<runtime stability> = Unstable
}
@Immutable
data class Snack(
val id: Long,
val name: String,
val imageUrl: String,
val price: Long,
val tagline: String = "",
val tags: Set<String> = emptySet()
) //snackList는 여전히 unstable
@Composable
private fun HighlightedSnacks(
snacks: ImmutableList<Snack>,
)
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
@Composable
private fun HighlightedSnacks(
index: Int,
- snacks: List<Snack>, (unstable)
+ snacks: ImmutableList<Snack>, (stable)
onSnackClick: (Long) -> Unit,
modifier: Modifier = Modifier
)
or
@Composable
private fun HighlightedSnacks(
index: Int,
snacks: SnackCollection,
onSnackClick: (Long) -> Unit,
modifier: Modifier = Modifier
)
https://developer.android.com/jetpack/compose/performance/stability/fix?hl=ko#immutable-collections
data class Contact(
var name: String,
var number: String
)
Painter
를 매개변수로 전달하는 대신 URL 또는 드로어블 리소스 ID를 매개변수로서 컴포저블에 전달하는 것이 좋습니다. @Composable
fun UnstableImage(painter: Painter) {
//항상 리컴포지션이 발생함.
}
@Composable
fun MyImage(url: String) {
}
파라미터로 url 또는 res 값을 받는 것을 권장
https://developer.android.com/jetpack/compose/graphics/images/optimization?hl=ko#pass-url
Stability in Compose | Jetpack Compose | Android Developers
→ 모든 파라미터가 안정적이라면 상태가 변할때만 UI가 갱신된다. 이상태를 skippable이라고한다. 우리는 최대한 skippable한 UI를 구성해서 렌더링 효율을 높이자
@Immutable
값이 절대 변하지 않을 것이라는 약속입니다.@Stable
값을 관찰할 수 있다는 약속이며 변경된 경우 리스너에게 알림을 보냅니다.불변 객체는 '인스턴스가 생성된 후에 공개적으로 액세스 가능한 모든 속성과 필드가 변경되지 않음'을 의미합니다. 이 특성은 Compose가 두 인스턴스 간의 '변경 사항'을 매우 쉽게 감지할 수 있음을 의미합니다.
반면에 안정적인 객체가 반드시 불변인 것은 아닙니다. 안정적인 클래스는 변경 가능한 데이터를 보유할 수 있지만 모든 변경 가능한 데이터는 변경 시 Compose에 알려야 필요에 따라 재구성이 발생할 수 있습니다.
Compose는 모든 함수 매개변수가 안정적이거나 변경할 수 없으며 건너뛸 수 있는 함수의 핵심임을 감지하면 런타임 시 다양한 최적화를 활성화할 수 있습니다. Compose는 클래스가 불변인지 안정적인지 자동으로 추론하려고 시도하지만 때로는 올바르게 추론하지 못하는 경우도 있습니다. 그런 일이 발생하면 클래스에서 [@Immutable](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Stable)
및 주석을 사용할 수 있습니다.[@Stable](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Stable)
다시 시작 가능 및 건너뛸 수 있음은 함수의 Compose 속성인 반면 불변성과 안정성은 객체 인스턴스, 특히 구성 가능한 함수에 전달되는 객체의 속성입니다.
불변 객체는 '인스턴스가 생성된 후에 공개적으로 액세스 가능한 모든 속성과 필드가 변경되지 않음'을 의미합니다. 이 특성은 Compose가 두 인스턴스 간의 '변경 사항'을 매우 쉽게 감지할 수 있음을 의미합니다.
반면에 안정적인 객체가 반드시 불변인 것은 아닙니다. 안정적인 클래스는 변경 가능한 데이터를 보유할 수 있지만 모든 변경 가능한 데이터는 변경 시 Compose에 알려야 필요에 따라 재구성이 발생할 수 있습니다.
@Stable
class SomeViewState {
var someFlag by mutableStateOf(false)
}
Compose는 모든 함수 매개변수가 안정적이거나 변경할 수 없으며 건너뛸 수 있는 함수의 핵심임을 감지하면 런타임 시 다양한 최적화를 활성화할 수 있습니다. Compose는 클래스가 불변인지 안정적인지 자동으로 추론하려고 시도하지만 때로는 올바르게 추론하지 못하는 경우도 있습니다. 그런 일이 발생하면 클래스에서 [@Immutable](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Stable)
및 주석을 사용할 수 있습니다.[@Stable](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Stable)
성능 문제: 구성 가능한 함수에서 불필요한 재구성이 수행되면 애플리케이션 성능에 부정적인 영향을 미칠 수 있습니다. 따라서 개발자는 재구성을 신중하게 사용하고 불필요한 재계산을 피해야 합니다.
메모리 소비: 구성 가능한 함수가 재구성되면 Jetpack Compose는 메모리를 활용합니다. 이로 인해 애플리케이션의 메모리 소비가 증가하여 성능에 부정적인 영향을 미칠 수 있습니다.
업데이트 문제: 재구성을 사용하면 구성 가능한 함수 내의 항목이 변경될 때 전체 함수를 다시 계산할 수 있습니다. 하나의 항목을 변경하면 전체 인터페이스에 영향을 미칠 수 있으므로 이는 특히 대규모 애플리케이션에서 문제가 될 수 있습니다. 따라서 Jetpack Compose 개발자는 컴포저블 기능을 최대한 작고 구체적으로 만드는 것이 좋습니다.
제한된 실행 취소 지원: 재구성은 구성 가능한 기능의 적절한 실행 취소를 항상 보장하지 않습니다. 따라서 개발자는 필요한 경우 실행 취소 작업을 수동으로 처리해야 할 수도 있습니다.
효율성: 재구성은 수정된 구성 요소만 다시 계산하므로 전체 인터페이스를 다시 계산할 필요가 없습니다. 이를 통해 애플리케이션의 성능이 향상되고 효율성이 향상됩니다.
단순성: Jetpack Compose는 Android 인터페이스를 디자인하기 위해 XML과 같은 기존 도구 대신 Kotlin으로 직접 코드 작성을 사용합니다. 이를 통해 개발자는 인터페이스를 더 빠르고 쉽게 만들 수 있습니다.
모듈성: 구성 가능한 기능은 재사용 가능한 작은 부분으로 나눌 수 있습니다. 이를 통해 개발자는 인터페이스를 더욱 모듈화하여 더 깔끔하고 읽기 쉬운 코드를 만들 수 있습니다.
쉬운 테스트 가능성 및 유지 관리: 재구성은 코드의 복잡성을 줄여줍니다. 개발자는 필요한 경우에만 특정 요소의 재계산을 지정하면 됩니다. 이렇게 하면 코드가 더 모듈화되고 유지 관리가 더 쉬워집니다.
애니메이션: 재구성은 애니메이션에도 사용할 수 있습니다. 애니메이션의 자동 재구성은 보다 원활한 사용자 경험을 제공합니다.
Recomposition은 세 단계로 나뉩니다.
위 세 단계를 거쳐 컴포저블 함수는 리컴포지션 됩니다.
→ 모든 경우에 가장 좋은 방법은 구성 가능한 함수를 빠르고, 멱등성이 있고, 부작용이 없도록 유지하는 것입니다.
쉽게 말하면 리컴포지션이랑 컴포저블의 상태가 바뀔 때 화면을 재구성하는데,
우리가 실제로 개발하면서 신경쓰이는 부분은 리컴포지션이 여러번 될 경우, 성능 저하 이슈가 발생할까? 어떤 악영향이 있을까? 리컴포지션 최적화가 성능향상에 도움이되는가? 이런 고민들을 하게 된다.
전체 UI 트리를 재구성하는 것은 컴퓨팅 성능과 배터리 수명을 사용하므로 계산 비용이 많이 들 수 있습니다. Compose는 지능적인 재구성을 통해 이 문제를 해결합니다 .
그렇다면 리컴포지션을 최소화하기 위한 방법은 무엇이 있을까?