Android MVP架构模式

Posted 保温杯拖把风扇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android MVP架构模式相关的知识,希望对你有一定的参考价值。

目录

概述

MVP模式是android常见的的一种架构模式,全称是Model、View、Presenter。其中,Model负责数据处理部分,View负责UI界面展示及用户操作交互,而Presenter则是负责大部分运行逻辑的编写。在Android中,常见的MVC架构模式一般是直接在Activity中实现运行逻辑,这样如果功能比较复杂,Activity的代码可读性将会很低,同时耦合性将会很高。与MVC模式相比,MVP模式断开了Model与View的直接交互,降低了代码的耦合性,也提高了代码的可复用性。

实例

本文以登录注册功能的编写演示MVP架构的实现方式。登录注册UI界面的编写可以参考这篇文章:Android开发:登录/注册界面的编写,在这里不再赘述。
Android中Activity一般充当View的角色,在Activity中实现对用户输入的检测以及交互;Presenter则负责将View传来的用户输入信息(UsernamePassword等)传给Model,由Model进行具体的数据判断,并将判断结果(用户是否存在、密码是否正确等)回传给Presenter,Presenter在收到Model的回传后,再调用View中的UI更新方法对界面进行更新,具体实现如下:

Model

我们编写一个LoginModelInterface接口用于表征Model,并新建一个LoginModel类实现LoginModelInterface接口。在LoginModelInterface中增加一个isUserValid(String username, String password)方法,并在LoginModel中对该方法进行实现:

public class LoginModel implements LoginModelInterface 
    @Override
    public boolean isUserValid(String username, String password) 
        // 在本方法中对用户输入的用户名及密码进行判断,并返回登录结果
        return false;
    

View

我们使用MainActivity充当View的角色。同样的,我们新建一个LoginViewInterface接口,在LoginViewInterface接口中编写两个方法:loginButtonClicked()以及showLoginResult()

public interface LoginViewInterface 
    void loginButtonClicked(String username, String password);
    void showLoginResult(boolean result);

MainActivity实现该接口,并实现上述两个方法:

@Override
public void loginButtonClicked(String username, String password) 
    mLoginPresenter.loginButtonClicked(username, password);


@Override
public void showLoginResult(boolean isValid) 
    if(isValid) 
        Toast.makeText(getApplicationContext(), "Login Success !", Toast.LENGTH_LONG).show();
     else 
        Toast.makeText(getApplicationContext(), "Login Failed !", Toast.LENGTH_LONG).show();
    

loginButtonClicked()方法中调用LoginPresenter的对应方法进行具体处理;而在showLoginResult()中根据LoginPresenter回传的结果设置具体的展示。为了方便演示,我们通过Toast的方式提示登录结果。

Presenter

同样的,新建一个LoginPresenterInterface接口用于表征Presenter,并新建一个LoginPresenter类实现上述接口,重写该接口中的loginButtonClicked()方法:

@Override
public void loginButtonClicked(String username, String password) 
    boolean isValid = mLoginModel.isUserValid(username, password);
    mLoginView.showLoginResult(isValid);

在该方法中,通过调用LoginModelisUserValid()方法进行用户输入的合法性判断,并调用LoginViewshowLoginResult()方法对UI界面进行更新,以此实现“中间人”的角色。

后记

  • 为什么每个类都要编写一个对应的接口?
    为了提高代码的可复用性。以LoginView为例,在LoginPresenter中,为了实现对LoginView中界面更新方法的调用,在LoginPresenter的构造方法中传入了LoginView的引用。如果不定义一个LoginViewInterface而是直接传入MainActivity,那么这个LoginPresenter只能与MainActivity一一对应,而如果传入的参数设置为LoginViewInterfce,那么只要让其他的View实现LoginViewInterface,这些View就能使用LoginPresenter,提高了代码的可复用性。
  • 目前只是初步实现了MVP架构,对传入引用可能导致的内存泄漏没有过多的关注,需要大家注意,后续有时间的话笔者也会针对这一块进行补充和完善。

全部代码

Model

  • LoginModelInterface
public interface LoginModelInterface 
    boolean isUserValid(String username, String password);

  • LoginModel
public class LoginModel implements LoginModelInterface 
    @Override
    public boolean isUserValid(String username, String password) 
        // 在本方法中对用户输入的用户名及密码进行判断,并返回登录结果
        return false;
    

View

  • LoginViewInterface
public interface LoginViewInterface 
    void loginButtonClicked(String username, String password);
    void showLoginResult(boolean isValid);

  • MainActivity
public class MainActivity extends AppCompatActivity implements LoginViewInterface 

    private Button mBtLogin;
    private EditText mEtUsername, mEtPassword;
    private LoginPresenterInterface mLoginPresenter;
    String TAG = "LoginPage";

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        // set click listener
        mBtLogin.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                //gain the content of usename and password,for further use
                String username = mEtUsername.getText().toString();
                String password = mEtPassword.getText().toString();
                //Tell Presenter that login button clicked
                loginButtonClicked(username, password);
            
        );
    

    @Override
    public void loginButtonClicked(String username, String password) 
        mLoginPresenter.loginButtonClicked(username, password);
    

    @Override
    public void showLoginResult(boolean isValid) 
        if(isValid) 
            Toast.makeText(getApplicationContext(), "Login Success !", Toast.LENGTH_LONG).show();
         else 
            Toast.makeText(getApplicationContext(), "Login Failed !", Toast.LENGTH_LONG).show();
        
    

    void init() 
        mBtLogin = findViewById(R.id.btn_login);
        mEtUsername = findViewById(R.id.et_username);
        mEtPassword = findViewById(R.id.et_password);
        mLoginPresenter = new LoginPresenter(this);
    


Presenter

  • LoginPresenterInterface
public interface LoginPresenterInterface 
    void loginButtonClicked(String username, String password);

  • LoginPresenter
public class LoginPresenter implements LoginPresenterInterface 
    private LoginViewInterface mLoginView;
    private LoginModelInterface mLoginModel;
    public LoginPresenter(LoginViewInterface loginView) 
        mLoginView = loginView;
        mLoginModel = new LoginModel();
    

    @Override
    public void loginButtonClicked(String username, String password) 
        boolean isValid = mLoginModel.isUserValid(username, password);
        mLoginView.showLoginResult(isValid);
    

Android架构篇--MVP模式的介绍篇

摘要: 在MVVM成熟之前MVP模式在Android上有被神化的趋势,笔者曾经在商业项目中从零开始大规模采用过MVP模式对项目进行开发。在使用MVP模式进行开发的时候发现项目的结构模式对开发是有一定的影响的,在这里笔者会对这一问题进行探讨。希望通过这篇blog能让读者了解如何使用MVP模式搭建一个功能完善的MVP模式开发框架,避免一些笔者认为比较严重的问题。

为什么要使用MVP模式

在传统的Android开发中,我们一般是使用MVC模式进行开发的。
传统MVC模式介绍:

  1. View: 视图层,对应xml文件
  2. Controller: 控制层,对应Activity和Fragment层,进行数据处理
  3. Model:实体层,负责获取实体数据

在Android开发中采用MVC模式的一个最大的弊端就是xml作为View层视图能力实在太弱,所以一般情况下我们都是通过Controller层来辅助处理一些视图的。这样的结果就导致Controller既作为控制层的同时又承担了View层的大部分功能,采用MVC模式往往会导致Activity和Fragment中的代码非常复杂。我们将Android中采用的MVC模式称为MV模式更加恰当。

MVP模式介绍:

  1. View: 视图层,对应xml文件与Activity/Fragment
  2. Presenter: 逻辑控制层,同时持有View和Model对象
  3. Model: 实体层,负责获取实体数据

MVP模式的流程图如下:
技术分享图片

MVP模式图解

采用MVP模式的优势是:

  1. 把业务逻辑抽离到Presenter层中,View层专注于UI的处理。
  2. 分离视图逻辑与业务逻辑,达到解耦的目的。
  3. 提高代码的阅读性。
  4. Presenter被抽象成接口,可以根据Presenter的实现方式进行单元测试。
  5. 可拓展性强。

采用MVP模式的缺点:

  1. 项目结构会对后期的开发和维护有一定的影响。具体视APP的体量而定。
  2. 代码量会增多,如何避免编写过多功能相似的重复代码是使用MVP开发的一个重点要处理的问题。
  3. 有一定的学习成本。

综上所述,在Android上采用MVP模式的优势是:大大优化代码的维护性与拓展性的同时对代码进行深度解耦,使各个层级的分工更加明晰。

Android上MVP模式的简单应用

先来看看一个简单用mvp模式模拟登陆的demo,下面的示例代码和其它简单介绍MVP模式的代码没有太大区别。如果有了解过的同学可以直接跳过看下一章关于如何优化MVP模式的结构的文章。

下面我们来看看在Android上用MVP模式实现简单的登录逻辑的方式:

. 登陆界面

技术分享图片

登陆界面

  1. 项目的结构:
    技术分享图片

    项目结构

从上面的代码结构图可看出,用MVP模式实现登陆模块需要创建6个文件,分别是M、V、P接口文件和接口的对应实现。其中LoginActivity就是View层的具体实现。这样的好处时Activity组件只需要负责处理UI相关逻辑就可以了,而相关的业务逻辑全部抽象到Presenter层中处理。通过这种方式能够很好的避免传统Android开发中的Activity/Fragment等UI组件既负责处理UI逻辑又处理业务逻辑的结果。

. 代码实现

说了这么多,最后我们来看看代码的实现吧。

  1. ILoginModel

    1
    2
    3
    public interface ILoginModel {
    void login(String name ,String password);
    }
  2. ILoginPresenter

    1
    2
    3
    4
    5
    6
    public interface ILoginPresenter {
     
    void loginToServer(String userName,String password);
     
    void loginSucceed();
    }
  3. ILoginView

    1
    2
    3
    4
    5
    6
    public interface ILoginView {
     
    void showProgress(boolean enable);
     
    void showLoginView();
    }

上面是登陆模块对应的MVP接口的具体设计,下面我来简单介绍一下接口中的几个方法:

  • ILoginModel.login(String name ,String password)登陆方法,通过该方法向服务器发送登陆请求。
  • ILoginPresenter. loginToServer (String name ,String password)通知Model响应登陆事件。
  • ILoginPresenter. loginSucceed()当登陆事件完成时(成功/失败),Model层要通知该方法登陆事件已完成。
  • ILoginView. showProgress(boolean enable)当Presenter层调用loginToServer (String name ,String password)方法时,要通过该方法通知View层显示加载动画。
  • ILoginView. showLoginView()登陆成功时,Presenter层会通过该方法通知View层登陆已成功。

下面我们来看看这几个接口的具体实现。

  1. LoginModel
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class LoginModel implements ILoginModel{
     
    private ILoginPresenter presenter;
     
    private Handler mHandler = new Handler();
     
    public LoginModel(ILoginPresenter presenter) {
    this.presenter = presenter;
     
    }
     
    @Override
    public void login(String name ,String password) {
    mHandler.postDelayed(new Runnable() {
    @Override
    public void run() {
    Log.d("LoginModel", "run: ");
    presenter.loginSucceed();
    }
    },2000);
    }
     
    }

上面的Model层实现了login(String name,Stringpassword)登陆方法,该方法的具体实现逻辑是通过线程休眠2秒来模拟网络登陆的过程,登陆成功后会通过LoginPresenter的loginSucceed()方法来通知Presenter层登陆结果。实际开发中我们需要根据具体的业务逻辑来实现该过程。

  1. LoginPresenter
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class LoginPresenter implements ILoginPresenter{
     
    private ILoginModel loginModel;
     
    private ILoginView loginView;
     
    public LoginPresenter(ILoginView loginView) {
    this.loginView = loginView;
    this.loginModel = new LoginModel(this);
    }
     
    @Override
    public void loginToServer(String userName, String password) {
    loginView.showProgress(true);
    loginModel.login(userName,password);
    }
     
    @Override
    public void loginSucceed() {
    loginView.showProgress(false);
    loginView.showLoginView();
    }
    }

从上面代码可以看出LoginPresenter的实现逻辑很简单,首先在构造方法中获取ILoginView对象并撞见ILoginModel对象。然后当View层调用loginToServer(String userName, String password)方法成功时,通知View层显示加载动画并调用ILoginModel层的login(String userName, String password)方法向服务器发送登陆请求。当登陆成功后(即Model层通知loginSucceed方法时)通过loginView.showProgress(false)方法通知View层隐藏加载动画,并通知View登陆成功。

  1. LoginActivity
    对于LoginActivity我们只需要关注其中的几个方法即可
    1
    2
    3
    4
    5
    6
    7
    loginBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    //模拟登陆,不需要账号密码
    loginPresenter.loginToServer("","");
    }
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public void showProgress(boolean enable) {
if (enable){
progressBar.setVisibility(View.VISIBLE);
loginLayout.setVisibility(View.GONE);
}else {
progressBar.setVisibility(View.GONE);
loginLayout.setVisibility(View.VISIBLE);
}
}
 
@Override
public void showLoginView() {
Toast.makeText(LoginActivity.this,"登陆功",Toast.LENGTHSHORT).show();
finish();
}

上面时实现了ILoginView接口的两个方法。
结合上面的代码可以看出,当点击登陆按钮的监听事件时,我们不需要关注业务逻辑,只需要调用loginPresenter.loginToServer("","");方法即可,然后根据实际情况实现View层中ILoginView接口的方法即可,这样达到了UI业务与逻辑完全分离的目的。






以上是关于Android MVP架构模式的主要内容,如果未能解决你的问题,请参考以下文章

Android架构篇--MVP模式的介绍篇

android MVP——mvp架构的应用和优化

Android 架构设计实现——MVP模式

Android开发架构模式MVP的简单思考

Android Mvp架构的小demo

Google 官方Android MVP架构实践