Android MVP

来源:互联网 发布:movist for mac 1.4.1 编辑:程序博客网 时间:2024/05/19 11:49

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对比 
下边用一张图来更清晰的对比两种逻辑 
MVP与MVC对比 
可以看到,二者最明显的区别就是,MVC允许View和Model进行数据交互,而MVP则是把View和Model的交互交给Presenter来完成,这样就使得Activity中的变的更轻,代码更加清爽简洁,耦合度也更低,便于后期的维护. 
通过代码来展示二者在设计上的一些区别 
3.代码设计 
1.包结构 这里通过一个模拟登录的例子来演示二者在设计上的异同,首先我们设计一下程序的包结构,一个好的包结构是程序的基石.贴出一张图来展示一下我的Demo程序包结构 
MVP Demo包结构 
是的,看上去很简单,就是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 基础类,提供异步请求,设置监听器方法等 * <p> * 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;/** * 异步框架类,该类主要实现将耗时操作放在异步线程里面操作,如页面发送请求等。 * <p/> * 发生请求成功: 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<Integer, WeakReference<BaseAsyncTask>> requestMap;    /**     * 构造方法     *     * @param context     */    private AsyncTaskManager(Context context) {        mContext = context;        mExecutorService = Executors.newFixedThreadPool(MAX_CONNECTIONS_NUM);        requestMap = new WeakHashMap<Integer, WeakReference<BaseAsyncTask>>();    }    /**     * 单例模式得到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<BaseAsyncTask>(mAsynctask));        } else {            NLog.e(tag, "the error is requestCode < 0");        }    }    /**     * 根据requestCode取消请求     *     * @param requestCode     */    public void cancelRequest(int requestCode) {        WeakReference<BaseAsyncTask> 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<Entry<Integer, WeakReference<BaseAsyncTask>>> it = requestMap.entrySet().iterator();            while (it.hasNext()) {                Entry<Integer, WeakReference<BaseAsyncTask>> entry = (Entry<Integer, WeakReference<BaseAsyncTask>>) 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

原创粉丝点击