ViewModel简介
其实Android 平台上之所以会出现诸如MVP、MVVM之类的项目架构,就是因为在传统的开发模式下,Activity 的任务实在是太重 了,既要负责逻辑处理,又要控制UI展示,甚至还得处理网络回调,等等。在一个小型项目中 这样写或许没有什么问题,但是如果在大型项目中仍然使用这种写法的话,那么这个项目将会 变得非常臃肿并且难以维护,因为没有任何架构上的划分。
而ViewModel的一个重要作用就是可以帮助Activity分担一部分工作,它是专门用于存放与界面相关的数据的。也就是说,只要是界面上能看到的数据,它的相关变量都应该存放在ViewModel中,而不是Activity中,这样一定程度上可以减少Activity中的逻辑。
ViewModel可以帮助我们更好地将页面与数据从代码层面分离开来,依赖ViewModel的生命周期特性,我们不需要在关心屏幕旋转导致数据丢失的问题,需要注意的是,如果想在ViewModel中使用Context,建议使用ViewModel的子类AndroidViewModel。
1.视图与数据模型之间的桥梁ViewModel
ViewModel是视图View和数据模型Model之间的沟通桥梁 借助ViewModel, 视图 与 数据模型 实现了解耦,同时还能保证视图 与 数据模型之间 保持通信 这样Activity的代码量减少了,只需要维护视图View相关内容,增加了代码的可维护性。
- 在ViewModel架构中,数据不由View直接进行管理,而是ViewModel进行管理
- 当Activity屏幕旋转,销毁时,只会销毁Activity组件,不会将ViewModel以及数据模型Model销毁
- Activity中的组件获取数据时,不直接从数据模型Model中获取,而是从ViewModel架构组件中获取
- ViewModel作为不同的Activity或Fragment之间沟通的桥梁
1.1 ViewModel与onSaveInstanceState()方法
- ViewModel: ViewModel能支持页面中所有的数据,但是需要注意的是,ViewModel不支持数据的持久化,当页面被彻底销毁时,ViewModel及持有的数据就不存在了
- onSaveInstanceState()方法:只能保存少量的,能支持序列化的数据,但是onSaveInstanceState()方法可以持久化页面的数据。
2.ViewModel的生命周期
如图:
ViewModel的生命周期与Acitivity或Fragment的生命周期相互独立,ViewModel不受Activity组件销毁的影响。如果由于屏幕旋转原因导致的Activity销毁重建,与之绑定的ViewModel会在销毁时解绑,Activity重建时重新绑定。 ViewModel会在应用生命周期内存活,并且可以在Activity或Fragment之间共享数据
ViewModel的生命周期:一个ViewModel实例对象可以与多个Activity或Fragment绑定
- 创建:在Activity的首次启动时 创建ViewModel实例对象,如果Activity多次启动,ViewModel只会创建一次。
- 绑定:Activity与ViewModel关联时,开始绑定ViewModel,Activity组件中绑定ViewModel代码如下:
csharp
MainViewModel mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
- 解绑:当Activity或者Fragment被销毁时,与之绑定的ViewModel会与UI组件解绑
- 销毁:ViewModel关联的所有的Activity或Fragment全部销毁,则ViewModel实例对象也会被销毁
3.ViewModel使用
3.1 ViewModel使用注意事项
使用ViewModel 时,不要将Context 上下文对象传入ViewModel中,否则会导致内存泄漏。
如果要使用Context 上下文对象,则ViewModel 需要继承AndroidViewModel 类,在其构造函数中获取Application对象
AndroidViewModel 继承自ViewModel ,并接收Application 作为Context ,因为Application 会扩展Context:
scala
public class TimerViewModel extends AndroidViewModel {
public TimerViewModel(@NonNull Application application) {
super(application);
}
}
3.2 使用
3.2.1 创建一个类继承ViewModel
注意:一般情况下,每一个Activity 都应该对应一个ViewModel ,例如MainActivity ,对应的ViewModel 应该是MainViewModel.
csharp
public class MyViewModel {
public int count;
public MyViewModel(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
3.2.2 在MainActivity中
scala
public class MainActivity extends AppCompatActivity {
private TextView textView;
private MyViewModel myViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取布局组件
textView = findViewById(R.id.textView);
Button btn_add = findViewById(R.id.btn_add);
// 获取 ViewModel
myViewModel new ViewModelProvider(this).get(MyViewModel.class)
btn_add.setOnclickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
myViewModel.setNumber(myViewModel.getNumber() + 1);
textView.setText(String.valueOf(myViewModel.getNumber()));
}
})
// 组件中显示 ViewModel 中的内容
textView.setText(String.valueOf(myViewModel.getNumber()));
}
}
运行效果: 在屏幕旋转后,Activity重建,也不会影响到数据运行,自增操作不会被打断
3.3 向ViewModel传递参数
如果我们确实 需要通过构造函数来传递一些参数,应该怎么办呢?由于所有ViewModel 的实例都是通过 ViewModelP rovider 来获取的,因此我们没有任何地方可以向ViewModel 的构造函数中传递参 数。只需要借助ViewModelProvider.Factory就可以实现了。
现在的计数器虽然在屏幕旋转的时候不会丢失数据,但是如果退出程序之后再重新打开,那么 之前的计数就会被清零了。接下来我们就对这一功能进行升级,保证即使在退出程序后又重新 打开的情况下,数据仍然不会丢失。
首先新建一个MyViewModelFactory 类,实现ViewModelProvider.Factory接口,
可以看到,MyViewModelFactory 中接收了一个参数countReserved ,另外ViewModelProvider.Factory 接口要求我们必须实现 create() 方法,因此这里在 crate() 方法中我们创建了MyViewModel 的实例,并将countReserved 参数传了进去。为什么这里就可以创建MainViewModel 的实例呢?因为 create() 方法的执行时机和Activity的生命周期无关。
然后我们在界面上添加一个清零按钮,
最后修改MainActivity中的代码:
typescript
public class MainActivity extends AppCompatActivity {
private MyViewModel myViewModel;
private SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.textview);
Button btn_add = findViewById(R.id.btn_add);
Button btn_clear = findViewById(R.id.btn_clear);
sp = getPreferences(Context.MODE_PRIVATE);
int count_reserved = sp.getInt("count_reserved", 0);
Log.d("aaabbb", "onCreate: "+count_reserved);
//获取ViewModel
myViewModel = new ViewModelProvider(this, new MyViewModelFactory(count_reserved))
.get(MyViewModel.class);
btn_add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myViewModel.setNumber(myViewModel.getNumber() + 1);
textView.setText(String.valueOf(myViewModel.getNumber()));
}
});
btn_clear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myViewModel.setNumber(0);
textView.setText(String.valueOf(0));
}
});
textView.setText(String.valueOf(myViewModel.getNumber()));
}
@Override
protected void onPause() {
super.onPause();
Log.d("aaabbb", "onPause(): "+myViewModel.getNumber());
sp.edit().putInt("count_reserved", myViewModel.getNumber()).apply();
}
}
在 oncrate() 方法中,我们首先获取了SharedPreferences的实例,然后读取之前保存的计数值,如果没有读到的话,就使用0作为默认值。
接下来在ViewModelProvider 中,额外传入了一个MyViewModelFactory 参数,这里将读取到的计数值传给了MyViewModelFactory 的构造函数,注意,这一步是非常重要的,只有用这种写法才能将计数值最终传递给MyViewModel的构造函数。
然后我们在clear 的按钮中对计数器进行清零,并在 onPause() 方法中对当前的计数进行保存,这样可以保证不管程序是退出还是进入后台,计数器都不会丢失。
现在重新运行程序,点击数次+1 按钮,然后退出程序并重新打开,你会发现,计数器 的值是不会丢失的