Jetpack-生命周期组件库ViewModel

Viewmodel#

用途#

用于关注界面相关的数据,关注生命周期期间所需的数据

例如:屏幕旋转、语言、时区发生更改后继续显示的数据。

关键:对数据的管理、存储数据,是一种管理数据的方式

优势#

  • 销毁或重建后,页面瞬时态的数据会丢失,如rotate后,ViewModel不会被销毁,Activity的成员变了会销毁
  • 比onSaveInstanceState能存的数据量更大,如网络请求查询回来的用户列表接口、bitmap、网络缓存的视频流适合放在Viewmodel里,不适合放在OnSaveInstanceState
  • 避免页面销毁后,异步任务返回造成的内存泄漏;
  • 减少代码维护量,避免手动编写代码管理这些资源的释放
  • Activity、Fragment不应对关注数据的处理,而只应该关注将数据显示到界面上
  • 从Activity和Fragment中分离出视图所需数据的管理责任,让代码逻辑解耦,让开发者管理业务和界面的关系更高效
  • 在Activity、Fragment之间共享数据,无需持有引用,无需相互调用,无需担心被其他Fragment生命周期影响

生命周期#

说明 ViewModel 随着 Activity 状态的改变而经历的生命周期。

activity销毁之前,会调用ViewModel的onCleared方法

1
2
2022-06-24 10:28:35.993 32581-32581/com.shaunsheep.mvvm E/MyRxjavaViewModel: onCleared: 
2022-06-24 10:28:35.993 32581-32581/com.shaunsheep.mvvm E/ViewModelSampleActivity: onDestroy:

问:如何触发的onCLeared?

创建ViewModel时,传递给 ViewModelProviderLifecycle

在ComponentActivity的构造函数里,里有如下代码

1
2
3
4
5
6
7
8
9
10
11
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});

问:ViewModel结束时间,在Activity与Fragment的有什么区别?

答:对于 activity,是在 activity#ondestory时;对于 fragment,是在 fragment#detach

问:ViewModel是如何持续存在的,换句话说为什么持续存在?

初始化ViewModel的方式决定ViewModel会持续存在

初始化方式:ViewModelProviders.of(this).get(MyRxjavaViewModel.class);

get方法调用了:mViewModelStore.get(key)

get方法调用了:mMap.get(key)

调用了HashMap,根据key获取对应的ViewModel

答案是:hashmap不被销毁、不清空,ViewModel将持续存在,初始化ViewModel的时候,传入同一个class文件,将返回hashMap中的缓存

问:如何在Recycleview使用viewmodel?

问:如何在多个组件共享viewmodel?

问:如何在一个Activity的多个Fragment之间共享viewmodel

答:在fragment实例化的时候,创建viewmodel传入绑定的Activity

1
2
model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);

基本类型#

名称 允许持有context
ViewModel
AndroidViewModel

基本用法#

创建#

继承ViewModel抽象类

1
2
public class SharedViewModel extends ViewModel {
}

初始化#

1
2
3
4
// 方式1 静态方法,这个方法在androidx.lifecycle:lifecycle-extensions 扩展包里
ViewModelProviders.of(this).get(NormalViewModel.class);
// 方式2 构造方法
new ViewModelProvider(this).get(SharedViewModel.class);

发送数据#

其实是调用livedata发送数据

post方式:在子线程或主线程都可以,最终会在主线程执行

1
2
3
4
5
6
7
8
public class SharedViewModel extends ViewModel {
private final MutableLiveData<String> selected = new MutableLiveData<String>();

public void select(String item) {
selected.postValue(item);
}

}

set方式:只能在主线程调用

1
2
3
public void select(String item) {
selected.setValue(item);
}

监听数据变化observer#

其实也是监听livedata的obser方法

1
2
3
4
5
6
7
8
9
10
11
public class SharedViewModel extends ViewModel {
private final MutableLiveData<String> selected = new MutableLiveData<String>();

public void select(String item) {
selected.postValue(item);
}

public MutableLiveData<String> getSelected() {
return selected;
}
}

在需要监听的地方

1
2
3
4
5
6
model.getSelected().observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
textView.setText(s);
}
});

多个Fragment共享数据#

viewmodel核心代码就一句:初始化viewmodel的时候传入绑定者Activity

1
model = new ViewModelProvider(this).get(SharedViewModel.class);

这样做的优势:

  1. Activity无需操作数据,无需增加额外代码,无需对Fragment的通信有任何干涉
  2. Fragment之间解耦,无需互相引用,互相了解,任一销毁不影响其他Fragment

Activity切换Fragment

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
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
Fragment fragment;
if (tab.getText().equals("tab" + 1)) {
fragment = fragmentList.get(0);
} else {
fragment = fragmentList.get(1);
}
getSupportFragmentManager()
.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.fl_container, fragment, null)
.commitNow();
}

@Override
public void onTabUnselected(TabLayout.Tab tab) {

}

@Override
public void onTabReselected(TabLayout.Tab tab) {

}
});

Fragment1初始化数据

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
38
39
40
41
42
43
44
45
46
47
public class LeftFragment extends Fragment {
private SharedViewModel model;
private TextView textView;

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_list, container, false);
}

@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
textView = view.findViewById(R.id.tv_show_msg);
model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
EditText editText = view.findViewById(R.id.ed_name);
textView.setText(model.getSelected().getValue());
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {

}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
model.select(s + "");
}

@Override
public void afterTextChanged(Editable s) {

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

@Override
public void onResume() {
super.onResume();
model.getSelected().postValue(model.getSelected().getValue());
}
}

Fragment2初始化数据

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
38
39
40
41
42
43
44
45
46
47
public class RightFragment extends Fragment {
private SharedViewModel model;

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_details, container, false);
}

@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TextView textView = view.findViewById(R.id.tv_show_msg);

model = new ViewModelProvider(this).get(SharedViewModel.class);
EditText editText = view.findViewById(R.id.ed_name);
textView.setText(model.getSelected().getValue());
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {

}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
model.select(s + "");
}

@Override
public void afterTextChanged(Editable s) {

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

@Override
public void onResume() {
super.onResume();
model.getSelected().postValue(model.getSelected().getValue());
}
}

保存界面状态#

根据官网资料,ViewModel仅在Activity销毁重建时可以恢复数据,换句话说,App进程被framwork杀死后Activity重建,viewmodel也会被重建,详情看官网的这个表格

ViewModel
SavedStateHandle
ViewModel 已保存实例状态
Bundle
持久性存储空间
存储位置 在独立进程中 在内存中 已序列化到磁盘 在磁盘或网络上
在配置更改后继续存在
在系统发起的进程终止后继续存在
在用户完成 Activity 关闭/onFinish() 后继续存在
数据限制 支持复杂对象,限制目前不清楚 支持复杂对象,但是空间受可用内存的限制 仅适用于基元类型和简单的小对象,例如字符串 仅受限于磁盘空间或从网络资源检索的成本/时间
读取/写入时间 快(仅限内存访问) 快(仅限内存访问) 慢(需要序列化/反序列化和磁盘访问) 慢(需要磁盘访问或网络事务)

典型的做法如下:

Activity#onCreate中,通常会获取数据,显示到界面上,针对进入onCreate的情况,分为以下3种case

  1. Activity从未被初始化过
  2. Activity被初始化过,但处于后台时,App进程存活,Activity被杀死了,如配置更改的时候会发生这个情况
  3. Activity被初始化过,但处于后台时,App进程和Activity都被杀死了,如系统资源不足的时候会发生这个情况

针对于case1:我们会调用viewmodel#geNetworkNumber方法,从网络异步获取一个数值,并且这个数值缓存于viewmodel的某个livedata实例中

对于case2:App进程不销毁的情况下,viewmodel会持久存在,Activity重新创建的时候,viewmodel中的缓存的值也一直存在,我们直接拿来用即可,使用方式为:处于onSaveInstanceState回调时,将viewmodel缓存的值存储到Bundle中;处于onRestoreInstanceState时,从Bundle取出并设置到viewmodel上

对于case3:为了恢复App进程被framework杀死前的数据,我们必须使用Bundle跨进程,我们可以这样做:

第一步:onSaveInstanceState,将viewmodel缓存的值存储到Bundle中;

第二步:以下俩方法三选一(实现任意一个皆可以),只要能从Bundle中取值并调用viewmodel相应的数据更新setNumber方法即可

onCreate中判断Bundle是否为空

  • 为空:表明是首次创建
  • 不为空:表明被销毁过,从Bundle取值设置到viewmodel中

onRestoreInstanceState中从Bundle取出并设置到viewmodel上

SavedStateHandle

  • viewmodel的构造函数传入SavedStateHandle
  • viewmodel通过调用SavedStateHandle#getLiveData,使用这个livedata管理跨进程存储的数据。这份数据会在进程被系统终止后继续保存

Activity的具体用法:注意观察onCreate、onSaveInstanceState、onRestoreInstanceState的判断

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
	@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model_sample);
savedStateHandleViewModel = new ViewModelProvider(this).get(SavedStateHandleViewModel.class);
if (savedInstanceState != null) {
// 说明Activity重新创建了--有可能app进程被杀死,有可能只是Activity被杀死
Log.e(TAG, "onRestoreInstanceState: "+savedInstanceState.getInt(KEY_NUMBER) );
savedStateHandleViewModel.setNumber(KEY_NUMBER, savedInstanceState.getInt(KEY_NUMBER));
}else {
// 表明是第一次初始化,则在这里进行网络请求等异步耗时操作
// savedStateHandleViewModel#geNetworkNumber 这是伪代码示例
}
int count = savedStateHandleViewModel.getNumberObserver(KEY_NUMBER).getValue();
Log.e(TAG, "进程销毁前存储的数值是 : " + count);
}
@Override
protected void onSaveInstanceState(@androidx.annotation.NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_NUMBER, savedStateHandleViewModel.getNumberObserver(KEY_NUMBER).getValue());
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null) {
// 说明Activity重新创建了--有可能app进程被杀死,有可能只是Activity被杀死
Log.e(TAG, "onRestoreInstanceState: "+savedInstanceState.getInt(KEY_NUMBER) );
savedStateHandleViewModel.setNumber(KEY_NUMBER, savedInstanceState.getInt(KEY_NUMBER));
}
}

这是 SavedStateHandleViewModel的具体用法

写一个viewmodel实现类,构造函数接收SavedStateHandle

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class SavedStateHandleViewModel extends ViewModel {
/**
* 可序列化数据
*/
private SavedStateHandle handle;
/**
* 不可序列化数据
*/
private static File tempFile = null;
/**
* 无需任何其他配置。默认 ViewModel 工厂会向您的 ViewModel 提供相应的 SavedStateHandle
* 不需要开发者传入
* @param savedStateHandle
*/
public SavedStateHandleViewModel(SavedStateHandle savedStateHandle) {
this.handle = savedStateHandle;
Bundle tempFileBundle = savedStateHandle.get("temp_file");
if (tempFileBundle != null) {
tempFile = TempFileSavedStateProvider.restoreTempFile(tempFileBundle);
}
savedStateHandle.set("temp_file", new TempFileSavedStateProvider());
}

public MutableLiveData<Integer> getNumberObserver(String key) {
if (!handle.contains(key)) {
handle.set(key, 0);
}
return handle.getLiveData(key);
}

public void setNumber(String key, int value) {
getNumberObserver(key).setValue(value);
}
@NonNull
public File createOrGetTempFile() throws IOException {
if (tempFile == null) {
tempFile = File.createTempFile("temp", null);
}
return tempFile;
}

public static class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {
@NonNull
@Override
public Bundle saveState() {
Bundle bundle = new Bundle();
if (tempFile != null) {
bundle.putString("path", tempFile.getAbsolutePath());
}
return bundle;
}

@Nullable
public static File restoreTempFile(Bundle bundle) {
if (bundle.containsKey("path")) {
return new File(bundle.getString("path"));
}
return null;
}
}
}

初始化viewmodel,默认 ViewModel 工厂会向您的 ViewModel 提供相应的 SavedStateHandle

1
savedStateHandleViewModel = new ViewModelProvider(this).get(SavedStateHandleViewModel.class);

如果你想问支持存储哪些类型,可以看官方文档ViewModel 的已保存状态模块支持的类型的一节,里面提到直接支持的可序列化数据类型(像使用Bundle那样),间接支持的非序列化数据类型(实现接口SavedStateRegistry.SavedStateProvider

常见问题#

问题1:创建时机不对

1
Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.

不能在Activity、Fragment的oncreate方法之前创建viewmodel,如全局创建

问题2:配置发生变化,viewmodel会销毁重建吗

答:不会,rotated前后,NormalViewModel的地址空间一直是cc67d9d

1
2
3
4
5
6
7
8
9
10
11
com.shaunsheep.mvvm E/ViewModelSampleActivity: onCreate: null
com.shaunsheep.mvvm E/ViewModelSampleActivity: onStart: com.shaunsheep.mvvm.ViewModel.share.NormalViewModel@cc67d9d
com.shaunsheep.mvvm E/ViewModelSampleActivity: onResume: com.shaunsheep.mvvm.ViewModel.share.NormalViewModel@cc67d9d
com.shaunsheep.mvvm E/ViewModelSampleActivity: onPause: com.shaunsheep.mvvm.ViewModel.share.NormalViewModel@cc67d9d
com.shaunsheep.mvvm E/ViewModelSampleActivity: onStop: com.shaunsheep.mvvm.ViewModel.share.NormalViewModel@cc67d9d
com.shaunsheep.mvvm E/ViewModelSampleActivity: onSaveInstanceState: com.shaunsheep.mvvm.ViewModel.share.NormalViewModel
com.shaunsheep.mvvm E/ViewModelSampleActivity: onDestroy: com.shaunsheep.mvvm.ViewModel.share.NormalViewModel@cc67d9d
com.shaunsheep.mvvm E/ViewModelSampleActivity: onCreate: null
com.shaunsheep.mvvm E/ViewModelSampleActivity: onStart: com.shaunsheep.mvvm.ViewModel.share.NormalViewModel@cc67d9d
com.shaunsheep.mvvm E/ViewModelSampleActivity: onRestoreInstanceState: com.shaunsheep.mvvm.ViewModel.share.NormalViewMo
com.shaunsheep.mvvm E/ViewModelSampleActivity: onResume: com.shaunsheep.mvvm.ViewModel.share.NormalViewModel@cc67d9d

问题3:能在ViewModel持有COntext、Activity、Fragment吗

答:普通的ViewModel不可以;AndroidViewModel可以

疑问#

  1. 配置更改期间会自动保留VIewModel对象,为什么不会自动保留Activity的成员变量?会自动保留Activity的静态变量吗?
点击查看
-------------------本文结束 感谢您的阅读-------------------