Android Passive MVC 架构

作者: KenChoi | 来源:发表于2016-06-15 11:14 被阅读1033次

    今天我给大家介绍一个 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                
            //...                
    
            //if username and password is valid, do login                
            //...                
            //if login succeed                
            mListener.loginSuccess();                
            //else do something                
            break;        
        }    
      }
    }
    

    以上就是一个简单的登录 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 没有进行拆分)。

    相关文章

      网友评论

        本文标题:Android Passive MVC 架构

        本文链接:https://www.haomeiwen.com/subject/myttrttx.html