Jetpack(三):ViewModel学习记录

时间:2021-7-21 作者:qvyue

原理

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 create(@NonNull Class 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 get(@NonNull Class 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 get(@NonNull String key, @NonNull Class 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




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 currentSecond;

    public MutableLiveData getCurrentSecond(){
        if(currentSecond == null){
            currentSecond = new MutableLiveData();
            currentSecond.setValue(0);
        }
        return currentSecond;
    }
}


activity_data.xml


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() {
            @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配合,计时器并不会重置


声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。