250706
안드로이드의 각 컴포넌트들은 생성되고, 사용자와 상호작용하고, 종료되기까지의 일련의 상태 변화 단계를 거친다. 이를 생명주기라고 하며, 시스템은 이 생명주기의 각 단계에 따라 정해진 콜백 메서드를 호출한다. 개발자는 이 콜백 내에서 상태에 맞는 동작을 정의해줘야 하는데, 그 이유는 아래와 같다.
(1) 크래시 방지
사용자가 앱을 사용하는 도중 전화가 오는 등 외부 이벤트에 이해 다른 앱으로 전환되게 되면 시스템은 상황에 따라 콜백을 호출해 강제로 프로세스를 종료할 수도 있다. 만약 이때 중요한 작업이 처리중이거나, 열려있던 리소스를 제대로 닫아주지 않으면 크래시가 발생할 수 있다.
(2) 리소스 낭비 방지
사용자가 앱을 나갔는데도 네트워크 연결이 계속 유지된다거나 GPS, 센서, 애니메이션 등이 계속 동작한다면 배터리와 같은 시스템 자원이 당비된다. 특히 사용자가 앱을 보고 있지 않은 상황에서 이러한 리소스 낭비는 치명적이다.
(3) 사용자 경험 손실 방지
앱을 사용하다가 다른 앱으로 전환하고 다시 돌아왔을 때, 처음 화면부터 시작하거나 이전의 상태를 기억하지 못한다면 사용자는 불편함을 느끼게 된다.
(4) 화면 회전 시 상태 유지
안드로이드는 기본적으로 화면을 회전하는 경우 액티비티를 재시작한다. 이 경우 입력하던 텍스트, 보고 있던 영상 재생 위치 등이 초기화될 수 있다.
그럼 구체적으로 어떤 콜백 함수들이 있으며, 어떤 상황에서 호출되는지 더 자세히 알아보자.
Activity Lifecycle
onCreate()
: 액티비티 생성 시 호출, 액티비티 전체 생명주기에서 단 한 번만 발생해야 하는 초기화 로직은 대부분 여기서 처리- action: setContentView() 호출, ViewBinding 초기화, 초기 데이터 로딩
onStart()
: 액티비티가 사용자에게 보여지기 시작할 때 호출- action: 애니메이션 재시작
onResume()
: 화면이 foreground로 노출되어 완전히 활성화된 상태, 사용자와 상호작용 가능- action: 사용자 입력 허용, 카메라 미리보기 시작
onPause()
: 화면이 포커스를 잃은 상태, 부분적으로 덮여있는 경우에도 여기에 해당- action: 카메라 미리보기 중단, 애니메이션 일시 정지, 데이터 임시 저장
onStop()
: 액티비티가 완전히 보이지 않는 상태- action: 무거운 리소스(DB 연결 등) 해제, 백그라운드 작업만 유지
onRestart()
: onStop()후 다시 시작될 때, onStart() 직전에 호출, UI 복구 단계- action: 중단되었던 UI 요소 복원(받은 메세지 목록 새로고침 등)
onDestroy()
: 액티비티가 종료되거나 화면 전환으로 인해 시스템이 액티비티를 파괴, 남은 리소스를 해제하고 GC가 할당된 모든 메모리를 회수함- action: ViewBinding 해제, 백그라운드 Thread 강제 종료, 메모리 해제, 캐시 삭제
여기서 onCreate
와 onDestroy()
는 전체 생명주기 중 보통 한 번씩만 호출된다. (화면 회전 등으로 화면 재생성시는 예외)
그럼 이런 것도 생각해볼 수 있겠다. 앱을 백 버튼을 눌러 종료하는 것과 달리, 홈 버튼을 눌러 종료하면 백그라운드에 살아있는 경우, 액티비티의 생명주기는 어떻게 될까?
백 버튼을 눌러 완전히 종료 시킬 시 onPause()
→ onStop()
→ onDestroy()
가 차례로 호출된다면, 홈 버튼을 누를 경우에는 onPause()
→ onStop()
까지만 호출되고 onDestroy()
가 호출되지 않는다. 즉, 홈 버튼을 누를 경우에는 앱의 리소스가 모두 메모리에 그대로 남아있는 상태가 된다.
Fragment Lifecycle
액티비티는 시스템이 직접 관리하는 독립적인 컴포넌트로, window, view hierarchy, lifecycle, back stack 등 화면을 구성하는 모든 요소를 자체적으로 포함한다. 따라서 새로운 액티비티를 실행하면 새로운 Window 객체가 생성되고, 전체 생명주기 콜백이 호출되며, 그만큼 메모리와 CPU 자원이 많이 소모된다.
반면 하나의 mainActivity 내에서 여러 화면을 모두 fragment로 구성할 경우, UI를 더 유연하게 조합할 수 있을 뿐만 아니라 리소스 부담도 줄일 수 있다. 프래그먼트는 액티비티 내부에서 작동하는 UI 모듈로, 별도의 window를 생성하지 않으며, 생명주기 또한 FragmentManager
를 통해 액티비티 안에서 통제된다. 액티비티는 그대로 유지된 채 프래그먼트만 교체할 수 있어, 전체 구조를 다시 로딩할 필요 없이 가볍고 빠른 화면 전환이 가능하다.
또한 프래그먼트는 Intent 없이도 액티비티나 다른 프래그먼트와 데이터를 주고받을 수 있으며, 트랜잭션을 통해 자체적으로 백스택을 관리함으로써 보다 자연스럽고 일관된 화면 흐름을 구성할 수 있다.
이때 프래그먼트는 액티비티 안에서 동작하지만, 액티비티와는 별개의 독립적인 생명주기를 가지고 있기 때문에 프래그먼트의 생명주기 역시 신중하게 관리할 필요가 있다.
아래에서 프래그먼트의 각 생명주기 단계와 그에 따른 역할에 대해 자세히 알아보자.
onAttach()
: 프래그먼트가 액티비티에 붙을 때 호출- action: Context 관련 초기 작업, DI 주입
onCreate()
: 프래그먼트가 생성되어 FragmentManager에 추가되었음을 의미, 프래그먼트 인스턴스 초기화 단계- action: ViewModel 준비, 비 UI 로직 초기화
onCreateView()
: UI 레이아웃 생성시 호출, XML 레이아웃을 View 객체로 바꿔서 유효한 인스턴스를 반환- action: LayoutInflater로 XML 레이아웃 inflate(XML로 적어둔 레이아웃을 실제로 메모리에 올려서 View 객체로 만드는 과정)
onViewCreated()
: 뷰가 완전히 만들어진 이후에 실행, inflate 이후이므로 UI 관련 작업 가능- action: ViewBinding 연결, 클릭 리스너 설정
onStart()
: 프래그먼트가 사용자에게 보여지기 시작할 때 호출- action: 애니메이션, 센서 리스너 등록
onResume()
: 화면이 foreground로 노출되어 완전히 활성화된 상태, 사용자와 상호작용 가능- action: 사용자 입력 허용, 카메라 미리보기 시작
onPause()
: 화면이 포커스를 잃은 상태, 부분적으로 덮여있는 경우에도 여기에 해당- action: 카메라 미리보기 중단, 애니메이션 일시 정지, 데이터 임시 저장
onStop()
: 프래그먼트가 완전히 보이지 않는 상태- action: 무거운 리소스(DB 연결 등) 해제, 백그라운드 작업만 유지
onDestroyView()
: View가 메모리에서 해제될 때 호출- action: ViewBinding 해제
onDestroy()
: 프래그먼트 인스턴스 자체가 삭제될 때 호출- action: 비 UI 리소스 정리
onDetach()
: 프래그먼트가 액티비티에서 완전히 분리될 때 호출- action: Context 관련 객체 정리
전체적으로 보면, 프래그먼트는 항상 액티비티의 수명주기를 따르나, ‘View 수명주기는 프래그먼트 내부적으로 따로 관리’하는 것을 알 수 있다.
Transaction
액티비티를 포함한 안드로이드 앱의 컴포넌트들은 Intent를 통해 활성화된다. 이 말은 즉, 액티비티간의 화면 전환에도 인텐트가 필요하다는 말이다.
그러나 앞서 언급한 것 처럼, 프래그먼트는 트랜잭션을 통해서 화면에 추가하거나 교체하는 방식으로 화면 전환이 이뤄진다.
화면 전환은 항상
beginTransaction()
→add()
/replace()
/remove()
→commit()
흐름을 따른다.
코드로 보면 아래와 같다.
supportFragmentManager.beginTransaction()
.replace(R.id.container, NewFragment())
.addToBackStack(null) // 백버튼으로 복귀 가능하게 - 없으면 백버튼 누를 시 바로 앱 종료
.commit()
Navigation
그런데 이렇게 트랜잭션을 일일이 코드로 다 적어주다 보면 백스택 관리 코드가 중복되고 복잡해진다. 이를 해결하고자 Jetpack Navigation이 지원된다.
Jetpack Navigation Component: Navigation을 “그래프” 기반으로 선언형으로 관리하게 해주는 Jetpack 라이브러리
간단한 사용 흐름은 아래와 같다.
nav_graph.xml
만들기 → Fragment 간 이동 정의NavHostFragment
배치 → Navigation의 중심NavController
로 화면 이동 제어Safe Args
로 데이터 전달
기존에는 수동으로 FragmentTransaction과 Intent를 사용했다면, Navigation Component를 사용하면 화면 흐름을 그래프 형태로 선언하고, 백스택도 자동으로 처리되어 코드가 더 단순하고 안정적으로 유지할 수 있게 된다.