이 글은 깡쌤의 안드로이드 프로그래밍 책을 보고 작성하였습니다.
안드로이드 앱은 '앱티비티', '서비스', '콘텐츠 프로바이더', '브로드캐스트 리시버' 등 4개의 컴포넌트 조합으로 개발합니다. 이 중 이용빈도가 가장 높고 생명주기가 가장 복잡한 컴포넌트인 액티비티에 대해 알아보겠습니다
액티비티는 실행부터 종료까지 많은 상태 변화를 거치며 상태가 변할 때마다 생명주기 함수가 자동으로 호출됩니다. 액티비티의 상태는 '활성 상태', '일시 정지 상태', '비활성 상태'가 있습니다.
액티비티가 사용자 화면에 보이고 있으며 포커스를 가지고 있어서 사용자 이벤트에 반응할 수 있는 상태입니다. 생성된 액티비티는 onCreate() -> onStart() -> onResume() 함수가 호출되면서 활성상태가 됩니다
일반적으로 setContentView() 함수를 이용하여 액티비티 화면을 출력하는데, setContentView() 함수가 호출되는 시점이 화면 출력 순간이 아니라, onResume() 함수까지 실행하고 그 후 setContentView() 함수에서 출력한 내용이 화면에 나오는 구조이다. 따라서 onResume()이 함수가 실행되기까지 onCreate(), onStart(), onResume() 안에서 setContentView()를 호출하면 화면에 레이아웃이 출력됩니다.
일시정시 상태는 액티비티가 여전히 화면에 보이지만, 포커스를 잃은 상태입니다. 대표적인 예가 다른 액티비티가 반투명하게 실행되거나 다이얼로그 스타일로 실행되어 여전히 자신이 화면에 보이지만, 포커스를 잃은 상태입니다. 하지만 일시 정지 상태로 멈출 때도 있지만, 대부분 정지 상태(onStop)로 전환되기 전에 호출되어 곧 정지될 것임을 나타내기 위해 사용됩니다. 일시 정시 상태가 되면 onPause() 함수가 자동으로 호출됩니다.
onPause() 함수에서는 대부분 다음의 내용을 구현하기를 권장합니다
이러한 정지를 onPause() 함수에서 구현하는 이유는 위와 같은 작업은 정지 상태에서 더 이상 실행될 필요가 없는 경우이므로 onPause() 함수에서 멈추었다가 다시 활성 상태로 변경될 때 동작하게 합니다.
비활성 상태는 다른 액티비티로 인해 화면이 완전히 가려진 상태입니다. 예로는 다른 액티비티로 화면이 전환되어 안 보이는 경우입니다. 이렇게 되면 onPause() -> onStop() 함수까지 호출됩니다. 이후 화면을 가렸던 액티비티에서 뒤로가기 버튼등으로 화면을 가렸던 액티비티가 사라지면 다시 활성상태로 전환되는데, 이때는 onRestart() -> onStart() -> onResume() 함수가 차례로 호출됩니다.
MainActivity | SecondActivity | Back to MainActivity |
---|---|---|
앱이 종료될 경우의 데이터 저장은 데이터베이스와 같은 데이터 영속화를 사용하면 됩니다. 하지만 앱이 아닌 액티비티가 의도치 않게 종료될 수가 있습니다. 대표적인 예가 화면 회전입니다. 화면 회전을 하게되면 액티비티가 종료되었다가 다시 시작합니다. 이때, 생명주기는 onResume()까지 호출 된 액티비티에서 화면 회전이 발생하면 onPause() -> onStop() -> onDestroy()까지 호출되어 종료됩니다. 그 후 시스템에서 다시 onCreate() -> onStart() -> onResume()까지 호출합니다. 화면은 다시 나타나지만 만약 onResume()이 실행된 후에 발생한 이벤트나 네트워킹에 의해 데이터가 발생하였다면 모두 유실됩니다.
화면 회전 전 | 화면 회전 이후 |
---|---|
이를 위해 데이터를 저장했다가 다시 액티비티가 시작될 때 복원하여 사용하기 위한 생명주기 함수들이 있습니다.
// 액티비티 종료되는 상황에 액티비티의 데이터를 저장
// Bundle은 컴포넡트의 데이터를 저장하기 위한 일종의 Map 객체
// Key - Value 형식으로 데잉터를 저장
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("data1", "hello")
outState.putInt("data2", 100)
}
우선 onSaveInstanceState 입니다. 이 함수는 onPause() 함수 호출 후 자동으로 호출되며, 이 함수에서 데이터를 저장합니다. 저장하는 방법은 매개변수로 전달되는 Bundle 객체를 사용하면 됩니다. Bundle은 컴포넌트의 데이터를 저장하기 위한 일종의 Map 객체로, 데이터를 Key - Value 형식으로 담아주면 됩니다.
// 액티비티가 다시 시작하면서 호출되는데 매개변수로 Bundle 객체가 넘어온다.
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
// Bundle 객체를 사용하여 해당하는 값 가져오기
val data1 = savedInstanceState.getString("data1")
val data2 = savedInstanceState.getInt("data2")
val toast = Toast.makeText(this, "$data1 : $data2", Toast.LENGTH_SHORT)
toast.show()
}
// Bundle 객체가 매개변수로 넘어온다
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val data1 = savedInstanceState?.getString("data1")
val data2 = savedInstanceState?.getInt("data2")
val toast = Toast.makeText(this, "$data1 : $data2", Toast.LENGTH_SHORT)
toast.show()
}
액티비티가 다시 시작하면서 onCreate()와 onResotreInstanceState()가 호출되는데, 저장된 데이터를 Bundle에 담아 매개변수로 전달합니다. 이 함수들을 사용하여 저장된 값을 불러옵니다.
override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
super.onSaveInstanceState(outState, outPersistentState)
}
override fun onRestoreInstanceState(
savedInstanceState: Bundle?,
persistentState: PersistableBundle?
) {
super.onRestoreInstanceState(savedInstanceState, persistentState)
}
매개변수가 하나인 onSaveInstanceState() 함수는 onPause() 함수가 호출된 후 자동으로 호출됩니다. 그런데 화면이 회전될 때가 아닌 다른 액티비티로 화면이 전활될 때도 호출됩니다. 하지만 매개변수가 두 개인 onSaveInstanceState() 함수는 onPuase() 함수가 호출 후 무조건 호출되지 않고 화면 회전처럼 액티비티가 종료할 때만 호출됩니다.
또한, 매개변수가 하나인 onRestoreInstanceState() 함수는 화면이 회전하여 액티비티가 다시 시작될 때 무조건 호출되지만, 매개변수가 두 개인 onRestoreInstanceState() 함수는 Bundle에 저장된 데이터가 없으면 호출되지 않습니다.