简述
MVVM(Model-View-ViewModel)架构是用于构建用户界面的架构模式,它将UI和业务逻辑分离。
- Model:数据模型,用于存储和操作数据
- View:用户页面,用于展示数据和接收用户输入
- ViewModel:负责连接View与Model,负责数据的双向绑定与转换(核心思想)
MVVM衍生于MVC(Model-View-Controller)架构,但是MVC存在以下问题:
- 所有业务逻辑都在Controller中操作,复杂且不易维护
- DOM(Document Object Model)操作使性能降低
- Model发生变化时,需要主动更新View;用户操作使Model发生变化时,需要将数据更新到Model中,不易于维护。
由此,MVVM基于上述缺点,提出ViewModel以将View与Model直接通讯。即用户操作View时,ViewModel会感知到变化,同步给Model,反之同理。
然而没有事物是完美的,需要在特定的场景下确定到底是使用MVC架构还是MVVM架构:
- MVVM:大型或复杂的项目、项目周期长、需要跨平台、对性能要求不高等
- MVC:小型或简单的项目、项目周期短、不需要跨平台、性能要求高等
Model(模型)
负责处理数据和业务逻辑。独立于界面,可以在多个界面中共享,用于提供数据和处理数据的方法,封装业务逻辑。例如:
java public class User {
private String name;
private int age;
// getter and setter methods
// 数据获取的方法
public LiveData getUser() {
// 从网络或数据库获取用户数据
return userRepository.getUser();
}
// 数据更新的方法
public void updateUser(User user) {
// 更新用户数据
userRepository.updateUser(user);
}
// ...
}
View(视图)
负责展示数据并与用户交互。可以是Activity、Fragment、View等。例如:
java public class MainActivity extends AppCompatActivity {
private UserViewModel userViewModel;
private TextView nameTextView;
private TextView ageTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nameTextView = findViewById(R.id.nameTextView);
ageTextView = findViewById(R.id.ageTextView);
userViewModel = new ViewModelProvider(this).get(UserViewModel.class); userViewModel.getUser().observe(this, user -> {
// 更新UI显示
nameTextView.setText(user.getName());
ageTextView.setText(String.valueOf(user.getAge()));
});
}
// 处理用户输入的方法
public void onUpdateUserClick(View view) { // 从UI获取用户输入
String name = nameEditText.getText().toString();
int age = Integer.parseInt(ageEditText.getText().toString());
// 更新ViewModel中的数据
User user = new User(name, age);
userViewModel.updateUser(user);
}
// ...
}
ViewModel(视图模型)
负责连接View和Model。从Model中获取数据,转化为View可以直接使用的形式;监听Model的数据变化,通知View进行更新。Model与View通常一一对应,每个View都有一个对应的ViewModel。例如:
public class UserViewModel extends ViewModel {
private User user;
private UserRepository userRepository;
public UserViewModel() {
userRepository = new UserRepository();
user = new User();
}
// 获取数据的方法
public LiveData<User> getUser() {
return user.getUser();
}
// 更新数据的方法
public void updateUser(User user) {
user.updateUser(user);
}
// ...
}
如例所示,在ViewModel中可以使用LiveData实现将View与Model绑定的功能。
LiveData
LiveData是AndroidX中JetPack提供的一种响应式编程组件,可以包含任何数据,并在数据变化的时候通知观察者,以便实现View与Model的数据相绑定。
使用方式:
在过去方法中,一直都是在Activity中手动获取ViewModel中的数据。但是ViewModel无法将数据的变化同步给Activity中。并且ViewModel的生命周期长于Activity,因此不能将Activity的实例传递给ViewModel中(否则会造成内存泄漏),由此使用LiveData。如例:
public class MainViewModel extends ViewModel {
// MutableLiveData是一种可变的LiveData
public MutableLiveData<Integer> count = new MutableLiveData<>();
public MainViewModel(int count) {
this.count.setValue(count);
}
public void plusOne() {
if (count.getValue() == null) {
return;
}
count.setValue(count.getValue() + 1);
}
public void clean() {
count.setValue(0);
}
}
如上所示,count为一个MutableLiveData对象(可变的LiveData),其对应有三个读写数据方法,getValue、SetValue和PostValue,其中SetValue必须在主线程中使用。
在MainActivity中,示例如下:
private static final String TAG = "MainActivity";
private TextView textView;
private Button plusOneBtn;
private Button cleanBtn;
private MainViewModel viewModel;
private SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sp = getPreferences(Context.MODE_PRIVATE);
int count = sp.getInt("count_reserved", 1);
textView = findViewById(R.id.info_text);
plusOneBtn = findViewById(R.id.plus_one_btn);
cleanBtn = findViewById(R.id.clean_btn);
viewModel =new ViewModelProvider(this, new MainViewModelFactory(count)).get(MainViewModel.class);
plusOneBtn.setOnClickListener(v -> {
viewModel.plusOne();
});
cleanBtn.setOnClickListener( v -> {
viewModel.clean();
});
// 用来观察数据的变化,任何LiveData都可以调用它进行
viewModel.count.observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
textView.setText(String.valueOf(integer));
}
});
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause: viewModel.counter = " + viewModel.count);
SharedPreferences.Editor editor = sp.edit();
if (viewModel.count.getValue() == null) {
Log.d(TAG, "onPause: viewModel.count.getValue() is null");
editor.putInt("count_reserved", 0);
return;
}
editor.putInt("count_reserved", viewModel.count.getValue());
editor.apply();
}
如上所示,其中,点击事件内的逻辑处理换到了ViewModel内,添加了count的observe()方法,这个方法就是用来观察数据的变化。任何LiveData对象都可以调用它的observe()方法来观察数据的变化。