美文网首页学习之鸿蒙&Android
Jetpack(三):ViewModel学习记录

Jetpack(三):ViewModel学习记录

作者: 打工崽 | 来源:发表于2021-07-04 17:59 被阅读0次

    原理

    MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class)

    由于在Activity中调用,所以this值为Activity,在Fragment中,this则为Fragment,因此of肯定有多个构造方法,以Activity中为例

    源码

    ViewModelProviders.java的of

    @NonNull
        @MainThread
        public static ViewModelProvider of(@NonNull FragmentActivity activity) {
            return of(activity, null);
        }
    
    @NonNull
        @MainThread
        public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
            Application application = checkApplication(checkActivity(fragment));
            if (factory == null) {
                factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
            }
            return new ViewModelProvider(fragment.getViewModelStore(), factory);
        }
    

    第2个of函数里,会调用getApplication方法来返回Activity对应的Application

    if语句中会创建AndroidViewModelFactory实例。最后会新建一个ViewModelProvider,将AndroidViewModelFactory作为参数传入


    ViewModelProvider.java的AndroidViewModelFactory

    public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
    
            private static AndroidViewModelFactory sInstance;
    
            /**
             * Retrieve a singleton instance of AndroidViewModelFactory.
             *
             * @param application an application to pass in {@link AndroidViewModel}
             * @return A valid {@link AndroidViewModelFactory}
             */
            @NonNull
            public static AndroidViewModelFactory getInstance(@NonNull Application application) {
                if (sInstance == null) {
                    sInstance = new AndroidViewModelFactory(application);
                }
                return sInstance;
            }
    
            private Application mApplication;
    
            /**
             * Creates a {@code AndroidViewModelFactory}
             *
             * @param application an application to pass in {@link AndroidViewModel}
             */
            public AndroidViewModelFactory(@NonNull Application application) {
                mApplication = application;
            }
    
            @NonNull
            @Override
            public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
                if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                    //noinspection TryWithIdenticalCatches
                    try {
                        return modelClass.getConstructor(Application.class).newInstance(mApplication);
                    } catch (NoSuchMethodException e) {
                        throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                    } catch (InstantiationException e) {
                        throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                    } catch (InvocationTargetException e) {
                        throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                    }
                }
                return super.create(modelClass);
            }
        }
    

    可以看到,AndroidViewModelFactory是一个单例,ViewModel本身是一个抽象类,我们一般通过继承ViewModel来实现自定义ViewModel,那么AndroidViewModelFactory的create方法的作用就是通过反射生成ViewModel的实现类的

    再来看看ViewModelProvider的get方法

    ViewModelProvider.java的get

    @NonNull
        @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);
        }
    
    
    @NonNull
        @MainThread
        public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
            ViewModel viewModel = mViewModelStore.get(key);
    
            if (modelClass.isInstance(viewModel)) {
                //noinspection unchecked
                return (T) viewModel;
            } else {
                //noinspection StatementWithEmptyBody
                if (viewModel != null) {
                    // TODO: log a warning.
                }
            }
    
            viewModel = mFactory.create(modelClass);
            mViewModelStore.put(key, viewModel);
            //noinspection unchecked
            return (T) viewModel;
        }
    
    

    第1个get中,先拿到modelClass的类名,并对其进行字符串拼接,作为第2个get的参数,DEFAULT_KEY就是androidx.lifecycle.ViewModelProvider.DefaultKey

    因此,第2个get方法中拿到的key就是DEFAULT_KEY + 类名,根据key从ViewModelStore获取ViewModel的实现类。如果ViewModel能直接转成modelClass类的对象,则直接返回该ViewModel,否则会通过Factory创建一个ViewModel,并将其存储到ViewModelStore中。这里的Factory指的就是AndroidViewModelFactory,在上面我们提到的ViewModelProvider创建时作为参数被传进来


    关系解析

    ViewModelProvider.java:为Fragment,Activity等提供ViewModels的utils类

    ViewModelProviders.java:提供of方法,返回一个ViewModelProvider

    ViewModelStore.java:以HashMap形式缓存ViewModel,需要时从缓存中找,有则返回,无则创建

    ViewModelStores.java:提供of方法,返回一个ViewModelStore给需要的Activity或Fragment

    AndroidViewModelFactory:ViewModelProvider的静态内部类,提供create方法反射创建ViewModel实现类


    如何在旋转后保存数据

    /**
         * Retain all appropriate fragment state.  You can NOT
         * override this yourself!  Use {@link #onRetainCustomNonConfigurationInstance()}
         * if you want to retain your own state.
         */
    @Override
        public final Object onRetainNonConfigurationInstance() {
            Object custom = onRetainCustomNonConfigurationInstance();
    
            FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
    
            if (fragments == null && mViewModelStore == null && custom == null) {
                return null;
            }
    
            NonConfigurationInstances nci = new NonConfigurationInstances();
            nci.custom = custom;
            nci.viewModelStore = mViewModelStore;
            nci.fragments = fragments;
            return nci;
        }
    
    
    /**
         * Use this instead of {@link #onRetainNonConfigurationInstance()}.
         * Retrieve later with {@link #getLastCustomNonConfigurationInstance()}.
         */
        public Object onRetainCustomNonConfigurationInstance() {
            return null;
        }
    
        /**
         * Return the value previously returned from
         * {@link #onRetainCustomNonConfigurationInstance()}.
         */
        @SuppressWarnings("deprecation")
        public Object getLastCustomNonConfigurationInstance() {
            NonConfigurationInstances nc = (NonConfigurationInstances)
                    getLastNonConfigurationInstance();
            return nc != null ? nc.custom : null;
        }
    

    核心就在这里,我们不可以重写第一个方法,如果想要实现自己的保存数据方式需要重写Custom方法,原生保存数据的流程是当我们旋转屏幕,系统会调用onRetainNonConfigurationInstance方法,在这个方法内会将我们的ViewModelStore进行保存。一旦当前activity去获取ViewModelStore,会通过getLastNonConfigurationInstance方法恢复之前的ViewModelStore,所以状态改变前后的ViewModelStore是同一个


    为何传入Context会内存泄漏

    ViewModel之所以不应该包含Context的实例或类似于上下文的其他对象的原因是因为它具有与Activity和Fragment不同的生命周期。假设我们在应用上进行了更改,这会导致Activity和Fragment自行销毁,因此它会重新创建。ViewModel意味着在此状态期间保持不变,因此如果它仍然在被破坏的Activity中持有View或Context,则可能会发生崩溃和其他异常


    应用举例

    MyViewModel

    public class MyViewModel extends ViewModel {
    
        public int num;
    }
    

    activity_main.xml

    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            android:textSize="34sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.237" />
    
        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="+"
            android:textSize="34sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:onClick="AddNum"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    MainActivity

    public class MainActivity extends AppCompatActivity {
    
        TextView textView;
        MyViewModel viewModel;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            textView = findViewById(R.id.textView);
            //viewmodel中不要传入context,如果必须要使用,换成AndroidViewModel里的Application
            viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);
            textView.setText(String.valueOf(viewModel.num));
        }
    
        //保存瞬态数据,屏幕旋转后数据还在
        public void AddNum(View view) {
            textView.setText(String.valueOf(++viewModel.num));
        }
    }
    

    这样就简单创建了一个Button和一个TextView用于显示数字,在屏幕旋转后数字并不会重置


    配合LiveData

    MyViewModel

    public class MyViewModel extends ViewModel {
    
        private MutableLiveData<Integer> currentSecond;
    
        public MutableLiveData<Integer> getCurrentSecond(){
            if(currentSecond == null){
                currentSecond = new MutableLiveData<>();
                currentSecond.setValue(0);
            }
            return currentSecond;
        }
    }
    
    

    activity_data.xml

    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".DataActivity">
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="0"
            android:textSize="30sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    DataActivity

    public class DataActivity extends AppCompatActivity {
    
        private MyViewModel viewModel;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_data);
            TextView textView = findViewById(R.id.textView);
            viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);
            textView.setText(String.valueOf(viewModel.getCurrentSecond().getValue()));
    
            viewModel.getCurrentSecond().observe(this, new Observer<Integer>() {
                @Override
                public void onChanged(Integer integer) {
                    textView.setText(String.valueOf(integer));
                }
            });
            startTime();
        }
    
        private void startTime(){
            new Timer().schedule(new TimerTask() {
                @Override
                public void run() {
                    //非UI线程,用postValue
                    //UI线程,用setValue
                    viewModel.getCurrentSecond().postValue(viewModel.getCurrentSecond().getValue() + 1);
                }
            },1000,1000);
        }
    

    这样就创建了一个计时器,在屏幕旋转后由于LiveData和ViewModel配合,计时器并不会重置


    总结

    ViewModel的大体使用方式在于他调用了ViewModelProvider构造器,构造器中传入的第一个参数是ViewModelStoreOwner,一般在Activity或者Fragment中直接传this即可。第二个参数便是一个Factory对象,而这个Factory对象用于创建ViewModel。然后调用get方法内部构造出一个key,用于从ViewModelStore中取出ViewModel

    而旋转后保存数据就通过onRetainNonConfigurationInstances方法构建一个NonConfigurationInstance对象,将此时的mViewModel对象设置进去,然后存储在ActivityClientRecord中。在Activity进行relaunch的时候就会传入之前的lastNonConfigurationInstances,这样ViewModelStore是上一个的,自然ViewModel也是上一个的,完成了数据的保存

    当Activity正常销毁时,则会通过clear方法清除所有的ViewModel


    相关文章

      网友评论

        本文标题:Jetpack(三):ViewModel学习记录

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