Android MVP

Android MVP架构和MVC架构比较 
代码示例请点击点击下载demo 
1.概述 如题,本文想要讨论的是MVP与MVC之间的比较,那么在这之前,我们首先来回顾一下MVC的概念.MVC我们再熟悉不过,即Model-View-Controllor,对应于Android项目结构如下

             - Model对应于业务逻辑和实体类
             - View对应于xml布局文件
             - Controller对应于Activity

 
 
   
   
   
   
  • 1
  • 2
  • 3
  • 4

这里我们的View布局文件只做了界面的展示工作,做的工作其实并不多,当然现在Android有了MVVM来解决这个问题,让View变的更加强大,我们这里暂且不做讨论.而界面的大部分显示逻辑和数据处理逻辑都是在Activity中完成的(类似于ios中的ViewControllor类),这就使我们的Activity即像Controllor又像View,导致Activity中的代码变的异常复杂,我本人以前接手的一个项目MainActivity中有超过3000行的代码,导致后期根本无法维护,只能重写来解决.

而随着MVP架构的出现,我们可以将以往在Activity中完成的数据展示逻辑到Presenter中完成,对应的架构就变成了下边这样

             - Model不变对应于业务逻辑和实体类
             - View对应于Activity,负责界面的展示
             - Presenter,负责业务逻辑处理,来连接Model和View

 
 
   
   
   
   
  • 1
  • 2
  • 3
  • 4

2.MVP与MVC对比 
下边用一张图来更清晰的对比两种逻辑 
Android MVP_第1张图片 
可以看到,二者最明显的区别就是,MVC允许View和Model进行数据交互,而MVP则是把View和Model的交互交给Presenter来完成,这样就使得Activity中的变的更轻,代码更加清爽简洁,耦合度也更低,便于后期的维护. 
通过代码来展示二者在设计上的一些区别 
3.代码设计 
1.包结构 这里通过一个模拟登录的例子来演示二者在设计上的异同,首先我们设计一下程序的包结构,一个好的包结构是程序的基石.贴出一张图来展示一下我的Demo程序包结构 
Android MVP_第2张图片 
是的,看上去很简单,就是model-view-presenter,让人一目了然.接下来我们来分析一下具体的业务逻辑,并且来编写代码. 
1.Action层 首先是实体类,这个必不可少,不做讨论

/**
 * 登录返回用户信息
 * Created by shidong on 15/11/26.
 */
public class UserInfo implements Serializable {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
 
 
   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

2.Action中的登录操作,我们就叫login()方法

/**
 * 具体实现登录操作(项目中一般是网络请求返回数据)
 * Created by shidong on 15/11/26.
 */
public class LoginAction {
    public UserInfo login() {
        UserInfo userInfo = new UserInfo();
        userInfo.setName("dong");
        userInfo.setAge(20);
        return userInfo;
    }
}
 
 
   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

好了,到这里我们的action层已经编写完了,跟以往的写法没有半点区别. 
2.Presenter层 
可以试想一下,我们的登录操作通常会有什么呢,无非就是在异步线程中请求登录操作,即上边的LoginAction,然后处理登录成功和失败的逻辑.那么好,我们的登录功能接口定义大概就是下边这样子

/**
 * 登录接口
 * Created by shidong on 15/11/26.
 */
public interface OnLoginListener {
    //登录成功
    public void loginSuccess(UserInfo userInfo);
    //登录失败
    public void loginFailure();
}
 
 
   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

为了演示方便,暂且只定义这两个方法. 
现在应该设计我们的Presenter类了,嗯,很显然,我们需要有一个login()方法,这个login调用LoginAction中的方法完成登录请求.应该是这样的

 public void login() {
        request(REQ_LOGIN);
    }
 
 
   
   
   
   
  • 1
  • 2
  • 3

嗯?request()方法是什么东东,好了,接下来继续分析.别忘了,这些请求一定是在异步线程中实现的,耗时操作都是需要这个异步请求操作的,Android已经为我们提供了AsyncTask,我们来对其进一步封装.既然是公用的,我们需要定义一个接口来完成上述操作,接口中方法可以仿照AsyncTask类设计如下

/**
 * [异步请求监听]
 * 
 * @author mashidong
 * @version V3.6.0
 * @date 2015-11-25
 * 
 **/
public interface OnDataListener {

    /**
     * 耗时操作将在该方法中实现,比如发网络请求等
     * @param requestCode 请求code
     * @return
     * @throws HttpException
     */
    public Object doInBackground(int requestCode) throws HttpException;

    /**
     * 请求成功回调方法
     * @param requestCode 请求code
     * @param result 结果
     */
    public void onSuccess(int requestCode, Object result);

    /**
     * 请求失败回调方法
     * @param requestCode 请求code
     * @param state 状态
     * @param result 结果
     */
    public void onFailure(int requestCode, int state, Object result);
}
 
 
   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

之后我们的每个Presenter都需要实现这个接口来完成异步请求任务,谁都不想每次都去写重复的代码,显然我们需要定义一个BasePresenter来完成这些操作.

package com.example.shidong.androidmvp.presenter;

import com.example.shidong.DemoApplication;
import com.example.shidong.network.async.AsyncTaskManager;
import com.example.shidong.network.async.OnDataListener;
import com.example.shidong.network.http.HttpException;
import com.example.shidong.network.utils.NToast;

/**
 * Presenter 基础类,提供异步请求,设置监听器方法等
 * 

* Created by shidong on 15/11/26. */ public abstract class BasePresenter<T> implements OnDataListener { private AsyncTaskManager mAsyncTaskManager; public BasePresenter() { //构造方法中初始化AsyncTaskManager this.mAsyncTaskManager = AsyncTaskManager.getInstance(DemoApplication.application); } /** * 发送请求,默认是需要检查网络的 * * @param requsetCode 请求code */ public void request(int requsetCode) { request(requsetCode, true); } /** * 发送请求 * * @param requsetCode 请求code * @param isCheckNetwork 是否需要检查网络, true需要,false不需要 */ public void request(int requsetCode, boolean isCheckNetwork) { mAsyncTaskManager.request(requsetCode, isCheckNetwork, this); } /** * 取消请求 * * @param requsetCode */ public void cancelRequest(int requsetCode) { mAsyncTaskManager.cancelRequest(requsetCode); } /** * 取消所有请求 */ public void cancelRequest() { mAsyncTaskManager.cancelRequest(); } /** * 设置listener * * @param listener */ public abstract void setListener(T listener); @Override public Object doInBackground(int requestCode) throws HttpException { return null; } @Override public void onSuccess(int requestCode, Object result) { } @Override public void onFailure(int requestCode, int state, Object result) { switch (state) { // 网络不可用给出提示 case AsyncTaskManager.HTTP_NULL_CODE: NToast.shortToast(DemoApplication.application, "网络不可用"); break; // 网络有问题给出提示 case AsyncTaskManager.REQUEST_ERROR_CODE: break; case AsyncTaskManager.HTTP_ERROR_CODE: NToast.longToast(DemoApplication.application, "网络连接错误"); break; } } }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

其中我们定义一个AsyncTaskManager来对异步请求进行管理.具体见代码


package com.example.shidong.network.async;

import android.content.Context;
import android.os.Build;

import com.example.shidong.network.utils.NLog;

import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


/**
 * 异步框架类,该类主要实现将耗时操作放在异步线程里面操作,如页面发送请求等。
 * 

* 发生请求成功: REQUEST_SUCCESS_CODE = 200 * 发生请求失败: REQUEST_ERROR_CODE = -999 * 网络有问题: HTTP_ERROR_CODE = -200 * 网络不可用: HTTP_NULL_CODE = -400 * * @author mashidong * @version V3.6.0 * @date 2015-11-25 **/ public class AsyncTaskManager { /** * 日志对象 **/ private final String tag = AsyncTaskManager.class.getSimpleName(); /** * 发生请求成功 **/ public static final int REQUEST_SUCCESS_CODE = 200; /** * 发生请求失败 **/ public static final int REQUEST_ERROR_CODE = -999; /** * 网络有问题 **/ public static final int HTTP_ERROR_CODE = -200; /** * 网络不可用 **/ public static final int HTTP_NULL_CODE = -400; /** * 默认下载请求码 **/ public static final int DEFAULT_DOWNLOAD_CODE = 10000; /** * 线程池最多线程数 **/ public final int MAX_CONNECTIONS_NUM = 10; private Context mContext; private static AsyncTaskManager instance; private static ExecutorService mExecutorService; private static Map> requestMap; /** * 构造方法 * * @param context */ private AsyncTaskManager(Context context) { mContext = context; mExecutorService = Executors.newFixedThreadPool(MAX_CONNECTIONS_NUM); requestMap = new WeakHashMap>(); } /** * 单例模式得到AsyncTaskManager实例对象 * * @param context * @return */ public static AsyncTaskManager getInstance(Context context) { if (instance == null) { synchronized (AsyncTaskManager.class) { if (instance == null) { instance = new AsyncTaskManager(context); } } } return instance; } /** * 发送请求, 默认是需要检查网络的 * * @param requestCode 请求code * @param listener回调 */ public void request(int requestCode, OnDataListener listener) { request(requestCode, true, listener); } /** * 发送请求 * * @param requestCode 请求code * @param isCheckNetwork 是否需要检查网络 * @param listener 回调 */ public void request(int requestCode, boolean isCheckNetwork, OnDataListener listener) { DownLoad bean = new DownLoad(requestCode, isCheckNetwork, listener); if (requestCode > 0) { BaseAsyncTask mAsynctask = new BaseAsyncTask(bean, mContext); //after version 2.3 added executeOnExecutor method. //before 2.3 only run five asyntask, more than five must wait if (Build.VERSION.SDK_INT >= 11) { mAsynctask.executeOnExecutor(mExecutorService); } else { mAsynctask.execute(); } requestMap.put(requestCode, new WeakReference(mAsynctask)); } else { NLog.e(tag, "the error is requestCode < 0"); } } /** * 根据requestCode取消请求 * * @param requestCode */ public void cancelRequest(int requestCode) { WeakReference requestTask = requestMap.get(requestCode); if (requestTask != null) { BaseAsyncTask request = requestTask.get(); if (request != null) { request.cancel(true); request = null; } } requestMap.remove(requestCode); } /** * 取消所有请求 */ public void cancelRequest() { if (requestMap != null) { Iterator>> it = requestMap.entrySet().iterator(); while (it.hasNext()) { Entry> entry = (Entry>) it.next(); Integer requestCode = entry.getKey(); cancelRequest(requestCode); } requestMap.clear(); } } }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165

这回好了,异步请求方法有了,我们就大胆的在Presenter中完成我们的操作吧.

package com.example.shidong.androidmvp.presenter;

import com.example.shidong.androidmvp.presenter.impl.OnLoginListener;
import com.example.shidong.androidmvp.module.action.LoginAction;
import com.example.shidong.androidmvp.module.model.UserInfo;
import com.example.shidong.network.http.HttpException;

/**
 * 登录Presenter 实现登录逻辑
 * Created by shidong on 15/11/26.
 */
public class LoginPresenter extends BasePresenter<OnLoginListener> {

    private static final int REQ_LOGIN = 0x0a;
    private OnLoginListener loginListener;

    @Override
    public void setListener(OnLoginListener listener) {
        this.loginListener = listener;
    }

    public void login() {
        request(REQ_LOGIN);
    }

    @Override
    public Object doInBackground(int requestCode) throws HttpException {
        switch (requestCode) {
            case REQ_LOGIN:
                LoginAction action = new LoginAction();
                return action.login();
            default:
                return null;
        }
    }

    @Override
    public void onSuccess(int requestCode, Object result) {
        super.onSuccess(requestCode, result);
        switch (requestCode) {
            case REQ_LOGIN:
                if (result != null) {
                    UserInfo userInfo = (UserInfo) result;
                    if (loginListener != null) {
                        loginListener.loginSuccess(userInfo);
                    }
                }
            default:
                break;
        }
    }

    @Override
    public void onFailure(int requestCode, int state, Object result) {
        super.onFailure(requestCode, state, result);
    }
}
 
 
   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

ok,到这里,我们所有的异步请求逻辑已经完成的,好像还少点什么,不错,最后要在我们的View中调用请求方法来完成显示 
3.Activity设计

package com.example.shidong.androidmvp.view;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;

import com.example.shidong.androidmvp.R;
import com.example.shidong.androidmvp.presenter.impl.OnLoginListener;
import com.example.shidong.androidmvp.module.model.UserInfo;
import com.example.shidong.androidmvp.presenter.LoginPresenter;
import com.example.shidong.network.utils.NToast;

public class LoginActivity extends AppCompatActivity implements OnLoginListener {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_mvp);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        //MVP模式
        findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //登录请求
                LoginPresenter presenter = new LoginPresenter();
                presenter.setListener(LoginActivity.this);
                presenter.login();
            }
        });

        //MVC模式
        findViewById(R.id.btn_test_mvc).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LoginActivity.this.startActivity(new Intent(LoginActivity.this, MVCLoginActivity.class));
            }
        });
    }

    //登录成功
    @Override
    public void loginSuccess(UserInfo userInfo) {
        NToast.longToast(this, "登录成功" + userInfo.toString());
    }

    @Override
    public void loginFailure() {
        NToast.longToast(this, "登录失败");
    }
}

 
 
   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

至此,我们的整个流程已经完成,Activity中只有区区几十行代码,处理几个回调方法即可,是不是很清爽,而且所有的逻辑都做了很好的分层,便于代码阅读和后期维护.当然这种写法带来的代价就是会增加很多类和接口的定义,所以我本人建议适当使用,即能用则用,简单的逻辑可以不用.比如上述登录请求,直接用MVC来完成是下边这样

package com.example.shidong.androidmvp.view;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;

import com.example.shidong.androidmvp.R;
import com.example.shidong.androidmvp.module.action.LoginAction;
import com.example.shidong.androidmvp.module.model.UserInfo;
import com.example.shidong.network.async.AsyncTaskManager;
import com.example.shidong.network.async.OnDataListener;
import com.example.shidong.network.http.HttpException;
import com.example.shidong.network.utils.NToast;

public class MVCLoginActivity extends AppCompatActivity implements OnDataListener {

    private static final int REQ_MVCLOGIN = 0x0b;
    private AsyncTaskManager mAsyncTaskManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvclogin);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mAsyncTaskManager = AsyncTaskManager.getInstance(this);
        findViewById(R.id.btn_mvclogin).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                request(REQ_MVCLOGIN);
            }
        });

    }

    /**
     * 发送请求,默认是需要检查网络的
     *
     * @param requsetCode 请求code
     */
    public void request(int requsetCode) {
        request(requsetCode, true);
    }

    /**
     * 发送请求
     *
     * @param requsetCode    请求code
     * @param isCheckNetwork 是否需要检查网络, true需要,false不需要
     */
    public void request(int requsetCode, boolean isCheckNetwork) {
        mAsyncTaskManager.request(requsetCode, isCheckNetwork, this);
    }

    /**
     * 取消请求
     *
     * @param requsetCode
     */
    public void cancelRequest(int requsetCode) {
        mAsyncTaskManager.cancelRequest(requsetCode);
    }

    /**
     * 取消所有请求
     */
    public void cancelRequest() {
        mAsyncTaskManager.cancelRequest();
    }


    @Override
    public Object doInBackground(int requestCode) throws HttpException {
        LoginAction action = new LoginAction();
        return action.login();
    }

    @Override
    public void onSuccess(int requestCode, Object result) {
        UserInfo userInfo = (UserInfo) result;
        NToast.longToast(this, userInfo.toString());
    }

    @Override
    public void onFailure(int requestCode, int state, Object result) {

    }
}

 
 
   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

所有的请求和显示逻辑在Activity中完成,目测比MVP写法多了一倍的代码,还好我们已经将异步请求逻辑封装,否则可能会更多; 
这样两种方法我们都可以运用自如, 在适当的时候选择适当的方法来完成.

好了,到这里已经写完我想要写的东西,个人水平有限,可能理解不够深刻,望多多讨论和提出更好建议.最后感谢hongyang大神的文章,让我进一步完善了项目总的请求封装逻辑.

参考文章 http://blog.csdn.net/lmj623565791/article/details/46596109 
完整Demo代码请点击 http://download.csdn.net/detail/ronaldong99/9303337

你可能感兴趣的:(Andorid架构,android,mvp,mvc,架构)