Android ViewModel 사용 요약
뷰 모델 의 라 이 프 사이클 을 보 세 요.
뷰 모델 은 정상 적 인 액 티 비 티 피 니 쉬 때 만 삭 제 됩 니 다.
질문 이 왔 습 니 다.
기본 사용
MainRepository
class MainRepository {
suspend fun getNameList(): List<String> {
return withContext(Dispatchers.IO) {
listOf(" ", " ")
}
}
}
MainViewModel
class MainViewModel: ViewModel() {
private val nameList = MutableLiveData<List<String>>()
val nameListResult: LiveData<List<String>> = nameList
private val mainRepository = MainRepository()
fun getNames() {
viewModelScope.launch {
nameList.value = mainRepository.getNameList()
}
}
}
MainActivity
class MainActivity : AppCompatActivity() {
// ViewModel 1
// kotlin ViewModel
// implementation 'androidx.activity:activity-ktx:1.2.3'
// viewModels() ViewModel 2 ViewModel
private val mainViewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate (savedInstanceState)
setContentView(R.layout.activity_main)
// ViewModel 2
val mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
mainViewModel.nameListResult.observe(this, {
Log.i("MainActivity", "mainViewModel: nameListResult: $it")
Log.i("MainActivity", "MainActivity: ${this@MainActivity} mainViewModel: $mainViewModel mainViewModel.nameListResult: ${mainViewModel.nameListResult}")
})
mainViewModel.getNames()
}
}
테스트 단계:app 열기->로그 정상적으로 보기
18:03:02.575 : mainViewModel: nameListResult: [ , ]
18:03:02.575 : com.yqy.myapplication.MainActivity@7ffa77 mainViewModel: com.yqy.myapplication.MainViewModel@29c0057 mainViewModel.nameListResult: androidx.lifecycle.MutableLiveData@ed0d744
다음 테스트 단계:설정 변경 시스템 언어 열기->현재 app 이 있 는 작업 으로 전환 하고 로 그 를 봅 니 다.
18:03:59.622 : mainViewModel: nameListResult: [ , ]
18:03:59.622 : com.yqy.myapplication.MainActivity@49a4455 mainViewModel: com.yqy.myapplication.MainViewModel@29c0057 mainViewModel.nameListResult: androidx.lifecycle.MutableLiveData@ed0d744
신기 하 다MainActivity 가 재 구축 되 었 고 ViewModel 의 인 스 턴 스 는 변 하지 않 았 으 며 ViewModel 대상 의 LiveData 대상 인 스 턴 스 도 변 하지 않 았 습 니 다.이것 이 뷰 모델 의 특성 이다.ViewModel 이 나타 나 기 전에 Activity 는 onSaveInstanceState()방법 으로 저장 한 다음 onCreate()의 Bundle 에서 데 이 터 를 복원 할 수 있 습 니 다.그러나 이 방법 은 사용자 정보 목록 이나 비트 맵 과 같은 수량 이 많은 데이터 에 만 적합 하지 않 습 니 다.뷰 모델 의 등장 은 이 문 제 를 완벽 하 게 해결 했다.
뷰 모델 이 어떻게 만 들 어 졌 는 지 먼저 살 펴 보 겠 습 니 다.위의 인 스 턴 스 코드 를 통 해 최종 뷰 모델 을 만 드 는 방법 은?
val mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
신기 하 다MainActivity 가 재 구축 되 었 고 ViewModel 의 인 스 턴 스 는 변 하지 않 았 으 며 ViewModel 대상 의 LiveData 대상 인 스 턴 스 도 변 하지 않 았 습 니 다.이것 이 뷰 모델 의 특성 이다.ViewModel 이 나타 나 기 전에 Activity 는 onSaveInstanceState()방법 으로 저장 한 다음 onCreate()의 Bundle 에서 데 이 터 를 복원 할 수 있 습 니 다.그러나 이 방법 은 사용자 정보 목록 이나 비트 맵 과 같은 수량 이 많은 데이터 에 만 적합 하지 않 습 니 다.뷰 모델 의 등장 은 이 문 제 를 완벽 하 게 해결 했다.
뷰 모델 이 어떻게 만 들 어 졌 는 지 먼저 살 펴 보 겠 습 니 다.위의 인 스 턴 스 코드 를 통 해 최종 뷰 모델 을 만 드 는 방법 은?
val mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
ViewModelProvider 대상 을 만 들 고 this 인 자 를 입력 한 다음 ViewModelProvider\#get 방법 을 통 해 MainViewModel 의 class 형식 을 입력 하고 mainViewModel 인 스 턴 스 를 받 았 습 니 다.ViewModelProvider 의 구조 방법
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
// owner ViewModelStore
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
ViewModelProvider 구조 방법의 매개 변수 유형 은 ViewModelStoreOwner 입 니까?View Model Store Owner 가 뭐 예요?저희 가 분명히 들 어 온 메 인 액 티 비 티 대상 이 잖 아 요!MainActivity 의 부류 들 을 보 니
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
...
// ViewModelStoreOwner
ViewModelStoreOwner,
...{
private ViewModelStore mViewModelStore;
// ViewModelStoreOwner getViewModelStore()
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
ensureViewModelStore();
return mViewModelStore;
}
Component Activity 류 는 View ModelStore Owner 인 터 페 이 스 를 실현 했다.오~방금 문제 가 해결 됐어 요.방금 ViewModelProvider 구조 방법 에서 this(ViewModelStore,Factory)를 호출 하여 Component Activity\#getViewModelStore 에서 돌아 온 ViewModelStore 인 스 턴 스 를 보 내 고 ViewModelProvider 에 캐 시 합 니 다.
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
// ViewModelStore
mViewModelStore = store;
}
이어서 ViewModelProvider\#get 방법 으로 무엇 을 했 는 지 살 펴 보 겠 습 니 다.
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
View Model 의 Canonical Name 을 가 져 와 다른 get 방법 을 호출 하 였 습 니 다.
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
// mViewModelStore
ViewModel viewModel = mViewModelStore.get(key);
//
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
// ViewModel
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
// ViewModel
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create(modelClass);
}
// ViewModel mViewModelStore
mViewModelStore.put(key, viewModel);
// ViewModel
return (T) viewModel;
}
mView Model Store 가 뭐 예요?ViewModelProvider 의 구조 방법 을 통 해 mViewModelStore 는 사실 우리 Activity 의 mViewModelStore 대상 임 을 알 수 있 습 니 다.이것 은 Component Activity 에서 설명 되 었 습 니 다.put 방법 을 보 았 으 니 내부 에 맵 구 조 를 사 용 했 는 지 짐작 하기 어렵 지 않다.
public class ViewModelStore {
// , HashMap
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
// key ViewModel
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
지금까지 정상 적 인 상황 에서 ViewModel 의 생 성 절 차 를 다 보 았 습 니 다.아무런 문제 도 해결 되 지 않 은 것 같 습 니 다.간단하게 요약 하면 ViewModel 대상 에 Component Activity 의 mViewModelStore 대상 이 존재 합 니 다.두 번 째 문제 가 해결 되 었 습 니 다:ViewModel 의 인 스 턴 스 캐 시 는 어디 에 있 습 니까?사고방식 을 바 꾸 는 mView ModelStore 의 출현 빈도 가 이렇게 높 은 데 언제 만 들 어 졌 는 지 보지 않 겠 는가?
방금 ViewModelProvider 의 구조 방법 을 보 았 을 때 ViewModelStore 대상 을 가 져 왔 을 때 실제 MainActivity\#getViewModelStore()를 호출 했 고 getViewModelStore()는 MainActivity 의 부모 클래스 ComponentActivity 에서 이 루어 졌 던 것 을 기억 하 십 니까?
// ComponentActivity#getViewModelStore()
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
ensureViewModelStore();
return mViewModelStore;
}
mViewModelStore 대상 으로 돌아 가기 전에 ensureViewModelStore()를 호출 했 습 니 다.
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
mView ModelStore==null 이 getLast NonConfigurationInstance()를 호출 하여 NonConfigurationInstances 대상 nc 를 가 져 왔 을 때 nc!=null 시 mViewModelStore 를 nc.viewModelStore 로 할당 하고 최종 viewModelStore==null 시 ViewModelStore 인 스 턴 스 를 만 듭 니 다.이전에 만 든 view ModelStore 대상 이 NonConfiguration Instances 에 캐 시 되 어 있 음 을 어렵 지 않 게 발견 할 수 있 습 니 다.
// ComponentActivity
static final class NonConfigurationInstances {
Object custom;
//
ViewModelStore viewModelStore;
}
mView ModelStore==null 이 getLast NonConfigurationInstance()를 호출 하여 NonConfigurationInstances 대상 nc 를 가 져 왔 을 때 nc!=null 시 mViewModelStore 를 nc.viewModelStore 로 할당 하고 최종 viewModelStore==null 시 ViewModelStore 인 스 턴 스 를 만 듭 니 다.이전에 만 든 view ModelStore 대상 이 NonConfiguration Instances 에 캐 시 되 어 있 음 을 어렵 지 않 게 발견 할 수 있 습 니 다.
// ComponentActivity
static final class NonConfigurationInstances {
Object custom;
//
ViewModelStore viewModelStore;
}
NonConfigurationInstances 대상 은 getLast NonConfigurationInstance()를 통 해 가 져 옵 니 다.
// Activity#getLastNonConfigurationInstance
/**
* Retrieve the non-configuration instance data that was previously
* returned by {@link #onRetainNonConfigurationInstance()}. This will
* be available from the initial {@link #onCreate} and
* {@link #onStart} calls to the new instance, allowing you to extract
* any useful dynamic state from the previous instance.
*
* <p>Note that the data you retrieve here should <em>only</em> be used
* as an optimization for handling configuration changes. You should always
* be able to handle getting a null pointer back, and an activity must
* still be able to restore itself to its previous state (through the
* normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
* function returns null.
*
* <p><strong>Note:</strong> For most cases you should use the {@link Fragment} API
* {@link Fragment#setRetainInstance(boolean)} instead; this is also
* available on older platforms through the Android support libraries.
*
* @return the object previously returned by {@link #onRetainNonConfigurationInstance()}
*/
@Nullable
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
아주 긴 주석,대략 몇 가지 의미 가 있 습 니 다.onRetainNonConfiguration Instance 방법 을 보 세 요.
/**
*
*/
@Override
@Nullable
@SuppressWarnings("deprecation")
public final Object onRetainNonConfigurationInstance() {
// Maintain backward compatibility.
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
// viewModelStore , getLastNonConfigurationInstance()
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
// , , null
if (viewModelStore == null && custom == null) {
return null;
}
// NonConfigurationInstances , viewModelStore
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
여기까지 알 겠 습 니 다.Activity 는 설정 변경 으로 재 구축 을 취소 하 는 과정 에서 onRetainNonConfiguration Instance 를 사용 하여 view Model Store 인 스 턴 스 를 저장 합 니 다.재 구축 후 getLast NonConfigurationInstance 방법 으로 이전 view Model Store 인 스 턴 스 를 가 져 올 수 있 습 니 다.첫 번 째 문제 가 해결 되 었 습 니 다.왜 Activity 가 화면 을 회전 한 후에 ViewModel 이 데 이 터 를 복원 할 수 있 습 니까?
세 번 째 질문:언제 ViewModel\#onCleared()가 호출 됩 니까?
public abstract class ViewModel {
protected void onCleared() {
}
@MainThread
final void clear() {
mCleared = true;
// Since clear() is final, this method is still called on mock objects
// and in those cases, mBagOfTags is null. It'll always be empty though
// because setTagIfAbsent and getTag are not final so we can skip
// clearing it
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
onCleared();
}
}
onCleared()방법 이 clear()에 호출 되 었 습 니 다.방금 ViewModelStore 소스 코드 를 보 았 을 때 clear()를 호출 한 것 같 습 니 다.돌 이 켜 보 세 요.
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
onCleared()방법 이 clear()에 호출 되 었 습 니 다.방금 ViewModelStore 소스 코드 를 보 았 을 때 clear()를 호출 한 것 같 습 니 다.돌 이 켜 보 세 요.
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
ViewModelStore 의 clear()에서 mMap 을 옮 겨 다 니 며 ViewModel 대상 의 clear()를 호출 하고 ViewModelStore 의 clear()가 언제 호출 되 었 는 지 확인 합 니 다.
// ComponentActivity
public ComponentActivity() {
...
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
...
}
현재 activity 생명주기 관찰,Lifecycle.Event==Lifecycle.Event.ONDESTROY,그리고 isChangingConfigurations()가 false 로 돌아 갈 때 만 ViewModelStore\#clear 를 호출 합 니 다.
// Activity#isChangingConfigurations()
/**
* Check to see whether this activity is in the process of being destroyed in order to be
* recreated with a new configuration. This is often used in
* {@link #onStop} to determine whether the state needs to be cleaned up or will be passed
* on to the next instance of the activity via {@link #onRetainNonConfigurationInstance()}.
*
* @return If the activity is being torn down in order to be recreated with a new configuration,
* returns true; else returns false.
*/
public boolean isChangingConfigurations() {
return mChangingConfigurations;
}
isChangingConfigurations 는 현재 Activity 가 Configuration 의 변화 로 인해 소각 되 었 는 지 확인 하 는 데 사 용 됩 니 다.설정 변경 은 true 로 돌아 가 고 설정 변경 이 아 닌 false 로 돌아 갑 니 다.결론 적 으로 activity 소각 시 비 설정 변경 으로 인 한 소각 이 라면 getView ModelStore().clear()가 호출 될 것 이 라 고 판단 합 니 다.
세 번 째 문제:언제 ViewModel\#onCleared()가 호출 되 어 해 결 됩 니까?
이상 은 안 드 로 이 드 뷰 모델 의 사용 요약 에 대한 상세 한 내용 입 니 다.안 드 로 이 드 뷰 모델 의 사용 에 관 한 자 료 는 다른 관련 글 을 주목 하 세 요!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Bitrise에서 배포 어플리케이션 설정 테스트하기이 글은 Bitrise 광고 달력의 23일째 글입니다. 자체 또는 당사 등에서 Bitrise 구축 서비스를 사용합니다. 그나저나 며칠 전 Bitrise User Group Meetup #3에서 아래 슬라이드를 발표했...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.