Android Passive MVC 架構

今天我給大家介紹一個 Android 架構,原文請戳這裏

前言

MVC 架構想來大家都比較熟悉,M 指 Model,V 指 View,C 指 Controller。MVC 架構認爲程序可以分爲三個層次:

  • View 視圖層,最上面的一層,負責與用戶進行交互;
  • Model 數據層,最底層,負責數據的存取;
  • Controller 控制層,負責視圖層與數據層的交互,將所需的數據返回視圖層或者將視圖層的請求傳達到數據層。

MVC 架構三個模塊彼此獨立又相互聯繫,每一層都對外提供接口,供下層調用。MVC 架構符合“高內聚,低耦合”這一編程理念,而且提供了清晰的編程思路。既然 MVC 有這些好處,那在 Android 中如何應用呢?
以下代碼在 Activity 中是比較常見的:

Button loginBtn = (Button) findViewById(R.id.login_btn);
loginBtn.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    //do something
  }
});

上面的代碼在 Activity 中初始化一個按鈕,並且增加了一個監聽器,用來處理點擊事件。但是點擊事件的處理實際上歸屬於 Controller 層。Android Passive MVC 這篇論文指出,在 Android 中使用 MVC 架構是“被動的”,爲什麼這麼說呢?因爲在 Android,Activity 扮演的角色比較特別,Activity 既涉及到 View 層,又涉及到 Controller 層。看來在 Android 使用 MVC 架構不能生搬硬套,接下來我們一起來學習一下如何在 Android 使用 MVC 架構。

開始

從上面我們已經知道由於 Activity 涉及到兩個層次,所以要從 Activity 中抽離出來一個 Controller 層,並且所有的 Activity 合起來作爲一個特殊的層次,這就和MVC架構有所不同。來看一下它們之間的關係:



上面我們可以看到幾個模塊之間的關係,箭頭的方向表明數據的流向,或者模塊之間的監聽方向。可以看到 Controller 以及 Model 都提供了接口供 Activity 及 Controller 調用。接下來我們以一個登錄界面爲例來看一下 Android Passive MVC 架構是如何具體到代碼層面的。

先來看一下項目結構:

可以看到這裏只是簡單地建了幾個目錄。下面新建一個資源文件:activity_login,用來寫登錄界面,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<com.android.test.view.LoginView xmlns:android="http://schemas.android.com/apk/res/android"    
android:id="@+id/login_view"
android:orientation="vertical"    
android:layout_width="match_parent"    
android:layout_height="match_parent"    
android:background="@android:color/white">    
  <RelativeLayout        
    android:layout_width="match_parent"        
    android:layout_height="38dp"        
    android:layout_marginLeft="28dp"        
    android:layout_marginRight="28dp"        
    android:layout_marginTop="25dp" >        
      <EditText            
        android:id="@+id/username"            
        android:layout_width="match_parent"            
        android:layout_height="match_parent"            
        android:layout_centerVertical="true"            
        android:layout_marginLeft="11dp"            
        android:background="@null"            
        android:cursorVisible="true"            
        android:hint="用戶名"            
        android:textColorHint="@android:color/darker_gray"            
        android:maxLines="1"            
        android:textSize="18sp"            
        android:textColor="@android:color/darker_gray">            
        <requestFocus />        
      </EditText>        
      <View            
        android:layout_width="match_parent"            
        android:layout_height="1dp"            
        android:layout_alignParentBottom="true"            
        android:background="#C1D3EC"  />    
  </RelativeLayout>    
  <RelativeLayout        
    android:layout_width="match_parent"        
    android:layout_height="38dp"        
    android:layout_marginLeft="28dp"        
    android:layout_marginRight="28dp"        
    android:layout_marginTop="25dp" >        
      <EditText            
        android:id="@+id/password"           
        android:layout_width="match_parent"          
        android:layout_height="match_parent"            
        android:layout_centerVertical="true"            
        android:layout_marginLeft="11dp"            
        android:background="@null"            
        android:hint="密碼"            
        android:textColorHint="@android:color/darker_gray"            
        android:cursorVisible="true"            
        android:inputType="textPassword"            
        android:textSize="18sp"
        android:textColorHint="@android:color/darker_gray"/>        
      <View            
        android:layout_width="match_parent"            
        android:layout_height="1dp"            
        android:layout_alignParentBottom="true"            
        android:background="#C1D3EC" />    
   </RelativeLayout>    
      <Button        
        android:id="@+id/login_btn"        
        android:layout_width="match_parent"        
        android:layout_height="wrap_content"        
        android:layout_marginLeft="28dp"        
        android:layout_marginRight="28dp"        
        android:layout_marginTop="25dp"        
        android:background="#6fd66b"        
        android:gravity="center"        
        android:text="登錄"        
        android:padding="10dp"        
        android:textColor="#ffffff"        
        android:textSize="18sp" />
</com.android.test.view.LoginView>

一個很簡單的界面,兩個輸入框加一個按鈕。下面在 view 中新建一個文件 LoginView,用來綁定這個 xml 文件,做一些初始化的操作,並且要在提供監聽點擊事件,將事件傳遞到 Controller 進行處理(有的界面會在 Controller 處理完數據以後更新 View),這些就是 View 層所要完成的任務。下面來看看 LoginView 的代碼:

public class LoginView extends LinearLayout {   
  private EditText mUserId;   
  private EditText mPassword;   
  private Button mLoginBtn;   

public LoginView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public void initModule() {
mUserId = (EditText) findViewById(R.id.username);
mPassword = (EditText) findViewById(R.id.password);
mLoginBtn = (Button) findViewById(R.id.login_btn);
}

public void setListener(OnClickListener onClickListener) {
mLoginBtn.setOnClickListener(onClickListener);
}

public String getUserId() {
return mUserId.getText().toString().trim();
}

public String getPassword() {
return mPassword.getText().toString().trim();
}
}

接下來在 listener 包下新建一個 LoginListener,代碼如下:

public interface LoginListener {    
  public void loginSuccess();
}

接下來在 activity 中新建一個 LoginActivity,代碼如下:

public class LoginActivity extends Activity implements LoginListener {    
  private LoginView mLoginView;    
  private LoginController mLoginController;    

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mLoginView = (LoginView) findViewById(R.id.login_view);
//初始化登錄模塊
mLoginView.initModule();
//綁定 View 和 Controller,初始化 LoginController
mLoginController = new LoginController(mLoginView, this);
//將點擊事件傳遞到 Controller
mLoginView.setListener(mLoginController);
}

//登錄成功後,Controller 回調這個方法進行界面跳轉
@Override
public void loginSuccess() {
Intent intent = new Intent();
intent.setClass(this, MainActivity.class);
startActivity(intent);
finish();
}
}

Activity 在 Android Passive MVC 中被抽出一個 Controller 模塊,它的作用就是將 View 與 Controller 一一綁定,並且負責界面的跳轉。這裏要注意一點,Activity 不能決定什麼時候跳轉界面,這是 Controller 的職責,也就是說回調的時機是由 Controller 決定的。接着在 controller 包中新建一個類:LoginController,代碼如下:

public class LoginController implements View.OnClickListener {    
   private LoginView mLoginView;    
   private LoginListener mListener;    
   public LoginController(LoginView view, LoginListener listener) {        
      mLoginView  = view;        
      mListener = listener;    
   }    

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.login_btn:
String username = mLoginView.getUserId();
String password = mLoginView.getPassword();
//verify username and password
//…

    <span class="token comment">//if username and password is valid, do login                </span>
    <span class="token comment">//...                </span>
    <span class="token comment">//if login succeed                </span>
    mListener<span class="token punctuation">.</span><span class="token function">loginSuccess</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                
    <span class="token comment">//else do something                </span>
    <span class="token keyword">break</span><span class="token punctuation">;</span>        
<span class="token punctuation">}</span>    

}
}

以上就是一個簡單的登錄 demo 運用 Android Passive MVC 的實現,下面我們用一個圖來總結一下這幾個類之間的關係:

從上圖我們可以看到這幾個模塊之間相互調用的方向,有直接調用也有通過接口調用。比如,在 LoginActivity 中通過 Android 本地調用

mLoginView = (LoginView) findViewById(R.id.login_view);
mLoginView.initModule();

來初始化登錄模塊,接着調用

mLoginController = new LoginController(mLoginView, this);
mLoginView.setListener(mLoginController);

來綁定 LoginView 和 LoginController,這樣就能在 LoginController 中處理點擊事件,並且有時候在 Controller 中處理完數據後通過傳遞過來的 View(如 mLoginView 對象) 對象可以直接改變 UI,同時,在完成登錄後,如果返回成功,則直接回調 LoginActivity 中的 loginSuccess() 方法。

Android Passive MVC 領域模型架構

經過上面的學習我們已經大概知道了 Android Passive MVC 架構的各個模塊之間的作用和聯繫,下面我們從領域模型的角度來看看 Model 層各個模塊的結構。領域模型包含 Android 應用常見的組成部分如:數據庫,網絡服務,業務邏輯等。如下所示:

Domain Model Architecture

Business 層包括網絡服務模塊,業務服務模塊(包含業務邏輯),以及可以重用的工具類;Data 層包括實體類、數據存取對象及數據庫管理。

總結

到此爲止,我們已經看到了 Android Passive MVC 架構在實際開發中是如何運用的,我們再來回顧一下在 Android Passive MVC 架構中以下概念的職責:

  • View 用戶交互層,負責初始化控件,將事件傳遞到 Controller,以及界面的更新;
  • Activity 負責綁定 View 和 Controller,界面的跳轉;
  • Controller 負責數據的中轉,處理事件或請求,界面跳轉及更新的時機由 Controller 決定;
  • Model 數據層,負責數據的存取,業務邏輯的實現等。

下圖給出他們之間的關係:

Android Passive MVC 架構適合於中大型項目,使用這種架構具備較好的維護性及擴展性,高內聚、低耦合,保證了代碼的簡單和可測試性;組件的重用使得代碼更加清晰,縮短開發週期,View-Controller 組件能夠輕易地嵌入其它使用了 Android Passive MVC 架構的項目中;同時 Android Passive MVC 架構輕量化了 Activity 組件,接口的使用也稍微增加了響應速度。關於 demo 可以參考這個(去掉了 Listener 模塊,並且簡單的 Activity 沒有進行拆分)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章