寫在前面的幾句話
<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的架構,所有架構是死的,人是活的,記得面試聽過一個大神說要跳出設計模式的圈圈,會有很多新的發現。。。共勉