UI 상태를 유지하기 위한 옵션
UI 상태에 관한 사용자 기대치가 기본 시스템 동작과 일치하지 않으면 사용자의 UI 상태를 저장하고 복원하여 시스템에서 시작된 폐기를 사용자가 인식할 수 있도록 해야 합니다.
UI 상태를 유지하기 위한 각각의 옵션은 사용자 환경에 영향을 주는 다음과 같은 측정기준에 따라 다릅니다.
| 속성들 | ViewModel | 저장된 인스턴스 상태 | 영구 저장소 |
|---|---|---|---|
| 저장소 위치 | 메모리 내 | 디스크에 직렬화 | 디스크 또는 네트워크 내 |
| 구성 변경 시에도 유지 | 예 | 예 | 예 |
| 시스템에서 시작된 프로세스 중단 시에도 유지 | 아니요 | 예 | 예 |
| 사용자의 완전한 활동 닫기/onFinish() 시에도 유지 | 아니요 | 아니요 | 예 |
| 데이터 제한 | 복잡한 객체도 괜찮지만 사용 가능한 메모리에 의해 공간이 제한됨 | 원시(primitive) 유형 및 문자열과 같은 단순하고 작은 객체만 해당 | 디스크 공간 또는 네트워크 리소스에서 검색하는 비용/시간에 의해서만 제한됨 |
| 읽기/쓰기 시간 | 빠름(메모리 액세스만) | 느림(직렬화/역직렬화 및 디스크 액세스 필요) | 느림(디스크 액세스 또는 네트워크 트랜잭션 필요) |
Fragment 1.1.0 또는 전이 종속 항목인 Activity 1.0.0부터, UI 컨트롤러(Activity 또는 Fragment)가 SavedStateRegistryOwner를 구현하고 컨트롤러에 결합되는 SavedStateRegistry를 제공
SavedStateRegistry를 사용하면, 저장된 UI 컨트롤러의 상태와 구성요소가 연결되어 소비하거나 기여할 수 있음
= 저장된 ViewModel의 상태 모듈은 SavedStateRegistry를 사용하여 SavedStateHandle를 만들어 ViewModel 객체에 제공
= getSavedStateRegistry()를 호출하여 UI 컨트롤러 내에서 SavedStateRegistry를 검색할 수 있음
저장된 상태에 기여하는 구성요소는 saveState()라는 단일 메서드를 정의하는 SavedStateRegistry.SavedStateProvider를 구현해야 함
saveState() 메서드를 통해 구성요소가 자체에서 저장되어야 하는 상태가 포함된 Bundle을 반환할 수 있음.
SavedStateRegistry는 UI 컨트롤러의 수명 주기의 상태 저장 단계에서 이 메서드를 호출합니다
SavedStateProvider를 등록하려면 SavedStateRegistry에서 registerSavedStateProvider()를 호출하여, 제공자의 데이터뿐만 아니라 제공자 자체와도 연결할 키를 전달
이전에 저장된 제공자 데이터는 SavedStateRegistry에서 consumeRestoredStateForKey()를 호출한 후, 제공자의 데이터와 연결된 키를 전달하는 방법을 통해 저장된 상태에서 검색할 수 있음
Activity 또는 Fragment 내에서 super.onCreate()를 호출한 후에 onCreate()에 SavedStateProvider를 등록할 수 있음
또는 SavedStateRegistryOwner에서 LifecycleObserver를 설정하여 LifecycleOwner를 구현하고 ON_CREATE 이벤트 발생 시 SavedStateProvider를 등록할 수 있음
LifecycleObserver를 사용하면 SavedStateRegistryOwner 자체에서 이전에 저장된 상태의 등록 및 검색을 분리할 수 있음
class SearchManager : SavedStateRegistry.SavedStateProvider {
companion object {
private const val QUERY = "query"
}
private val query: String? = null
...
override fun saveState(): Bundle {
return bundleOf(QUERY to query)
}
}
class SearchManager(registryOwner: SavedStateRegistryOwner) : SavedStateRegistry.SavedStateProvider {
companion object {
private const val PROVIDER = "search_manager"
private const val QUERY = "query"
}
private val query: String? = null
init {
// Register a LifecycleObserver for when the Lifecycle hits ON_CREATE
registryOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_CREATE) {
val registry = registryOwner.savedStateRegistry
// Register this object for future calls to saveState()
registry.registerSavedStateProvider(PROVIDER, this)
// Get the previously saved state and restore it
val state = registry.consumeRestoredStateForKey(PROVIDER)
// Apply the previously saved state
query = state?.getString(QUERY)
}
}
}
override fun saveState(): Bundle {
return bundleOf(QUERY to query)
}
...
}
class SearchFragment : Fragment() {
private var searchManager = SearchManager(this)
...
}
참고: SavedStateRegistry는 onSaveInstanceState()와 동일한 Bundle에 데이터를 저장하므로, 동일한 고려사항과 데이터 제한이 적용됩니다.
영구 로컬 저장소(데이터베이스 또는 공유 환경설정)는 애플리케이션이 사용자의 기기에 설치되어 있는 동안(사용자가 앱의 데이터를 삭제하지 않는 한) 유지
이러한 로컬 저장소는 시스템에서 시작된 활동 및 애플리케이션 프로세스 중단 시에도 유지되지만 검색하는 데 비용이 많이 듦
= 데이터를 로컬 저장소에서 메모리로 읽어와야 하기 때문
활동을 열고 닫을 때 손실하지 않으려는 모든 데이터를 저장하기 위해, 대개 영구 로컬 저장소는 이미 애플리케이션 아키텍처의 일부일 수 있습니다.
ViewModel이나 저장된 인스턴스 상태는, 장기 저장소 솔루션이 아니므로 데이터베이스와 같은 로컬 저장소를 대체하지 못합니다.
= 일시적인 UI 상태를 임시 저장하는 데에만 이러한 메커니즘을 사용하고 다른 앱 데이터에는 영구 저장소를 사용해야
로컬 저장소를 활용하여 앱 모델 데이터를 장기적으로(예: 기기 재시작 전체에 걸쳐) 유지하는 방법에 관한 자세한 내용은 앱 아키텍처 가이드를 참고
UI 상태를 효율적으로 저장 및 복원하기 위해 - 데이터 복잡도, 액세스 속도 및 전체 기간의 균형에 따라 사용되는 다양한 유형의 데이터를 저장 등을 고려하여 아래 방식을 섞어 사용
사용자가 노래를 추가하면 ViewModel은 즉시 이 데이터가 로컬에 유지되도록 위임
새로 추가된 이 노래가 UI에 표시되어야 하는 경우 노래가 추가되었음을 반영하도록 ViewModel 객체의 데이터도 업데이트
이때 기본 스레드에서 모든 데이터베이스 삽입 작업을 진행해야 합니다.
사용자가 노래를 검색할 때 데이터베이스에서 로드되어 UI 컨트롤러에 표시될 노래 데이터는 아무리 복잡하더라도 즉시 ViewModel 객체에 저장되어야 함
또한 검색어 자체는 ViewModel 객체에 저장되어야 합니다.
Activity가 백그라운드로 전환되면 시스템은 onSaveInstanceState()를 호출
검색어는 onSaveInstanceState() 번들에 저장
이 소량의 데이터는 저장하기 쉽습니다. 그리고 이 데이터는 활동을 현재 상태로 되돌리는 데 필요한 모든 정보이기도 합니다.
사용자가 활동으로 돌아갈 때 활동이 다시 생성되는 시나리오에는 두 가지
활동이 시스템에 의해 중지된 후 다시 생성
= 활동의 쿼리가 onSaveInstanceState() 번들에 저장되어 있으며 이 쿼리를 ViewModel에 전달
= ViewModel은 캐시된 검색결과가 없음을 확인한 후, 주어진 검색어를 사용하여 검색결과 로드를 위임
구성 변경 후 활동이 생성
= 활동의 쿼리가 onSaveInstanceState() 번들에 저장되어 있으며 ViewModel에 이미 캐시된 검색결과가 저장되어 있음
= onSaveInstanceState() 번들에서 ViewModel로 쿼리를 전달하면 ViewModel은 필요한 데이터를 이미 로드했으며 데이터베이스를 다시 쿼리할 필요가 없음을 파악
참고: 활동이 처음 생성되면 onSaveInstanceState() 번들에는 데이터가 없으며 ViewModel 객체는 비어 있습니다. ViewModel 객체를 만들 때 빈 쿼리를 전달함으로써 로드할 데이터가 아직 없음을 ViewModel 객체에 알려줄 수 있습니다. 그러면 활동은 빈 상태에서 시작됩니다.