안드로이드는 장치 회전에 따른 액티비티 소멸 및 재생성에 문제가 생길 수 있다.
이런 경우 해결하는 방법을 알아보자
화면의 회전이 발생한 경우 UI상태가 초기화되면 안된다. 이런 경우 ViewModel을 사용한다.
ViewModel 인스턴스는 액티비티 생명주기와 연동된다.
ViewModel 인스턴스는 액티비티 상태변화와 무관하게 액티비티가 종료될 때까지 메모리에 남아 있다가 액티비티가 종료되면 소멸된다.
Activity.onDestroy()
가 호출된 경우 Activity.isFinishing
속성으로 이 값이 false 인 경우 viewModel이 메모리에 남아있고 true인 경우 viewModel이 소멸된다.
장치 회전 등 구성 변경이 생길 때
액티비티 인스턴스는 소멸되고 새로운 인스턴스가 생성되지만
연관된 viewModel은 메모리에 남아 여전히 사용됨.
만약 viewModel이 액티비티나 다른 뷰의 참조를 가지면(강한 참조) 메모리 유실이 발생한다.
why?
장치 회전 시 액티비티 인스턴스는 소멸되지만, ViewModel 인스턴스는 메모리에 남는다. 이 때 viewModel이 액티비티 인스턴스에 대한 강한 참조를 가지면 2가지 문제가 발생한다.
1. 액티비티 인스턴스가 메모리에서 제거되지 않아 이 인스턴스가 사용하는 메모리 유실
2. ViewModel 인스턴스가 현재 사용되지 않는 과거 액티비티의 참조를 가져 ViewModel 인스턴스가 과거 액티비티의 뷰를 변경하려 하면 IllegalStateException
발생
안드로이드 OS가 앱의 프로세스를 소멸시킬 때는 메모리에 있는 앱의 모든 액티비티들과 ViewModel들이 제거되지만, 액티비티나 ViewModel의 어떤 생명주기 콜백 함수도 호출하지 않는다.
그렇다면, 액티비티가 소멸될 때 UI 상태 데이터를 보존해 액티비티의 재구성에 사용할 수 있는 방법은?
SIS(Saved Instance State)에 데이터를 저장하는 방법이 있다.
onSaveInstanceState(Bundle)
을 오버라이드해 SIS에 데이터 추가 가능중단
상태로 바뀔 때 언제든지 안드로이드 OS가 onSaveInstanceState(Bundle)
호출 /*
* Activity
*/
private const val KEY_INDEX = "index"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate(Bundle?) called")
setContentView(R.layout.activity_main)
val currentIndex = savedInstanceState?.getInt(KEY_INDEX, 0) ?: 0
quizViewModel.currentIndex = currentIndex
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Log.d(TAG, "onSaveInstanceState")
outState.putInt(KEY_INDEX, quizViewModel.currentIndex)
}
onSaveInstanceState
를 오버라이드해 저장할 값을 저장한다.
onCreate
에서 Bundle
객체에서 저장한 값을 얻어와 사용한다.
ViewModel
과 SIS
모두 장치 회전이 발생한 경우 즉..
액티비티의 인스턴스가 소멸된 경우 데이터를 유지해 UI 그대로 다시 표시할 수 있다.
무엇을 써야할까?
SIS
에는 크거나 복잡한 객체를 저장하는 것은 피해야한다. -> 소량의 정보 저장
ViewModel
에는 많은 데이터에 빠르고 쉽게 접근하고자 메모리에 캐싱할 때 사용
그렇다면 장기간 저장해야하는 데이터의 경우 무엇을 사용해야할까?
이는 ViewModel
과 SIS
가 아닌 DB
혹 Shared preference
에 저장해야한다.