Android Jetpack之ViewModel

Android Jetpack之ViewModel

ViewModel概念

ViewModel是用来保存应用UI数据的类,会在配置变更后继续存在(比如旋转屏幕),我们知道当手机旋转的时候,Activity会被销毁重建,里面的数据都会丢失,或导致界面崩溃,以前我们解决这个问题一般重写onSaveInstanceState方法来保存一些关键数据,在onCreate或者onRestoreInstanceState方法中恢复数据,但此方法仅适用于可以序列化然后反序列化的少量数据,而不适用于潜在的大量数据

使用了ViewModel就不用这么麻烦了,在旋转屏幕的时候它不会被销毁,而且ViewMode中也可以保存相对大一点的数据。

另外遵守单一职责的原则,Activity应该只负责显示视图,数据的请求操作部分交给别的管理类去做ViewModel就可以完成这个任务

当然ViewModel并不能完全替代onSaveInstanceState,当进程被关闭的时候,ViewModel会被销毁,而onSaveInstanceState并会。

ViewModel简单使用

比如我们一个请求接口的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class NameViewModel1 extends ViewModel {

private MutableLiveData<String> currentName;

public MutableLiveData<String> getCurrentName() {
if (currentName == null) {
currentName = new MutableLiveData<String>();
loadData();
}
return currentName;
}

private void loadData() {
OkGo.<String>get("http://gank.io/api/xiandu/categories")
.execute(new StringCallback() {
@Override
public void onSuccess(Response<String> response) {
Gson gson = new Gson();
Catefories catefories = gson.fromJson(response.body(), Catefories.class);
currentName.postValue(catefories.getResults().get(0).getName());
}
});
}
}

在ViewModel中请求接口,将返回值赋值给MutableLiveData,然后在Activity中绑定这个LiveData即可,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

public class NameActivity extends AppCompatActivity {
private NameViewModel1 mViewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_name);

final TextView textView = findViewById(R.id.tv_name);
mViewModel = ViewModelProviders.of(this).get(NameViewModel1.class);

mViewModel.getCurrentName().observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
textView.setText(s);
}
});
}
}

这样当ViewModel请求完数据之后,Activity中的TextView就会自动被赋值了。当Activity结束后,系统框架层会自动调用ViewModel的onCleared()方法来清理资源。

注意永远不要把Activity,View,Fragment的引用传入ViewModel中。比如前面我们知道当旋转屏幕的时候ctivity会被销毁然后重建,这时候ViewModel没被销毁,但是它还持有者以前被销毁的Activity的引用,这就会造成内存泄露。

如果 ViewModel需要 Application上下文,可以使用ViewModel的子类AndroidViewModel,它里面会有Application的上下文对象

ViewModel的生命周期

ViewModel对象的作用域是在获取ViewModel时传递给ViewModelProvider的生命周期。它会一直存在,直到Lifecycle告诉它该关闭了,比如activity finish了,或者fragment detached了。

通常情况下ViewModel在系统第一次创建一个activity的时候,在其onCreate()方法中创建,在activity的整个活动周期中可能会调用onCreate()方法多次,比如旋转屏幕,ViewModel会一直存在,直到这个activity完全退出和销毁。

在不同的fragment之间共享数据

activity中两个fragment之前互相通信也是比较常见的,使用ViewModel可以很好的共享数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

public void select(Item item) {
selected.setValue(item);
}

public LiveData<Item> getSelected() {
return selected;
}
}


public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}

public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}

可以看到,两个Fragment都是把他们所依赖的activity传入ViewModelProvider中,那么他们所获取到的ViewModel也是同一个。此ViewModel的生命周期也限制在这个activity的范围内。

这样做的好处:

  1. activity不用做任何事情,也不需要了解两个fragment之间的沟通
  2. 两个fragment之间除了它们共享的ViewModel,别的都不需要彼此关心,即使有一个fragment崩溃了,另一个依然会正常工作
  3. 每个fragment都有自己的生命周期,互相不影响。

ViewModel可以结合LiveDataRoom,可以让UI和数据中数据同步,这可以替换以前的CursorLoader。


OK,上面大部分都是官方文档的翻译,看完就知道ViewModel的简单使用了,下面来看看其源码看看内部实现

前面我们知道ViweModel的初始化方法是这样:ViewModelProviders.of(this).get(NameViewModel1.class),下面从of开始。

1
2
3
4
5
6
7
8
9
10
11
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
return of(activity, null);
}
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
Application application = checkApplication(activity);
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(activity.getViewModelStore(), factory);
}

fragment也有类似的上面两个方法,这里是从activity中调用的。

  • 调用of方法,传入当前的activity或者fragment,
  • 然后调用了两个参数的重载方法,传入的第二个参数Factory为null
  • 因为为null,所以通过activity获取到当前的Application对象,然后创建出当前的工厂(factory)
  • 最后创建一个ViewModelProvider对象,两个参数,当前activity相关联的ViewModelStore和factory。

这俩参数是干啥的,一个一个来,先看第一个参数ViewModelStore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}

看看Activity中的NonConfigurationInstances对象是不是空,如果不是给mViewModelStore赋值,如果mViewModelStore还未null,创建一个ViewModelStore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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。

在看这个工厂AndroidViewModelFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {

private static AndroidViewModelFactory sInstance;
@NonNull
public static AndroidViewModelFactory getInstance(@NonNull Application application) {
if (sInstance == null) {
sInstance = new AndroidViewModelFactory(application);
}
return sInstance;
}

private Application mApplication;

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,是ViewModelProvider的静态内部类,getInstance获取它的单例。create中,可以看到,通过反射创建出一个Class对象。其实就是我们自己写的ViewModel类

OK到这里of方法就看完了,其实就是创建了一个ViewModelProvider对象,创建这个对象需要传入一个保存ViewModel的ViewModelStore类和一个工厂类,这个工厂有个create方法可以通过反射创建相应的Class对象。

下面来看get方法

1
2
3
4
5
6
7
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);
}

找到全类名,然后拼接上一个默认的key,之后调用get的两个参数重载方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
mViewModelStore.put(key, viewModel);
//noinspection unchecked
return (T) viewModel;
}

通过key到mViewModelStore中寻找ViewModel,如果找到了并且是ViewModel类型的,就返回,如果没找到,调用工厂的create方法创建一个,并保存到ViewModelStore中,最后返回这个viewModel

OK到这里ViewModel的源码就看完啦,总结一下ViewModel是通过ViewModelprovider中的AndroidViewModelFactory这个工厂创建的,创建完成后完成后保存在ViewModelStore中。

前面看文档我们知道,当手机屏幕旋转的时候,activity重建,但是ViewModel中的数据不丢失,这是怎么实现的呢?

搜了一些博客都是是说创建了一个HolderFragment ,创建的时候调用了setRetainInstance(true)方法,这个方法可以保证activity销毁的时候这个HolderFragment不会重建,从而保证数据不会丢失。

应该是版本不同的原因,我现在看的是androidx中的源码,并没有发现这个HolderFragment,那它是怎么保证数据不丢失的呢

现在回到getViewModelStore()方法中,看到在那ViewModelStore的实例的时候,先去NonConfigurationInstances中那,拿不到才创建。

1
2
3
4
5
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
FragmentManagerNonConfig fragments;
}

可以看到它是Activity的静态内部类,在activity创建时执行attach方法的时候被赋值,那么它的生命周期就跟这个Activity就没有关系了,Activity销毁了它也可能存在当Activity重新创建的时候,在FragmentActivity的onCreate中可以看到下面

1
2
3
4
5
6
7
8
9
10
11
12
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.attachHost(null /*parent*/);

super.onCreate(savedInstanceState);

NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
mViewModelStore = nc.viewModelStore;
}
......
}

如果NonConfigurationInstances不为null,并且它中的viewModelStore也不为null,activity中的mViewModelStore为null的时候,会把NonConfigurationInstances中的viewModelStore的值赋值给mViewModelStore,所以数据也就没丢失了。

コメント

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×