美文网首页学习之鸿蒙&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