[안드로이드] 앱 성능 최적화 : 메모리 최적화
요즘은 안드로이드 폰의 하드웨어 성능이 많이 좋아져서 예전처럼 OOM(Out Of Memory) 오류를 자주 보긴 어렵지만,
초창기 안드로이드 시절에는 메모리 부족으로 인한 앱 종료가 흔한 문제였다. 지금도 앱을 개발하다 보면 "앱이 느려요", "종료돼요", "메모리 부족 경고가 떠요" 같은 피드백을 종종 받게 되는데, 이럴 땐 가장 먼저 앱의 메모리 사용량을 점검해보는 것이 좋다.
이 글에서는 메모리 누수를 방지하고, 효율적인 메모리 사용을 통해 성능을 개선하는 방법을 정리해보려고한다.
1. 불필요한 Context 참조 피하기
Context는 앱의 거의 모든 리소스에 접근 가능한 강력한 객체지만, 잘못 사용하면 메모리 누수의 주범이 된다.
❌ 잘못된 예:
class MyUtil(context: Context) {
private val context = context // Activity context를 참조하고 있음
}
✅ 올바른 예:
class MyUtil(context: Context) {
private val appContext = context.applicationContext // application context 사용
}
2. Activity/Fragment에서 View 참조 해제
Fragment 또는 Activity에서 View 객체를 멤버 변수로 들고 있다가, 화면이 종료돼도 해제되지 않으면 메모리 누수 발생!
onDestroyView에서 참조 해제:
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
3. 리스너 해제 꼭 하기
리스너, 콜백, 옵저버 등은 등록만 해놓고 제거하지 않으면 메모리 누수가 발생한다.
LiveData Observer 제거 예:
override fun onDestroy() {
super.onDestroy()
viewModel.data.removeObservers(this)
}
4. Glide, Picasso 사용 시 주의사항
이미지 로딩 라이브러리는 강력하지만, Activity/Fragment가 종료돼도 이미지 로딩이 계속되면 메모리 낭비가 발생한다.
Glide 예:
Glide.with(context)
.load(url)
.into(imageView)
5. 메모리 릭 탐지 도구 활용 (LeakCanary)
LeakCanary는 메모리 누수를 자동으로 탐지해주는 오픈소스 라이브러리인데,
프로젝트에 간단히 추가만 하면, 앱 내 메모리 누수를 로그로 바로 확인있다.
Gradle 설정:
debugImplementation "com.squareup.leakcanary:leakcanary-android:2.12"
6. 불필요한 객체 생성 줄이기
반복문 안에서 객체를 계속 생성하거나, Bitmap을 자주 생성하는 건 메모리를 빠르게 잡아먹는다. 이럴 떈 가능하면 객체 재사용을 고려해야한다. 메모리 낭비뿐 아니라, GC(Garbage Collection)가 자주 일어나서 앱이 끊기는 원인이 되기도 하기 때문이다.
❌ 안 좋은 예: 매번 새로 객체 생성
fun getFormattedDate(): String {
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
return sdf.format(Date())
}
이렇게 하면 SimpleDateFormat 객체가 호출될 때마다 새로 만들어지기 때문에, 반복 호출 시 메모리 낭비가 발생한다.
✅ 좋은 예: 객체 재사용
private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
fun getFormattedDate(): String {
return dateFormat.format(Date())
}
한 번 생성한 객체를 재사용하면 메모리 낭비를 줄이고 성능도 좋아진다.
❌ 리스트 초기화 반복
fun addItems(): List<String> {
val list = ArrayList<String>()
list.add("A")
list.add("B")
return list
}
이렇게 매번 리스트를 새로 생성하지 말고,
✅ 재사용 고려
private val cachedList = listOf("A", "B")
fun getItems() = cachedList
작은 객체라도 반복적으로 생성되면 전체 앱 퍼포먼스에 영향을 줄 수 있어서, 이런 부분을 챙기는 게 중요하다.
7. StrictMode로 디버깅하기
개발 중에 StrictMode를 설정해두면, UI 스레드에서의 잘못된 작업이나 메모리 누수 의심 코드를 미리 알려준다.
예:
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedClosableObjects()
.penaltyLog()
.build()
)
8. 마무리
앱 기능에는 지장이 없다고 종종 간과되곤 하지만, 메모리 최적화는 "작동은 하지만 어딘가 무거운 앱"을 "빠릿하고 안정적인 앱"으로 바꾸는 첫걸음이다. 작아 보이는 Context 참조 하나, 뷰 정리 하나가 앱 전체 성능에 큰 영향을 줄 수 있으니, 이러한 부분들을 염두에 두고 개발을 진행해야겠다.