Android ViewModel 사용 요약

20873 단어 AndroidViewModel
뷰 모델 관련 문 제 는 고주파 면접 문제 다.주로 MVVM 구조 모델 의 중요 한 구성 요소 이 고 설정 변경 으로 인해 페이지 가 삭제 되 고 재 구축 되 었 을 때 ViewModel 인 스 턴 스 를 유지 할 수 있 기 때 문 입 니 다.
뷰 모델 의 라 이 프 사이클 을 보 세 요.

뷰 모델 은 정상 적 인 액 티 비 티 피 니 쉬 때 만 삭 제 됩 니 다.
질문 이 왔 습 니 다.
  • 왜 Activity 가 화면 을 회전 한 후에 ViewModel 이 데 이 터 를 복원 할 수 있 습 니까
  • ViewModel 의 인 스 턴 스 캐 시 가 어디 에 있 습 니까?
  • 언제 ViewModel\#onCleared()가 호출 됩 니까?
  • 이 세 가지 문 제 를 해결 하기 전에 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;
        }
    
    아주 긴 주석,대략 몇 가지 의미 가 있 습 니 다.
  • onRetainNonConfigurationInstance 방법 과 getLastNonConfigurationInstance 는 쌍 을 이 루어 나타 나 며,onSaveInstanceState 체제 와 유사 하 며,설정 변경 만 처리 하 는 최적화 에 불과 합 니 다.
  • onRetainNonConfiguration Instance 가 돌아 온 대상 을 되 돌려 줍 니 다.
  • onRetainNonConfigurationInstance 와 getLast NonConfigurationInstance 의 호출 시 기 는 이 글 에서 군말 하지 않 고 후속 글 에서 설명 합 니 다.
    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()가 호출 되 어 해 결 됩 니까?
    이상 은 안 드 로 이 드 뷰 모델 의 사용 요약 에 대한 상세 한 내용 입 니 다.안 드 로 이 드 뷰 모델 의 사용 에 관 한 자 료 는 다른 관련 글 을 주목 하 세 요!

    좋은 웹페이지 즐겨찾기