美文网首页
Android Jetpack — ViewModel

Android Jetpack — ViewModel

作者: 安静的蓝孩子 | 来源:发表于2020-06-15 14:27 被阅读0次

    ViewModel是为了更好的以生命周期的方式管理界面相关的数据。

    以一个简单的计数 demo 来演示之间的区别。

    图1.gif

    上图中,是以平常的方式实现的计数器,当我们旋转屏幕而没有其他处理的时候,计数器的数据丢失了。这是因为屏幕旋转时,我们的activity被销毁重建了,而存放在activity的数据自然也是没有了。

    class MainActivity : AppCompatActivity() {
    
        private lateinit var mBtnAdd: Button
        private lateinit var mTvNum: TextView
    
        private var mNum = 0
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            init()
        }
    
        private fun init() {
            mBtnAdd = findViewById(R.id.btn_add)
            mTvNum = findViewById(R.id.tv_num)
    
            mTvNum.text = mNum.toString()
    
            mBtnAdd.setOnClickListener {
                mNum++
                mTvNum.text = mNum.toString()
            }
        }
    }
    

    这时,我们采用ViewModel来实现,效果如图:

    图2

    可见,旋转屏幕后,数据还被保存了下来。

    ViewModel究竟怎么使用?以及是什么原理呢?

    1. ViewModel的使用

    1.1 依赖

    ViewModel 的依赖,可以查看官方文档,导入最新的版本。

    官网地址

    现在示例使用的版本:

    def lifecycle_version = "2.2.0"
    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
    

    1.2 创建一个ViewModel类

    这个ViewModel类的作用主要是用于保存与View相关的数据。

    在此次的demo中,就是要把计数的mNum存到ViewModel中。

    *** 需要注意的是ViewModel类中不应该持有ActivityFragmentview的引用,具体原因后面会讲解释***
    如果确实需要,应该使用applicationcontext,或者使用含有上下文的AndroidViewModel

    class NumberViewModel : ViewModel() {
    
        var num = 0
    
    }
    

    1.3 View中关联ViewModel

    class MainActivity2 : AppCompatActivity() {
    
        private lateinit var mBtnAdd: Button
        private lateinit var mTvNum: TextView
    
        private lateinit var mViewModel: NumberViewModel
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main_2)
    
            init()
        }
    
        private fun init() {
            mBtnAdd = findViewById(R.id.btn_add)
            mTvNum = findViewById(R.id.tv_num)
    
            //获取ViewModel
            mViewModel = ViewModelProvider(this).get(NumberViewModel::class.java)
            //使用ViewModel中的数据
            mTvNum.text = mViewModel.num.toString()
    
            mBtnAdd.setOnClickListener {
                mViewModel.num = mViewModel.num + 1
                mTvNum.text = mViewModel.num.toString()
            }
        }
    }
    
    ViewModelProvider(this).get(NumberViewModel::class.java)
    //2.2.0-alpha02 以前的版本是通过下面的方式,新版本已弃用  
    //ViewModelProviders.of(this).get(NumberViewModel::class.java)
    

    这里获取到当前Activity对应的NumberViewModel(第一次是创建->保存->返回),然后就可以直接使用了。

    这里的this可以 FragmentActivityAppCompatActivityFragment

    2. ViewModel的内部实现

    一步步跟进ViewModel的内部实现。

    首先是通过ViewModelProvider(this)构造方法,创建一个ViewModelProvider,并在其构造方法中,获取到存储ViewModelViewModelStore对象。

    /**
         * Creates {@code ViewModelProvider}. This will create {@code ViewModels}
         * and retain them in a store of the given {@code ViewModelStoreOwner}.
         * <p>
         * This method will use the
         * {@link HasDefaultViewModelProviderFactory#getDefaultViewModelProviderFactory() default factory}
         * if the owner implements {@link HasDefaultViewModelProviderFactory}. Otherwise, a
         * {@link NewInstanceFactory} will be used.
         */
        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就是我们传入的this(FragmentActivityAppCompatActivityFragment)。

    我们看看XXXActivity中的getViewModelStore方法。

    /**
         * Returns the {@link ViewModelStore} associated with this activity
         * <p>
         * Overriding this method is no longer supported and this method will be made
         * <code>final</code> in a future version of ComponentActivity.
         *
         * @return a {@code ViewModelStore}
         * @throws IllegalStateException if called before the Activity is attached to the Application
         * instance i.e., before onCreate()
         */
        @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.");
            }
            if (mViewModelStore == null) {
                //如果当前的mViewModelStore为空,会先向nc中取mViewModelStore,这里边存储的就是上一次Activity对应的实例的mViewModelStore
                NonConfigurationInstances nc =
                        (NonConfigurationInstances) getLastNonConfigurationInstance();
                if (nc != null) {
                    // Restore the ViewModelStore from NonConfigurationInstances
                    mViewModelStore = nc.viewModelStore;
                }
                if (mViewModelStore == null) {
                    mViewModelStore = new ViewModelStore();
                }
            }
            return mViewModelStore;
        }
    

    可见,这里的关键是NonConfigurationInstances。在设备旋转的时候,当前Activity被销毁了,mViewModelStore等数据会被封装到NonConfigurationInstances中存储出来,创建了新的对象会重新传入该NonConfigurationInstances

    然后是存储ViewModelViewModelStore

    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还是比较简单的,就是用HashMap来存储的数据。

    ViewModel的生成和获取相关, 是在ViewModelProviderget方法中。

    /**
         * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
         * an activity), associated with this {@code ViewModelProvider}.
         * <p>
         * The created ViewModel is associated with the given scope and will be retained
         * as long as the scope is alive (e.g. if it is an activity, until it is
         * finished or process is killed).
         *
         * @param modelClass The class of the ViewModel to create an instance of it if it is not
         *                   present.
         * @param <T>        The type parameter for the ViewModel.
         * @return A ViewModel that is an instance of the given type {@code T}.
         */
        @NonNull
        @MainThread
        public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
            //获取我们传入的NumberViewModel简称(就这样叫吧),作为key的一部分
            String canonicalName = modelClass.getCanonicalName();
            if (canonicalName == null) {
                throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
            }
            return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
        }
        
        public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
            //从mViewModelStore中取出viewModel
            ViewModel viewModel = mViewModelStore.get(key);
    
            //判断viewModel是否是modelClass类的实例
            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);
            return (T) viewModel;
        }
    

    3. ViewModel的生命周期

    引用官网的一张图片

    图3.png

    onCreateActivity的生命周期内可能会多次调用(例如,本例中的旋转应用程序时),但ViewModel会在整个过程中保留下来。

    这张图也解释了为什么ViewModel中不能持有ActivityFragmentview的引用。因为Activity在重建后是一个新的对象,如果ViewModel中持有旧对象的引用,这个旧对象可能就等不到释放,造成泄漏。

    相关文章

      网友评论

          本文标题:Android Jetpack — ViewModel

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