안녕하세요. 자비스 2기 부장을 맡고 있는 안드로이드 개발자 박의엘입니다. 자비스 안드로이드에서 Compose로 개발되어 있는 코드들을 최적화한 내용에 대해 소개합니다.
우선 Compose로 만들어진 코드에서 Stable이 얼마나 중요한지 알아야합니다.
Recomposition이 발생했을 때 Parameter가 Stable 하다면 Compose Runtime에서 Recomposition을 건너뛰는 것(Skippable)이 가능한 상태.
Stable이 Compose에서 중요하게 작용하는 이유는 Smart Recomposition 때문입니다.
@Composable 어노테이션이 달린 함수는 Compose 컴파일러 플러그인에 의해 일반 함수와 다르게 Compose 생명주기에 따라 재실행 합니다. 상태가 바뀔때마다 실행되는 불필요한 recompostion을 막기 위해 Smart Recomposition을 사용합니다.
Smart Recomposition은 변경된 상태가 이전상태와 동일한지 비교하여 변경된 부분만 다시 그리고 나머지는 재사용하여 효율적으로 UI를 업데이트 합니다.
Smart Recomposition을 만들기 위해서는 객체를 Stable로 만들어야합니다.
Mendable은 Jetpack Compose compiler metrics의 지표를 HTML로 볼 수 있게 해주는 CLI 툴입니다.
allprojects {
tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinCompile::class.java).configureEach {
kotlinOptions {
// Trigger this with:
// ./gradlew assembleRelease -PenableMultiModuleComposeReports=true --rerun-tasks
if (project.findProperty("enableMultiModuleComposeReports") == "true") {
freeCompilerArgs += listOf("-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + rootProject.buildDir.absolutePath + "/compose_metrics/")
freeCompilerArgs += listOf("-P", "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + rootProject.buildDir.absolutePath + "/compose_metrics/")
}
}
}
}
루트 모듈의 build.gradle
파일에 컴파일 옵션을 추가합니다.
./gradlew assembleRelease -PenableMultiModuleComposeReports=true --rerun-tasks
저는 11분동안 돌아가서 노트북 터지는 줄 알았습니다.
rootProject/build/compose_metrics
경로에 mendable.jar
를 저장합니다.
mendable.jar
는 여기에서 다운로드 받습니다.
java -jar mendable.jar
mendable.jar
가 저장된 경로에서 gradle 명령어를 실행합니다.
실행이 완료되면 index.html
파일이 생성됩니다.
자비스 프로젝트에 적용을 했는데 70% stable
이라는 결과가 나왔습니다.
아래 결과를 보고 수정하는 작업을 하면 됩니다.
companyInfo: List<CompanyInfoData> // Not stable
list는 MutableList를 넣을 수 있기 때문에 Unstable한 함수로 확인이 됩니다. immutable collection을 사용해서 ImmutableList로 변경하였습니다.
companyInfo: ImmutableList<CompanyInfoData>
companyInfo = companyInfo.toPersistentList()
state: SetProfileState // Not stable
StableMaker의 @Immutable 어노테이션을 사용하여 stable하게 수정하였습니다.
@Immutable
internal data class SetProfileState(
val image: File?,
val imageUrl: String,
val uri: Uri?,
val buttonEnabled: Boolean,
)
하지만 여기서 DataClass가 compose와 의존성이 없는 모듈에 있다면 문제가 발생합니다. 자비스에서는 domain 모듈에 있는 entity를 참조하는 과정에서 문제가 발생했습니다. 찾아본 결과 skydoves님이 만드신 stableMarker 라이브러리를 사용하여 해결할 수 있었습니다.
painter: Painter // Not stable
Painter
클래스는 공식문서에 따르면 이미지 처리의 복잡성으로 인해 stable 클래스로 명시하지 않다고 나와있습니다. 따라서 painter
클래스 대신 Int 타입의 리소스 아이디를 매개변수로 전달하는 새로운 컴포저블을 만들면 문제를 해결할 수 있습니다.
@DrawableRes drawableResId: Int
uri: Uri? // Not stable
이미지를 사용하는곳에서 uri를 참조하는 부분이 있어 stable한 String으로 수정하였습니다.
uri: String?
navController: NavController // Not stable
navcontroller에 default값을 추가하여 stable로 변경하였습니다.
navController: NavController = rememberNavController()
100%를 달성하였습니다!!
Mendable을 적용하고 수정하면서 recomposition에 대해 더 많이 알게 되었습니다. Stable로 변경하고 찾아보면서 좋은 경험이 되었습니다. 앞으로도 100%를 유지할 수 있도록 하겠습니다.