안드로이드 어플리케이션에서 액티비티가 종료되거나 구성 변경 등이 일어나도 사용자는 자신의 UI 상태가 유지되기를 기대한다. (입력 값, 버튼 클릭 시의 색상 변경 유지 등) 이를 제공하기 위해서 개발자는 UI의 상태를 저장하고 복구해야하는데 이를 위해 다양한 방법이 쓰인다.
보통 UI의 상태를 저장하는 이유는 UI가 종료됐기 때문일텐데, UI가 닫히는 상황부터 먼저 알아보자.
UI가 닫히는 상황
사용자가 시작한 UI 상태 닫기
- 최근 사용 화면에서 액티비티 스와이프하여 닫기
- 설정 화면에서 앱 종료 또는 강제 종료
- 기기 재부팅
- Activity.finish()로 종료
시스템에서 시작한 UI 상태 닫기
- 구성 변경
- 화면 회전
- 멀티 윈도우
- 다른 앱 사용 후 다시 컴백
UI의 상태 저장 도구
개발자는 다음과 같은 방법으로 UI의 상태를 저장할 수 있다.
- ViewModel
- Saved instance states
- Compose 에서는 rememberSaveable
- 일반 View에서는 onSaveInstanceState() API
- ViewModel에서는 SavedStateHandle
- 로컬 스토리지
각각을 비교해 보면 다음과 같다.
ViewModel | SavedInstanceState | 로컬 영구 스토리지 | |
저장소 위치 | 메모리 | 메모리 | 디스크 또는 네트워크 |
구성 변경 시에도 유지 | 예 | 예 | 예 |
시스템에서 시작된 프로세스 종료 시에도 유지 | 아니요 | 예 | 예 |
사용자의 완전한 활동 닫기/onFinish() 시에도 유지 | 아니요 | 아니요 | 예 |
데이터 제한 | 복잡한 객체도 괜찮지만 사용 가능한 메모리에 의해 공간이 제한됨 | 원시(primitive) 유형 및 문자열과 같은 단순하고 작은 객체만 해당 | 디스크 공간 또는 비용, 네트워크 리소스에서 검색하는 시간에 의해서만 제한됨 |
읽기/쓰기 시간 | 빠름(메모리 액세스만) | 느림(직렬화/역직렬화 필요) | 느림(디스크 액세스 / 네트워크 트랜잭션 필요) |
여기서 ViewModel은 양이 방대해 따로 포스팅해 자세히 알아보도록하자. 또, 로컬 영구 스토리지는 Room과 같은 내장 스토리지에 해당한다는 점을 밝히고 가볍게 넘어가고, 오늘은 SavedInstanceState를 주로 다루도록 한다.
SavedInstanceState
앞서 말했듯이 저장된 인스턴스 상태에는 다음과 같은 것들이 포함된다.
- Compose 에서는 rememberSaveable
- 일반 View에서는 onSaveInstanceState() API
- ViewModel에서는 SavedStateHandle
이들은 시스템이 액티비티 또는 프래그먼트와 같은 UI 컨트롤러를 폐기하고 나중에 다시 생성할 때 컨트롤러의 상태를 다시 로드하는 데 필요한 데이터를 저장한다.
인스턴스 상태
사용자가 뒤로가기 버튼을 누르거나 액티비티가 자체적으로 종료되어 활동이 소멸되면 해당 액티비티 인스턴스에 관한 시스템과 사용자의 개념이 모두 아예 사라진다. 이 경우, 액티비티의 소멸이 의도했던 바임으로 상관없지만, 시스템 제약 (예: 구성 변경 또는 메모리 부족)으로 인해 액티비티가 소멸되는 경우 실제 액티비티 인스턴스는 사라지더라도 시스템은 존재했다는 사실을 기억한다.
이경우, 사용자가 액티비티로 돌아가려고 하면 시스템은 소멸되기전 액티비티의 상태를 설명하는 데이터를 사용해서 복구하는데, 이 때의 데이터를 인스턴스 상태 (Instance State)라고 한다.
💡 이때 Instance State는 bundle 객체로 저장된 키-값의 쌍 모음이다.
위에 정리가 되어있지만 다시 한번 특징을 정리해보자
- 저장위치 : 메모리
- 유지 여부
- 구성 변경 : O
- 시스템에서 시작된 프로세스 종료 시 유지 : O
- 사용자가 시작한 프로세스 종료 시 유지 : X
- 소요 시간 : 느림
- bundle 객체이므로 직렬화 / 역직렬화 과정이 필요하기 때문
onSavedInstanceState의 활용
Instance State에 대해 알아보았으면, 이게 언제 저장되고 언제 복원되는 지에 대해 자세히 알아보자. 사실 안드로이드 개발을 시작만 했다면 우리는 Instance State를 마주친 적이 있다. 바로 액티비티 onCreate() 에서 사용된다.
Android Code Search에서 제공하는 플랫폼 내부 코드를 보면 액티비티가 생성될 때 시스템은 savedInstanceState가 비어있으면 최초 생성, 비어있지 않으면 이전에 소멸된 액티비티를 데이터를 복구한다.
/**
* The {@code savedInstanceState} {@link Bundle} passed into
* {@link Activity#onCreate(Bundle)} or
* {@link Activity#onCreate(Bundle, PersistableBundle)}.
*/
public Bundle savedInstanceState() {
if (mSavedInstanceState == null) {
return null;
}
return mSavedInstanceState.get();
}
그렇다면 추가적으로 InstanceState를 저장하려면 어떻게 할까 ? Bundle 객체이므로 다음과 같이 키-값 쌍을 추가하면 된다.
override fun onSaveInstanceState(outState: Bundle?) {
// Save the user's current game state.
outState?.run {
putInt(STATE_SCORE, currentScore)
putInt(STATE_LEVEL, currentLevel)
}
// Always call the superclass so it can save the view hierarchy state.
super.onSaveInstanceState(outState)
}
companion object {
val STATE_SCORE = "playerScore"
val STATE_LEVEL = "playerLevel"
}
그러고서 저장된 값을 액티비티 복구 시 불러오기 위해서 다음과 같이 불러와서 값으로 저장해 활용한다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
with(savedInstanceState) {
// Restore value of members from saved state.
currentScore = getInt(STATE_SCORE)
currentLevel = getInt(STATE_LEVEL)
}
} else {
// Probably initialize members with default values for a new instance.
}
// ...
}
이때, onCreate() 에서 상태를 복구하는 대신 onStart() 후 호출되는 onRestoreInstanceState() 를 구현하도록 해도 된다.
onRestoreInstanceState
액티비티의 생명주기 중 onStart() 후에 호출되는 것으로, 이 때 주의할 점은 시스템은 복원할 저장된 상태가 있는 경우에만만 onRestoreInstanceState()를 호출하므로 Bundle이 null인지 확인할 필요가 없다.
즉, 앞서 말했듯이 Instance State는 사용자가 이도한 종료 (뒤로가기 버튼, 액티비티 자체 종료) 시에는 저장되지 않는다.
메모리 부족으로 인한 소멸, 다크모드로의 변경 등 예기치 못한 상황으로 시스템에 의한 소멸이 일어났을 때만 저장이 된다.
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
// onRestoreInstanceState()의 슈퍼클래스 구현을 호출하여 기본 구현에서 뷰 계층 구조의 상태를 복원할 수 있도록 하자
super.onRestoreInstanceState(savedInstanceState)
// savedInstanceState 로부터 Restore 해온다
savedInstanceState?.run {
currentScore = getInt(STATE_SCORE)
currentLevel = getInt(STATE_LEVEL)
}
}
이와 같이 안드로이드 앱에서 UI 상태를 저장하는 방법은 다음과 같고, 각각의 특징과 상황에 따라 적절한 것을 판단해 선택하는 것이 필요함을 알 수 있다.
참고자료
활동 수명 주기 | Android Developers
활동은 사용자가 전화 걸기, 사진 찍기, 이메일 보내기 또는 지도 보기와 같은 작업을 하기 위해 상호작용할 수 있는 화면을 제공하는 애플리케이션 구성요소입니다. 각 활동에는 사용자 인터페
developer.android.com
UI 상태 저장 | Android Developers
구성 변경 시 UI 상태를 유지하는 방법을 알아봅니다.
developer.android.com
Pluu Dev - Android 상태 저장의 기본에서 Savedstate까지
[요약] Designing scalable Compose APIs (Google I/O '24) Posted on 23 Jun 2024 [정리] Compose 가이드 문서 ~ Performance Posted on 19 Jun 2024 [정리] Compose 가이드 문서 ~ 터치&입력 Posted on 15 Jun 2024 [정리] Compose 가이드 문서
pluu.github.io
'안드로이드 > 꺼진개념다시보기' 카테고리의 다른 글
[Android] 안드로이드 UI 어디까지 아는데 ? - (1) dp, 해상도와 DIP & 픽셀 단위의 이해 (0) | 2024.08.24 |
---|---|
[Android] viewModelScope, lifecycleScope과 repeatOnLifecycle (0) | 2024.07.10 |
[Android] Serializable, Parcelable (0) | 2024.07.07 |
[Android] Runnable을 UI Thread위에서 실행시키는 법 - view.post, asyncTask, runOnUiThread, handler.post (1) | 2024.06.04 |
[Android] Thread, Handler와 Looper (0) | 2024.05.06 |