原理
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
网友评论