01 架构介绍
先来看一下MVC、MVP、MVVM的架构图。
从这些架构图中,可以看到每种架构都有3个模块以及数据流动方向箭头。
模块
在系统架构中,首先要做的就是把系统整体按照一定的原则划分成模块。
数据流动
模块划分之后,模块之间的通信,就是数据的流动。在Android中,流动数据包括两部分,事件和数据。
架构
模块和模块之间的数据通信方式构成不同的架构。在这3种架构中,都是把系统整体划分成了3个模块:视图层,数据层,业务层。 他们之间的区别在于,模块之间的通信方式(数据流动方向)不一致。
- MVC是视图层接收到事件后调用到业务层处理业务逻辑,业务层调用数据层处理数据,数据层再调用视图层更新页面。
- MVP是视图层接收到事件后调用到业务层处理,业务层调用数据层处理数据,数据层处理数据后回调给业务层,业务层再回调给视图层更新页面。(数据层已不再持有视图层,他们之间通过业务层(Presenter)交互,具体使用接口实现,使数据层和视图层解耦。
- MVVM在MVP的基础上实现了视图层和业务层的双向数据绑定(data binding),不再通过接口的方式交互,ViewModel不在和Presenter一样持有视图层,使视图层和业务层解耦。
02 具体实现
MVC
视图层:在MVC架构中, Android的xml布局文件和Activity/Fragment文件被划分为View视图层。 因为xml作为视图层功能太弱,只能够实现页面的布局,不能够实现页面数据和事件的处理。需要和Activity一起才能够构成一个完整的视图层。
业务层:大多数的MVC架构开发的安卓项目, 并没有把Controller业务层独立出来,而是将业务层也在Activity/Fragment中实现。这导致了Activity/Fragment的代码非常臃肿,这就是MVC的缺点之一。 在本例中,我们会将业务层独立出来,实现一个标准的MVC架构。
数据层:数据层Model指的是,数据管理模块,这包括了数据的获取,处理。存储等。 MVP、MVVM的架构中的Model也是一样。后面不再赘述。
代码结构
xml代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_gallery_outer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<EditText
android:id="@+id/tv_account"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_marginLeft="16dp"
android:layout_marginTop="20dp"
android:layout_marginRight="16dp"
android:gravity="center"
android:hint="输入用户名" />
<EditText
android:id="@+id/tv_pwd"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:gravity="center"
android:hint="输入密码" />
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:gravity="center"
android:text="登录" />
</LinearLayout>
Activity代码
public class MVCActivity extends AppCompatActivity {
TextView tvResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
tvResult = findViewById(R.id.tv_result);
TextView tvAccount = findViewById(R.id.tv_account);
TextView tvPwd = findViewById(R.id.tv_pwd);
Button btnLogin = findViewById(R.id.btn_login);
MVCController mvcController = new MVCController();
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mvcController.login(tvAccount.getText().toString(),tvPwd.getText().toString(), MVCActivity.this);
}
});
}
public void loginSuccess(){
tvResult.setText("登录结果:登录成功");
}
public void loginFail(){
tvResult.setText("登录结果:登录失败");
}
}
Controller代码
public class MVCController {
MVCModel mvcModel;
public MVCController() {
mvcModel = new MVCModel();
}
public void login(String account, String pwd, MVCActivity loginActivity) {
mvcModel.login(account, pwd, loginActivity);
}
}
Model代码
public class MVCModel {
public void login(String account, String pwd, MVCActivity loginActivity){
if (account == null || account.length()==0) {
loginActivity.loginFail();
}
if (pwd == null || pwd.length()==0) {
loginActivity.loginFail();
}
if ("user123".equals(account) && "pwd123".equals(pwd)){
loginActivity.loginSuccess();
}
}
}
实现代码说明
在Activity中监听登录按钮的事件,接收到事件之后,调用Controller的登录方法处理登录逻辑,在Controller的登录方法中调用Model请求网络数据(这里是模拟)判断是否登录成功,Model拿到登录结果后,调用Activity的方法刷新页面数据,展示登录结果。
优缺点
优点:通过划分模块的方式,将系统分成了3个模块,视图层,业务层和数据层。 代码开发实现不再是只在一个代码文件中,一定程度便于程序开发。
缺点:但是三个模块之间还存在很强的耦合关系。 不利于业务需求的更变和代码维护工作。
MVP
MVP架构是基于MVC的改进,将MVC的中Controller独立出来作为Presenter。 xml和Activity还是作为视图层, 视图层接收到页面数据,调用Presenter进行业务逻辑处理,Presenter调用Model进行数据处理,Model回传数据给Presenter,Presenter回传数据给View。数据的回传通过接口回调的方式来实现。
代码结构
IModel接口代码
public interface IModel {
public boolean login(String account, String pwd);
}
IView接口代码
public interface IView {
public void loginSuccess();
public void loginFail();
}
Activity代码
public class MVPActivity extends AppCompatActivity implements IView {
TextView tvResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
tvResult = findViewById(R.id.tv_result);
TextView tvAccount = findViewById(R.id.tv_account);
TextView tvPwd = findViewById(R.id.tv_pwd);
Button btnLogin = findViewById(R.id.btn_login);
MVPPresenter presenter = new MVPPresenter();
presenter.setiView(this);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.login(tvAccount.getText().toString(), tvPwd.getText().toString());
}
});
}
public void loginSuccess() {
tvResult.setText("登录结果:登录成功");
}
public void loginFail() {
tvResult.setText("登录结果:登录失败");
}
}
Model代码
public class MVPModel implements IModel {
public boolean login(String account, String pwd) {
if (account == null || account.length() == 0) {
return false;
}
if (pwd == null || pwd.length() == 0) {
return false;
}
if ("user123".equals(account) && "pwd123".equals(pwd)) {
return true;
}
return false;
}
}
Presenter代码
public class MVPPresenter {
MVPModel model;
public MVPPresenter() {
model = new MVPModel();
}
IView iView;
public void setiView(IView iView) {
this.iView = iView;
}
public void login(String account, String pwd) {
boolean loginResult = model.login(account, pwd);
if (loginResult){
iView.loginSuccess();
}else {
iView.loginFail();
}
}
}
实现代码说明
定义了两个接口,IView和IModel, Activity和Model分别实现了这两个接口。 在Presenter中持有这两个实例。Presenter调用Model处理数据后,通过Iview的接口方法回调给Activity刷新页面。
优缺点
从上面的代码可以看到,三个模块之间的通信是通过接口实现的,在实际开发,定义的接口和方法会非常多。 导致很简单的一个页面功能也需要实现多个接口和方法。
优点就是通过Presenter,把MVC中的Controller代码抽出来了,并且Presenter作为View和Model通信的桥梁,完成了Model和View的解耦。
MVVM
MVVM在MVP的基础上加入了双向绑定,使View能够感知ViewModel中的数据变化,ViewModel能够感知View数据的变化。
代码结构
xml代码
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.domain.android.study.notes.architecture.mvvm.MVVMViewModel" />
</data>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_gallery_outer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.result}"
android:layout_gravity="center" />
<EditText
android:id="@+id/tv_account"
android:layout_width="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_height="40dp"
android:hint="输入用户名"
android:gravity="center"
android:text="@={viewModel.account}"
android:layout_gravity="center"
android:layout_marginTop="20dp" />
<EditText
android:id="@+id/tv_pwd"
android:layout_width="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_height="40dp"
android:hint="输入密码"
android:text="@={viewModel.pwd}"
android:gravity="center"
android:layout_gravity="center" />
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_height="40dp"
android:text="登录"
android:gravity="center"
android:layout_gravity="center" />
</LinearLayout>
</layout>
Activity代码
public class MVVMActivity extends AppCompatActivity {
MVVMViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityLoginBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_login);
viewModel = ViewModelProviders.of(this).get(MVVMViewModel.class);
binding.setVariable(BR.viewModel, viewModel);
binding.setLifecycleOwner(this);
binding.btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewModel.login();
}
});
}
}
ViewModel代码
public class MVVMViewModel extends ViewModel {
public ObservableField<String> account = new ObservableField<>("");
public ObservableField<String> pwd = new ObservableField<>("");
public ObservableField<String> result = new ObservableField<>("");
MVVMModel mvvmModel;
public MVVMViewModel() {
mvvmModel = new MVVMModel();
}
public void login(){
boolean loginResult = mvvmModel.login(account.get(), pwd.get());
result.set(loginResult ? "登录结果:成功" :"登录结果:失败");
}
}
Model代码
public class MVVMModel {
public boolean login(String account, String pwd) {
if (account == null || account.length() == 0) {
return false;
}
if (pwd == null || pwd.length() == 0) {
return false;
}
if ("user123".equals(account) && "pwd123".equals(pwd)) {
return true;
}
return false;
}
}
注意
在本例MVVM架构实现中,用到了Android提供的data binding这个数据双向绑定框架。需要在APP模块的gralde文件中添加以下配置开启:
android {
...
dataBinding {
enabled true
}
...
}
实现代码说明
通过Android提供的数据双向绑定库data binding 将Acitvity/xml视图层与ViewModel绑定。在xml布局文件中,通过@{}
来表示单向绑定或者@={}
来表示双向绑定。Activity接受到视图层的登录点击事件后,调用ViewModel处理登录业务逻辑,ViewModel通过双向数据绑定拿到到视图层输入的账号密码数据,调用Model处理数据,Model处理数据后,回传给ViewModel, ViewModel的数据改变,View感知后刷新页面。
注意
data binding通过观察者模式实现。 内部具体实现也是通过调用notify通知数据变化给观察者,notify调用了观察者实现的接口方法。
优缺点
优点:经过数据双向绑定之后,我们不在需要想MVP中写那么多接口回调方法区实现视图层和业务层的交互。业务层也不再持有视图层的引用。
缺点:通过这种方式进行数据双向绑定后,xml中会多出一些标签、表达式、甚至和业务有点的简单计算代码。这不利于业务的逻辑的查看。并且由于双向绑定是data binding实现的。在这个过程中, 如果出现bug导致数据没有被感知改变,不方便排错,因为xml不能debug调试。
03 总结
MVC、MVP、MVVM大体上都是把系统划分成3个模块:视图层、业务层、数据层。 但是他们的通信方式、数据流动方向不一致,形成了不同的架构。 其后面产生的架构都是为了更好的解耦,解决已有架构的不足。每个架构都有自己的优缺点,没有最好的架构,只有最合适的架构。
Android 知识点归整
Android 性能调优系列:https://qr18.cn/FVlo89
Android 车载学习指南:https://qr18.cn/F05ZCM
Android Framework核心知识点笔记:https://qr18.cn/AQpN4J
Android 音视频学习笔记:https://qr18.cn/Ei3VPD
Jetpack全家桶(含Compose):https://qr18.cn/A0gajp
Kotlin 入门到精进:https://qr18.cn/CdjtAF
Flutter 基础到进阶实战:https://qr18.cn/DIvKma
Android 八大知识体系:https://qr18.cn/CyxarU
Android 中高级面试题锦:https://qr18.cn/CKV8OZ
后续如有新知识点,将会持续更新,尽请期待……