写在前面的几句话
<p>
最近有打算写一些独立的App的打算,所以对现在的Android架构与技术的选择进行了重新的思考,抛开那些乱七八糟的东西,以一个极客的态度去选择。
首先我们来看看Android开发中的几大架构
一.MVC,MVP,MVVM的区分
<p>
1.MVC
传统的Android App其实都是基于MVC的,Activity,Fragment相当于C,布局相当于V,数据逻辑相当于M
随着业务的增长Controller里的代码会越来越臃肿,因为它不只要负责业务逻辑,还要控制View的展示。也就是说Activity、Fragment杂糅了Controller和View,耦合变大。并不能算作真正意义上的MVC。
这也是为什么后面的MVP会引起很多开发者兴趣的原因了。
2.MVP
MVP架构其实可以说与MVC的架构还是有很大的差别的,数据逻辑相当于M,Activity(负责View的绘制以及与用户交互)相当于V ,View于Model间的交互则为P
理论上感觉区别有点抽象,可以通过下面的图来看一看其中的区别
其实最明显的区别就是,MVC中是允许Model和View进行交互的,而MVP中很明显,Model与View之间的交互由Presenter完成。还有一点就是Presenter与View之间的交互是通过接口的
3.MVVM
MVVM是Model-View-ViewModel的简写. 它是有三个部分组成:Model、View、ViewModel。Model:数据模型层。包含业务逻辑和校验逻辑,View:屏幕上显示的UI界面(layout、views),ViewModel:View和Model之间的链接桥梁,处理视图逻辑。
当View有用户输入后,ViewModel通知Model更新数据,同理Model数据更新后,ViewModel通知View更新。
MVVM其实与MVP架构看起来很相似
这里可以可以看到 ViewModel 承担了 Presenter 中与 view和 Model 交互的职责,与 MVP模式不同的是,VM与 V 之间是通过 Datebinding 实现的,而 P是持有 View 的对象,直接调用 View 中的一些接口方法来实现。ViewModel可以理解成是View的数据模型和Presenter的合体。它通过双向绑定(松耦合)解决了MVP中Presenter与View联系比较紧密的问题,然而Android中的Datebinding只能单向绑定,只能从ViewModel绑定到View中
二 .MVP and MVVM代码对比
<p>
接下来就对同一功能用MVP与MVVM架构来实现,通过代码对比,分析到底选择哪一种比较好
代码很简单就用登录的简单逻辑
Mvp代码实现
首先看下结构
- 数据逻辑相当于M
- Activity(负责View的绘制以及与用户交互)相当于V
- View与Model间的交互则为P
从M开始
UserModel.Class
public class UserModel {
private String username;
private String password;
public UserModel(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int checkUserValidity(String username, String password) {
if (username == null || password == null ||
username.isEmpty() ||
password.isEmpty()) {
return -1;
}
return 0;
}
}
接下来是V
ILoginView.Class
public interface ILoginView {
void showProgress();
void hideProgress();
void setPasswordError();
String getUsername();
String getPassword();
void loginSuccess();
}
LoginActivity.Class
public class LoginActivity extends AppCompatActivity implements ILoginView,View.OnClickListener{
private EditText usernameEdit,passwrodEdit;
private Button loginButton;
ProgressDialog pd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvplogin);
pd = new ProgressDialog(this);
usernameEdit = (EditText) findViewById(R.id.et_username);
passwrodEdit = (EditText) findViewById(R.id.et_username);
loginButton = (Button) findViewById(R.id.bt_login);
loginButton.setOnClickListener(this);
}
@Override
public void showProgress() {
pd.show();
}
@Override
public void hideProgress() {
pd.cancel();
}
@Override
public void setPasswordError() {
passwrodEdit.setError("passwrod error");
}
@Override
public String getUsername() {
return usernameEdit.getText().toString();
}
@Override
public String getPassword() {
return passwrodEdit.getText().toString();
}
@Override
public void loginSuccess() {
Toast.makeText(this, "login success", Toast.LENGTH_SHORT).show();
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bt_login:
break;
}
}
最后就是P了
ILoginPresenter.Class
public interface ILoginPresenter {
void Login(String username, String password);
}
LoginPresenter.Class
public class LoginPersenter implements ILoginPresenter{
private ILoginView loginView;
private UserModel mUser;
public LoginPersenter(ILoginView loginView) {
this.loginView = loginView;
initUser();
}
private void initUser(){
mUser = new UserModel(loginView.getUsername(),loginView.getPassword());
}
@Override
public void Login(String username, String password) {
loginView.showProgress();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
loginView.hideProgress();
int code = mUser.checkUserValidity(loginView.getUsername(), loginView.getPassword());
if (code == -1) {
loginView.setPasswordError();
} else if (code == 0) {
loginView.loginSuccess();
}
}
},2000);
}
}
最后在LoginActivity中补上P的调用
//初始化
loginPresenter = new LoginPersenter(this);
//Click方法中的调用
loginPresenter.Login(usernameEdit.getText().toString(),passwrodEdit.getText().toString());
到这里MVP模式的代码就已经实现了
Mvvm代码实现
- Model:数据模型层。包含业务逻辑和校验逻辑
- View:屏幕上显示的UI界面(layout、views)
- ViewModel:View和Model之间的链接桥梁,处理视图逻辑。
从M开始
UserModel.Class
public class UserModel extends BaseObservable{
private String username;
private String password;
public UserModel(String username, String password) {
this.username = username;
this.password = password;
}
@Bindable
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
notifyPropertyChanged(BR.username);
}
@Bindable
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
notifyPropertyChanged(BR.password);
}
public int checkUserValidity() {
if (username == null || password == null ||
username.isEmpty() ||
password.isEmpty()) {
return -1;
}
return 0;
}
}
接下来是V
activity_mvvmlogin.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.netease.mvpormvvmdemo.mvvm.UserModel"/>
</data>
<LinearLayout
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/bt_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Login"
/>
</LinearLayout>
</layout>
最后是VM了
LoginActivity.Class
public class LoginActivity extends AppCompatActivity{
ActivityMvvmloginBinding binding;
ProgressDialog pd;
UserModel userModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvmlogin);
pd = new ProgressDialog(this);
binding.btLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
userModel = new UserModel(binding.etUsername.getText().toString(),binding.etPassword.getText().toString());
binding.setUser(userModel);
doLoign();
}
});
}
private void doLoign(){
pd.show();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
pd.cancel();
int code = userModel.checkUserValidity();
if (code == -1) {
binding.etPassword.setError("passwrod error");
} else if (code == 0) {
Toast.makeText(getBaseContext(), "login success", Toast.LENGTH_SHORT).show();
}
}
},2000);
}
}
到这里MVVM的代码也实现了
是不是看到这里觉得这不是很坑爹嘛?MVVM的写法和以前MVC的写法基本一样呀?这样还有什么意义?
确实一样,这是为什么呢?其实之前介绍的时候也有提到过Android中的Datebingding只能单向绑定,只能从ViewModel绑定到View中,所以呢View中数据的变化我们在ViewModel中并不能拿到,所以写法和MVC没有什么区别
但是我们可以从ViewModel绑定到View中,这里其实就有很大的变化了
那我们修改一下登陆逻辑,登录后清除界面元素信息,显示成功页或者失败页
MVP架构的代码就不书写了,和之前的是一样结构的,主要是来看下MVVM从ViewModel绑定到View中
首先修改下Model
public class UserModel extends BaseObservable{
private String username;
private String password;
private int status = 1;
@Bindable
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
notifyPropertyChanged(BR.username);
}
@Bindable
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
notifyPropertyChanged(BR.password);
}
@Bindable
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
notifyPropertyChanged(BR.status);
}
public void checkUserValidity() {
if (username == null || password == null ||
username.isEmpty() ||
password.isEmpty()) {
setStatus(-1);
}else {
setStatus(0);
}
}
}
这里主要是增加了一个新的属性为status,这个属性主要是用作判断当前界面的状态
接下来看看view的修改
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View"/>
<variable
name="user"
type="com.netease.mvpormvvmdemo.mvvm.UserModel"/>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="@{user.status == 1 ? View.VISIBLE : View.GONE}"
>
<EditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/bt_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Login"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="@{user.status == 0 ? View.VISIBLE : View.GONE}"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.username}"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.password}"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="@{user.status == -1 ? View.VISIBLE : View.GONE}"
>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="error"
/>
</LinearLayout>
</RelativeLayout>
</layout>
view内主要是修改为三个布局,根据status的状态进行变化
最后看看viewmodel的变化,viewmodel其实没有怎么变化,因为model里面小的修改了下
private void doLoign(){
pd.show();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
pd.cancel();
userModel.checkUserValidity();
}
},2000);
}
这里的代码只有这些
当我们输入后点击login后,model会对输入状态取检测,从而改变model里面的status属性,而通过ViewModel,view会及时拿到最新的状态,从而通过判断去做view层的变化。这就是MVVM的绑定机制了
看下运行截图
三.对比结论与分析
<p>
通过代码的对比,明显可以发现Mvp模式下的代码量相对来说确实增加了很多,但是逻辑相对的更加清晰,所以我觉得Mvp模式不是很适合小型的项目,小型项目整一堆类出来确实不是很好的事情,但是如果是一个较大型的项目还是可以选用这种架构来做开发,毕竟逻辑清晰,维护起来也比较方便。
而Mvvm的架构我个人觉得是Android往后发展的趋势,毕竟谷歌都推出了Datebinding,而使用Datebinding也就可以不用去使用bufferknife了,而且绑定的这种机制也确实带来了model,view与vm的分离,从逻辑上看也确实清晰了很多,问题是暂时只支持单向绑定,这个也要等谷歌后面的更新了,而且代码的阅读性会下降很多,所以呢这个比较时候小型的极客项目,暂时不适合大型的项目。
写在后面的几句话
其实架构性的东西在我看来没有一定的固定的写法,包括mvp和mvvm也可以根据自己的业务和理解去做不同的代码区分,像前阵子也有人提出了基于MVP与MVVM的mvvmp的架构,所有架构是死的,人是活的,记得面试听过一个大神说要跳出设计模式的圈圈,会有很多新的发现。。。共勉