美文网首页Android
ViewModel 使用及原理

ViewModel 使用及原理

作者: swz_android | 来源:发表于2022-09-08 17:31 被阅读0次

    ViewModel 类旨在以注重生命周期的方式存储和管理界面相关数据。ViewModel 类可让数据在发生屏幕旋转等配置更改后继续留存,还可以避免异步请求后操作数据造成的内存泄漏

    ViewModel 的使用

    首先,引入 ViewModel:

     def lifecycle_version = "2.4.0-alpha02"
     // ViewModel
     implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    
    

    然后定义一个数据类

    public class UserInfo {
    
        private String firstName;
        private String lastName;
        private int age;
    
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    

    接着定义一个类,继承 ViewModel,并包含 UserInfo 属性

    public class ViewModelInfo extends ViewModel {
    
        // 这个是 LiveData,用来监听数据改变的,一般会习惯把 LiveData 和 ViewModel 绑定使用,LiveData 我们后续再说
        private MutableLiveData<UserInfo> user;
    
        public MutableLiveData<UserInfo> getUser() {
            if (user == null) {
                user = new MutableLiveData<UserInfo>();
                loadUser();
            }
            return user;
        }
    
        private void loadUser() {
    
            UserInfo userInfo = new UserInfo();
            userInfo.setFirstName("firstName");
            userInfo.setLastName("lastName");
            user.setValue(userInfo);
        }
    
    }
    

    接下来我们就能在 Activity 中使用它了

    //获取ViewModel对象
    ViewModulInfo viewModulInfo = new ViewModelProvider(this).get(ViewModulInfo.class);
    

    使用就是这么简单

    对于 ViewModel ,只要 new ViewModelProvider(this) 中这个 this 是同一个 ViewModelStoreOwner,那么返回的 ViewModel 对象就是同一个。基于这个特性,我们就可以在 Activity 和 Fragment 或 Fragment 和Fragment 中传递数据。原理我们在下面的文章中分析

    原理

    1、创建 ViewModel

    我们先看一下 new ViewModelProvider(this) 做了什么

     public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
            this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                    ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                    : NewInstanceFactory.getInstance());
        }
    
     public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
            mFactory = factory;
            mViewModelStore = store;
        }
    
    

    首先 owner.getViewModelStore() 返回一个 ViewModelStore 实例

    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;
        }
    
        @SuppressWarnings("WeakerAccess") /* synthetic access */
        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();
                }
            }
        }
    

    ViewModelStore 是一个通过 HashMap 保存 ViewModel 的类:

    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();
        }
    }
    

    对于第二个参数,我们知道,AppCompatActivity 继承自 FragmentActivity,FragmentActivity 继承自 ComponentActivity ,我们看下 ComponentActivity 的代码:

    public class ComponentActivity extends androidx.core.app.ComponentActivity implements
            ...
            ViewModelStoreOwner,
            HasDefaultViewModelProviderFactory,
            ... {
             ...
             
      public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
            if (getApplication() == null) {
                throw new IllegalStateException("Your activity is not yet attached to the "
                        + "Application instance. You can't request ViewModel before onCreate call.");
            }
            if (mDefaultFactory == null) {
                mDefaultFactory = new SavedStateViewModelFactory(
                        getApplication(),
                        this,
                        getIntent() != null ? getIntent().getExtras() : null);
            }
            return mDefaultFactory;
        }  
               
             ...
      }
    
    public final class SavedStateViewModelFactory extends ViewModelProvider.KeyedFactory {
      private final Application mApplication;
      private final ViewModelProvider.Factory mFactory;
      private final Bundle mDefaultArgs;
      ...
        
       public SavedStateViewModelFactory(@Nullable Application application,
                @NonNull SavedStateRegistryOwner owner,
                @Nullable Bundle defaultArgs) {
            mSavedStateRegistry = owner.getSavedStateRegistry();
            mLifecycle = owner.getLifecycle();
            mDefaultArgs = defaultArgs;
            mApplication = application;
            mFactory = application != null
                    ? ViewModelProvider.AndroidViewModelFactory.getInstance(application)
                    : ViewModelProvider.NewInstanceFactory.getInstance();
        } 
      ...  
      
    }
    
    

    可见,ComponentActivity 实现了 HasDefaultViewModelProviderFactory 接口,且重写了 getDefaultViewModelProviderFactory 方法,在该方法中返回了一个 ViewModelProvider.Factory 的实例 SavedStateViewModelFactory 。在 SavedStateViewModelFactory 中,将 mFactory 初始化为 AndroidViewModelFactory。

    至于 NewInstanceFactory.getInstance(),它是一个简单工厂类,我们也看一下

    public static class NewInstanceFactory implements Factory {
    
            private static NewInstanceFactory sInstance;
    
            /**
             * Retrieve a singleton instance of NewInstanceFactory.
             *
             * @return A valid {@link NewInstanceFactory}
             */
            @NonNull
            static NewInstanceFactory getInstance() {
                if (sInstance == null) {
                    sInstance = new NewInstanceFactory();
                }
                return sInstance;
            }
    
            @SuppressWarnings("ClassNewInstance")
            @NonNull
            @Override
            public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
                //noinspection TryWithIdenticalCatches
                try {
                    return modelClass.newInstance();
                } catch (InstantiationException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                }
            }
        }
    

    好了,现在我们知道,通过 new ViewModelProvider(this) 方法,ViewModelProvider 将 mFactory 初始化为 SavedStateViewModelFactory 对象,将 mViewModelStore 初始化为 ViewModelStore 对象。

    接下来,再去看看 get(ViewModelInfo.class) 又做了哪些事情

     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);
        }
    

    本地类或者匿名类的时候 getCanonicalName() 方法将会返回null,也就是说不能使用这些类创建 ViewModel(这里留个问题,为什么不行?),接着,我们再看看 return 的 那个 get() 方法里,又做了哪些操作

    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
            //看有没有缓存,有缓存就使用缓存
            ViewModel viewModel = mViewModelStore.get(key);
    
            //通过 isInstance 判断 modelClass 能不能强转为 viewModel 的类型
            if (modelClass.isInstance(viewModel)) {
                if (mFactory instanceof OnRequeryFactory) {
                    ((OnRequeryFactory) mFactory).onRequery(viewModel);
                }
                return (T) viewModel;
            } else {
                if (viewModel != null) {
                    // TODO: log a warning.
                }
            }
            // 没有缓存,则新建
            if (mFactory instanceof KeyedFactory) {
                viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
            } else {
                viewModel = mFactory.create(modelClass);
            }
            mViewModelStore.put(key, viewModel);
            return (T) viewModel;
        }
    

    我们先看 mViewModelStore.get(key) ,这里通过 canonicalName 获取 mViewModelStore 中对应的 ViewModel,所以,通过 new ViewModelProvider(this) 创建的 ViewModelProvider,只要 “this” 相同,即是同一个 ViewModelStoreOwner ,那么相同的类就会有相同的 ViewModel 实例

    如果缓存中有,则直接返回这个 ViewModel,并且判断如果 mFactory 是 OnRequeryFactory 的子类,则调用 onRequery 方法。通过上面的分析,我们知道 mFactory 就是 SavedStateViewModelFactory ,SavedStateViewModelFactory 继承于 ViewModelProvider.KeyedFactory ,而 ViewModelProvider.KeyedFactory 就是继承于 OnRequeryFactory。所以。这里如果缓存中有 ViewModel ,那就会执行 onRequery 方法,这个我们后续再分析。

    如果缓存中没有,则新建一个并放到 mViewModelStore 里面。那怎么新建的呢?去 SavedStateViewModelFactory 类里看一下

    private static final Class<?>[] ANDROID_VIEWMODEL_SIGNATURE = new Class[]{Application.class,
                SavedStateHandle.class};
    private static final Class<?>[] VIEWMODEL_SIGNATURE = new Class[]{SavedStateHandle.class};
    
    public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
            // isAssignableFrom:当前Class对象如果是参数Class对象的父类,父接口,或者是相同,都会返回true
            // 所以,这里就是判断 modelClass 是否是 AndroidViewModel 或是 AndroidViewModel 的子类
            boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
            Constructor<T> constructor;
            if (isAndroidViewModel && mApplication != null) {
                // 查找构造器
                constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
            } else {
                constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
            }
            // doesn't need SavedStateHandle
            if (constructor == null) {
                return mFactory.create(modelClass);
            }
    
            SavedStateHandleController controller = SavedStateHandleController.create(
                    mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
            try {
                T viewmodel;
                if (isAndroidViewModel && mApplication != null) {
                    // 通过反射创建对象
                    viewmodel = constructor.newInstance(mApplication, controller.getHandle());
                } else {
                    viewmodel = constructor.newInstance(controller.getHandle());
                }
                // 设置 tag
                viewmodel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller);
                return viewmodel;
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Failed to access " + modelClass, e);
            } catch (InstantiationException e) {
                throw new RuntimeException("A " + modelClass + " cannot be instantiated.", e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException("An exception happened in constructor of "
                        + modelClass, e.getCause());
            }
        }
    
    private static <T> Constructor<T> findMatchingConstructor(Class<T> modelClass,
                Class<?>[] signature) {
            for (Constructor<?> constructor : modelClass.getConstructors()) {
                Class<?>[] parameterTypes = constructor.getParameterTypes();
                if (Arrays.equals(signature, parameterTypes)) {
                    return (Constructor<T>) constructor;
                }
            }
            return null;
        }
    
    <T> T setTagIfAbsent(String key, T newValue) {
            T previous;
            synchronized (mBagOfTags) {
                previous = (T) mBagOfTags.get(key);
                if (previous == null) {
                    mBagOfTags.put(key, newValue);
                }
            }
            T result = previous == null ? newValue : previous;
            if (mCleared) {
                // It is possible that we'll call close() multiple times on the same object, but
                // Closeable interface requires close method to be idempotent:
                // "if the stream is already closed then invoking this method has no effect." (c)
                closeWithRuntimeException(result);
            }
            return result;
        }
    

    在 SavedStateViewModelFactory 通过反射的方式创建了一个 ViewModel 对象。

    销毁 ViewModel

    分析到这,并没有发现 ViewModel 跟 Activity 的生命周期有什么关系,所以,想着在 ComponentActivity 中搜索一下 onDestroy 方法看看,结果尴尬了,ComponentActivity 中就没有重写 onDestroy 方法...

    往前看看 FragmentActivity 啦,AppCompatActivity 啦,它们的 onDestroy 方法跟 ViewModel 也没有什么关系。

    线索到这突然就断了,忽然灵光一闪,想起了分析 Lifecycle 源码时好像在 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();
                        // 清除 ViewModelStore
                        if (!isChangingConfigurations()) {
                            getViewModelStore().clear();
                        }
                    }
                }
            });
            ...
        }
    

    找到啦,可以看到,在非配置改变导致执行 Lifecycle.Event.ON_DESTROY 的时候,会清除掉 ViewModelStore,清除的方法我们也看一下

    public class ViewModelStore {
    
        private final HashMap<String, ViewModel> mMap = new HashMap<>();
    
        ...
         
        /**
         *  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();
        }
    }
    

    Activity 异常重建情况下 ViewModel 的保存和恢复

    因为配置改变等原因造成的异常销毁呢,ViewModel是怎么保存及恢复的?这才是我们想知道的啊。我们直接从 AMS 去找,我们知道,Activity 配置发生变化的时候,会调用 ActivityThread 的 performDestroyActivity() 方法来销毁旧的 Activity,如下:

    void performDestroyActivity(ActivityClientRecord r, boolean finishing,
                  ...         
              if (getNonConfigInstance) {
                  try {
                      r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
                  } catch (Exception e) {
                      if (!mInstrumentation.onException(r.activity, e)) {
                          throw new RuntimeException("Unable to retain activity "
                                 + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
                      }
                  }
              }
                  ...
          }
    

    我们看到配置改变了之后,会调用 activity.retainNonConfigurationInstances() 方法,将其返回的数据存储到 ActivityClientRecord 的 lastNonConfigurationInstances 中,我们跟进 retainNonConfigurationInstances() 看下:

    NonConfigurationInstances retainNonConfigurationInstances() {
            // 保存数据
            Object activity = onRetainNonConfigurationInstance();
            HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
            FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
    
            // We're already stopped but we've been asked to retain.
            // Our fragments are taken care of but we need to mark the loaders for retention.
            // In order to do this correctly we need to restart the loaders first before
            // handing them off to the next activity.
            mFragments.doLoaderStart();
            mFragments.doLoaderStop(true);
            ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
    
            if (activity == null && children == null && fragments == null && loaders == null
                    && mVoiceInteractor == null) {
                return null;
            }
    
            NonConfigurationInstances nci = new NonConfigurationInstances();
            nci.activity = activity;
            nci.children = children;
            nci.fragments = fragments;
            nci.loaders = loaders;
            if (mVoiceInteractor != null) {
                mVoiceInteractor.retainInstance();
                nci.voiceInteractor = mVoiceInteractor;
            }
            return nci;
        }
    

    接着,我们去 ComponentActivity 中的 onRetainNonConfigurationInstance 方法里,看看又做了哪些操作

     public final Object onRetainNonConfigurationInstance() {
            Object custom = onRetainCustomNonConfigurationInstance();
            // 保存当前的 ViewModelStore
            ViewModelStore viewModelStore = mViewModelStore;
            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;
                }
            }
    
            if (viewModelStore == null && custom == null) {
                return null;
            }
    
            NonConfigurationInstances nci = new NonConfigurationInstances();
            nci.custom = custom;
            nci.viewModelStore = viewModelStore;
            return nci;
        }
    

    可以看到,在这个方法里,我们将 ViewModelStore 保存起来。所以,先总结一下:在 Activity 配置发生改变导致重建的时候,我们通过 ActivityThread 的 performDestroyActivity() ,调用 Activity 的 retainNonConfigurationInstances(),在 retainNonConfigurationInstances() 方法中,通过调用 ComponentActivity 的 onRetainNonConfigurationInstance() 将 ViewModelStore 保存下来,然后在 ActivityThread 的 performDestroyActivity() ,将其存储到到 ActivityClientRecord 的 lastNonConfigurationInstances 属性中,这就是存储的逻辑

    那恢复的逻辑又是什么呢?我们继续往下看。根据 Activity 的 onRetainNonConfigurationInstance() 方法的注释来看,恢复跟 getLastNonConfigurationInstance() 方法有关

     public Object getLastNonConfigurationInstance() {
            return mLastNonConfigurationInstances != null
                    ? mLastNonConfigurationInstances.activity : null;
        }
    

    这个 mLastNonConfigurationInstances 是在哪赋值的呢?是在 attach 的时候。

    NonConfigurationInstances mLastNonConfigurationInstances;
    
    final void attach(Context context, ActivityThread aThread,
                Instrumentation instr, IBinder token, int ident,
                Application application, Intent intent, ActivityInfo info,
                CharSequence title, Activity parent, String id,
                NonConfigurationInstances lastNonConfigurationInstances,
                Configuration config, String referrer, IVoiceInteractor voiceInteractor,
                Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
                IBinder shareableActivityToken) {
            ...
            mLastNonConfigurationInstances = lastNonConfigurationInstances;
            ...
        }
    

    走,去 ActivityThread 中的 performLaunchActivity 方法中看看 attach 的参数是咋来的吧

     private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
             ...
             activity.attach(appContext, this, getInstrumentation(), r.token,
                             r.ident, app, r.intent, r.activityInfo, title, r.parent,
                             //主要的就是这个 r.lastNonConfigurationInstances
                             r.embeddedID, r.lastNonConfigurationInstances, config,
                             r.referrer, r.voiceInteractor, window, r.configCallback,
                             r.assistToken, r.shareableActivityToken);
      
             ...
      
              return activity;
    }
     
    

    可以看到,在调用 attach() 方法的时候,将之前在 ActivityClientRecord 类的 lastNonConfigurationInstances 属性中存储的数据又带回给了 Activity,这样,在调用 getLastNonConfigurationInstance() 时获取到的就是之前保存的数据,这就是恢复的逻辑

    相关文章

      网友评论

        本文标题:ViewModel 使用及原理

        本文链接:https://www.haomeiwen.com/subject/dzuonrtx.html