GitHub地址:ProjectPatternStudy
基本Android项目都采用MVC、MVP、MVVM架构,个人认为软件架构没有绝对的优劣之分,大家都各有利弊。
- 如果页面比较单一,采用MVC也未尝不可;
- 如果需要稳定性高,解耦性强就可以选用MVP,使M层与V层分离,结构更清晰;
- 如果想尝鲜(其实已经有段时间了),少写接口,高效,也可以使用MVVM;
阮一峰《MVC,MVP 和 MVVM 的图示》总结的非常简练,这里相当于扩展了一下,对于不太懂的人可能会用处更大。
MVP-databinding:是使用MVP架构,但是布局使用databinding设置值,也是行之有效的一种,也可以满足你的需求。
MVC
Model-View-Controller,最常见的软件架构之一。
- 视图(View):用户界面。
- 控制器(Controller):业务逻辑
- 模型(Model):数据保存
如Avtivity
里的一个点击事件:
/**
* 将业务逻辑封装在Model里, 但C(Activity)层可以和Model直接交互, 交互完后根据显示结果来调整V层(如 显示数据)
*/
EssayModel essayModel = new EssayModel(LoadDataActivity.this);
essayModel.getEssay(3, new EssayModel.OnEssayListener() {
@Override
public void onSuccess(List<Essay> list) {
/**
* 直接使用list,得到List的逻辑都放在mode层
*/
if (list != null && list.get(0) != null) {
tvViewUpdata.setText("MVC 更新数据: " + list.get(0).getTitle());
}
}
@Override
public void onError() {
}
});
如果一个页面比较简单,只有简单的几个操作,也不会经常去改可以使用此方式;如果页面逻辑比较复杂,接口请求都有好几个,那么不建议使用MVC,因为代码会全部堆积在一个Activity里面,会显得非常之冗余。
MVP
MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。
通过P层将Model层与View层解耦,同时P与V、P与M可以相互通信。
下面举个登录的例子:
public class UserLoginActivity extends AppCompatActivity implements IUserLoginView {
@BindView(R.id.et_username)
EditText etUsername;
@BindView(R.id.et_password)
EditText etPassword;
@BindView(R.id.bt_login)
Button btLogin;
@BindView(R.id.bt_clear)
Button btClear;
@BindView(R.id.progress)
ProgressBar progress;
@BindView(R.id.activity_user_login)
RelativeLayout activityUserLogin;
private UserLoginPresenter userLoginPresenter = new UserLoginPresenter(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_login);
ButterKnife.bind(this);
setTitle("用户登录(MVP)");
initListener();
}
private void initListener() {
btLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
userLoginPresenter.login();
}
});
btClear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
userLoginPresenter.clear();
}
});
}
@Override
public String getUserName() {
return etUsername.getText().toString().trim();
}
@Override
public String getPassword() {
return etPassword.getText().toString().trim();
}
@Override
public void clearUserName() {
etUsername.setText("");
}
@Override
public void clearPassword() {
etPassword.setText("");
}
@Override
public void showLoading() {
progress.setVisibility(View.VISIBLE);
}
@Override
public void hindLoading() {
progress.setVisibility(View.GONE);
}
@Override
public void toMainActivity() {
Toast.makeText(this, "login success , to MainActivity!", Toast.LENGTH_SHORT).show();
}
@Override
public void showFailedError() {
Toast.makeText(this, "login failed!", Toast.LENGTH_SHORT).show();
}
}
/**
* Created by jingbin on 2016/11/3.
* Presenter是用作Model和View之间交互的桥梁,那么应该有什么方法呢?
* 其实也是主要看该功能有什么操作,比如本例,两个操作:login和clear。
*/
public class UserLoginPresenter {
// view
private IUserLoginView iUserLoginView;
// model
private UserBiz mUserBiz;
private Handler mHandler = new Handler();
public UserLoginPresenter(IUserLoginView iUserLoginView) {
this.iUserLoginView = iUserLoginView;
this.mUserBiz = new UserBiz();
}
public void login() {
//view
iUserLoginView.showLoading();
// model
mUserBiz.login(iUserLoginView.getUserName(), iUserLoginView.getPassword(), new OnLoginListener() {
@Override
public void loginSuccess(User user) {
// 需要在UI线程中执行
mHandler.post(new Runnable() {
@Override
public void run() {
iUserLoginView.toMainActivity();
iUserLoginView.hindLoading();
}
});
}
@Override
public void loginFailed() {
// 需要在UI线程中执行
mHandler.post(new Runnable() {
@Override
public void run() {
iUserLoginView.hindLoading();
iUserLoginView.showFailedError();
}
});
}
});
}
public void clear() {
iUserLoginView.clearUserName();
iUserLoginView.clearPassword();
}
}
public interface IUserLoginView {
// login说明了要有用户名、密码,那么对应两个方法:
String getUserName();
String getPassword();
void clearUserName();
void clearPassword();
// 再者login是个耗时操作,我们需要给用户一个友好的提示,一般就是操作ProgressBar,所以再两个:
void showLoading();
void hindLoading();
// login当然存在登录成功与失败的处理,我们主要看成功我们是跳转Activity,而失败可能是去给个提醒:
void toMainActivity();
void showFailedError();
}
用户点击登录,触发点击事件,然后通过P层userLoginPresenter
,调用登录的方法login()
,方法里面会通过Model层mUserBiz.login()
去做一些数据请求操作的处理,然后得到相应的数据返回。这里看到Model层的数据处理操作放在P层里,是不与V层直接交互的。
然后M层得到数据后回调,P层根据相应的数据,显示不同的UI,如toMainActivity
,showFailedError
等,这样V层只会出现一些基本的显示逻辑的处理。
MVVM
MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。
唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。
/**
* 简单的MVVM + data-binding案例:
* 以点击一下按钮然后年龄会+2 为例
*
* @author jingbin
*/
public class ChangeAgeActivity extends AppCompatActivity {
private ChangeAgeViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityChangeAgeBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_change_age);
setTitle("MVVM + data-binding");
viewModel = ViewModelProviders.of(this).get(ChangeAgeViewModel.class);
binding.setViewModel(viewModel);
binding.setButtonname("年龄+2");
/**监听年龄的变化*/
viewModel.desc.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String desc) {
Log.e("desc", desc);
}
});
binding.btAge.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewModel.change();
}
});
}
}
/**
* @author jingbin
*/
public class ChangeAgeViewModel extends AndroidViewModel {
final MutableLiveData<String> desc = new MutableLiveData<>();
public final ObservableField<String> age = new ObservableField<>();
public ChangeAgeViewModel(@NonNull Application application) {
super(application);
age.set(String.valueOf(23));
}
void change() {
String value = age.get();
if (!TextUtils.isEmpty(value)) {
Integer integer = Integer.valueOf(value);
// 改变age的值 布局里的值直接改变
age.set(String.valueOf(integer + 2));
desc.setValue("年龄改变:" + age.get());
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- View + ViewModel-->
<data>
<variable
name="viewModel"
type="com.example.jingbin.projectstru.mvvm.ChangeAgeViewModel" />
<variable
name="buttonname"
type="String" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="25dp">
<TextView
android:id="@+id/tv_mvvm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="年龄"
android:textColor="@color/colorPrimary" />
<TextView
android:id="@+id/tv_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="@{viewModel.age}" />
<Button
android:id="@+id/bt_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{buttonname}" />
</LinearLayout>
</layout>
可以看出,MVVM比MVP少了对应View的接口文件,这样更简洁了,而且,改变ViewModel里的值,则xml
文件对应的值也会对应改变。如果通过手动setText(),则ViewModel
里的值也会得到改变。通过这一层关系,我们可以通过数据去操控View
里的显示,所以才可以去除掉对应View的接口文件。
MVP-databinding
基本实现了MVC,MVP,MVVM后,我发现它们各自有各自的优缺点。
MVC:简单,单一页面可以实现。但是不利于复杂页面。
MVP:解耦,结构清晰。但文件较多,每一个页面基本要新建P层和V层的文件,同时还会有findViewById操作。
MVVM:解耦,结构相对清晰,文件相对MVP较少。但如果页面显示比较复杂,需要通过多个值去控制页面的显示,或者页面一个值的显示 要通过多种逻辑去处理得到结果,个人感觉还是不太适用。(其中的ViewModel与对应宿主的生命周期相同,从而内存泄漏问题比MVP处理较好这里先不做讨论)
MVP-databinding:
处理方式与MVP相同,只是使用了databinding的优势,databinding节省了类似findViewById和数据绑定的时间,从此代码里就没有findViewById和ButterKnife之类的代码了,而且也不会有通过多个值去控制页面的显示
这样不好操作的情况了。当然文件还是会多一些。
/**
* MVP + data-binding
*
* @author jingbin
*/
public class MvpDataBindingActivity extends AppCompatActivity implements ChangeAgeView {
private ActivityMvpDataBindingBinding binding;
private ChangeAgePresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_mvp_data_binding);
setTitle("MVP + data-binding");
presenter = new ChangeAgePresenter(this);
binding.setButtonname("年龄+2");
binding.btAge.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.changeAge(binding.getUser());
}
});
}
@Override
public void showContentView(UserBean user) {
binding.setUser(user);
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.clear();
}
}
/**
* @author jingbin
* @date 2019/02/26
*/
public class ChangeAgePresenter {
private ChangeAgeView changeInterface;
private UserModel userModel;
public ChangeAgePresenter(ChangeAgeView changeInterface) {
this.changeInterface = changeInterface;
// 初始化
changeInterface.showContentView(new UserBean("小白", 23));
}
/**
* 改变年龄
*/
public void changeAge(UserBean myUser) {
if (userModel == null) {
userModel = new UserModel();
}
userModel.changeAge(myUser, 2, new UserModel.ChangeInterface() {
@Override
public void success(UserBean user) {
changeInterface.showContentView(user);
}
});
}
public void clear() {
userModel = null;
}
}
参考资料
- 张鸿洋:浅谈 MVP in Android
- 阮一峰: MVC,MVP 和 MVVM 的图示
- Jensen: Android中的MVC和MVP(分析+实例)
- CSDN: 认清Android框架 MVC,MVP和MVVM
End
对应项目:ProjectPatternStudy 😁
此文仅个人总结,如有不当之处,请留言告知。
projectstru
├─ MainActivity.java
├─ mvc
│ ├─ LoadDataActivity.java
│ ├─ bean
│ │ └─ Essay.java
│ └─ model
│ ├─ EssayModel.java
│ └─ MainModel.java
├─ mvp
│ ├─ UserLoginActivity.java
│ ├─ bean
│ │ └─ User.java
│ ├─ model
│ │ ├─ IUserBiz.java
│ │ ├─ OnLoginListener.java
│ │ └─ UserBiz.java
│ ├─ presenter
│ │ └─ UserLoginPresenter.java
│ └─ view
│ └─ IUserLoginView.java
├─ mvpdatabindind
│ ├─ MvpDataBindingActivity.java
│ ├─ bean
│ │ └─ UserBean.java
│ ├─ model
│ │ └─ UserModel.java
│ ├─ presenter
│ │ └─ ChangeAgePresenter.java
│ └─ view
│ └─ ChangeAgeView.java
└─ mvvm
├─ ChangeAgeActivity.java
└─ ChangeAgeViewModel.java