본문 바로가기

Android

[안드로이드] 앱 성능 최적화 : 렌더링 성능

반응형

렌더링 성능에 대해 글을 쓰기 전에 간단히 렌더링이 무엇인지 정리해본다.

 

렌더링(Rendering)은 간단히 말하면, 데이터를 화면에 보이는 형태로 그려주는 과정을 말한다.

조금 풀어서 설명하면:

  • 안드로이드 앱에서는 코드나 데이터(예: 텍스트, 이미지, 버튼 등)가 실제로 눈에 보이는 화면(UI)으로 변환되는 과정을 "렌더링"이라고 부른다.
  • 예를 들면, 버튼을 만들었다면 → 이걸 "그리는 작업" → 유저가 터치할 수 있게 화면에 보여주기까지 이 전체 과정이 렌더링이다.

예를 들면:

  1. 개발자가: TextView에 "Hello World"를 설정함 → (데이터)
  2. 앱이: 이 텍스트를 화면에 맞는 글꼴, 색상, 위치로 "그려서" 보여줌 → (렌더링)

렌더링 왜 중요할까?

  • 렌더링이 느리면 화면이 버벅이거나, 스크롤할 때 끊김이 발생한다.
  • 렌더링이 최적화되어야 부드럽게 터치, 스크롤, 애니메이션 등이 작동한다

요약

렌더링 = 데이터를 화면에 보이게 만드는 과정
빠르고 부드러운 렌더링 = 좋은 사용자 경험 (UX)

 

앱에서 렌더링이 느리면? 사용자는 그냥 "앱이 별로구나"라고 느껴진다.
UI는 곧 UX, 이번엔 화면 렌더링 성능을 높이는 핵심 팁이라 앱 성능 최적화 영향을 미친다.


1. Recomposition 최소화 (Jetpack Compose)

  • remember, derivedStateOf, key() 등 Compose의 recomposition 제어 도구를 적극 사용한다.
val derived = remember(myValue) {
    derivedStateOf { myValue + 1 }
}

 

1) Recomposition이란?

  • Compose는 화면(UI)을 "상태(State)" 에 따라 그린다.
  • 상태가 바뀌면, 바뀐 부분만 다시 그리는 작업을 하는데, 이걸 Recomposition(리컴포지션) 이라고 부른다.

쉽게 말하면 👉 "아, 상태가 변했네? 다시 그려야겠다!" 하는 순간이 바로 리컴포지션이다.

 

2) 그런데 왜 "최소화"해야 할까?

  • 리컴포지션은 필요할 때만 일어나야 효율적이에요.
  • 불필요하게 자주 일어나면 ➔ CPU 사용량 증가 ➔ 배터리 소모 ➔ 앱이 버벅거림.
  • 그래서 "필요할 때만 최소한으로" 리컴포지션이 발생하도록 신경 써야 합니다.

3) Recomposition 최소화를 위한 기본 방법

remember 사용 변하지 않는 값은 기억해서, 불필요한 리컴포지션을 막는다.
derivedStateOf 사용 다른 상태(state)를 기반으로 파생된 값이 필요할 때 효율적으로 관리한다
Composable 함수 쪼개기 작은 단위로 쪼개서, 필요한 부분만 다시 그리게 한다.
key를 잘 활용 리스트나 반복문에서 항목이 고유하게 식별되게 한다.
Stable 데이터 사용 데이터 클래스에 불필요한 변화를 주지 않도록 설계한다.
LaunchedEffect, rememberUpdatedState 비싼 작업(예: API 호출)이 매번 재실행되지 않게 막는다.
 

비유하면

상태가 바뀔 때마다 집 전체를 리모델링하는 건 비효율적?
필요한 방만, 필요한 가구만 고쳐야 빠르고 효율적인 것과 같다.


4) 아주 간단한 예

 

❌ 리컴포지션이 너무 많이 일어나는 코드

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Text("Count: $count")
    Button(onClick = { count++ }) {
        Text("Add")
    }
}

 

여기선 버튼, 텍스트 모두가 매번 다시 그려진다.

 

✅ 리컴포지션 범위를 줄인 코드

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Column {
        CountText(count)  // 별도로 분리
        Button(onClick = { count++ }) {
            Text("Add")
        }
    }
}

@Composable
fun CountText(count: Int) {
    Text("Count: $count")
}

 

이제 버튼은 리컴포지션 안 되고, 텍스트만 다시 그린다. (효율적!)

 

5) Recomposition을 효율적으로하면?

 

  • 상태가 변할 때 필요한 부분만 다시 그리는 게 목표.
  • remember, derivedStateOf, Composable 쪼개기를 적극 활용.
  • "변하지 않는 부분"은 리컴포지션 안 일어나게 잘 관리!
  •  

 2. Layout Overdraw 줄이기 (View 방식)

  • 화면에 같은 픽셀을 여러 번 그리는 일이 없도록 Layout Inspector로 중복된 뷰 제거
  • 배경 중복, 투명 배경 조심!

3. 무거운 작업은 UI 쓰레드에서 분리

  • ViewModelScope.launch(Dispatchers.IO)로 데이터 로딩 등은 백그라운드에서 처리
  • UI 쓰레드에서 block 되는 작업은 반드시 피하기

 4. ConstraintLayout 활용

  • 중첩된 LinearLayout보다 성능에 훨씬 유리
  • 불필요한 Nested Layout은 피하기

 5. RecyclerView 성능 개선

  • setHasFixedSize(true) 설정
  • DiffUtil을 사용한 효율적인 데이터 변경
  • ViewHolder 재사용 제대로 하기

 6. 애니메이션 과도하게 사용 금지

  • 너무 무거운 애니메이션은 렌더링 병목의 원인이 됩니다.
  • MotionLayout은 적절히 사용하되, 성능에 유의할 것

7.  정리

컴포즈의 Recomposition에 대한 설명이 중요하다 생각하여 정리가 길어졌지만, 다른 항목들은 간단하게 정리했다.

렌더링 성능을 잘 조절하면, 앱이 '가볍고 자연스럽다'는 인상을 줄 수 있고, 하드웨어 성능에만 의존하지 않고도 깔끔하고 매끄러운 UX를 만들 수 있다.
성능 좋은 UI가 최고의 마케팅 수단이 될 수 있다고 생각하기 때문에, 렌더링 최적화는 그만큼 중요한 부분이라고 생각한다.

 

반응형