Android开发中的MVP模式详解

在Android开发中,我们通常会去将项目分成一个个的模块文件夹,来进行管理维护,有的人是直接按照功能来分模块,这也是最常见的,有的人则会按照一定的设计模式,再结合功能来进行项目模式设计,比如MVP、MVVM这两种目前比较流行的项目设计模式,本文主要讲解MVP模式。

MVC、MVP、MVVM

MVC

对于MVC我想大家应该都不陌生,最典型的MVC就是JSP + servlet + javabean的模式了。


MVC1
MVC2

这两张是我从百度中截来的图(原谅我太懒,不想画图),从中很容易看出MVC的大致流程,用户通过操作View来发送用户请求;Controller接收到 用户请求 后,负责决定应该调用哪个Model来进行处理;然后Model根据用户请求进行相应的业务逻辑处理,并返回数据;最后Controller调用相应的视图View来显示Model返回的数据。

MVP

MVP

首先,我们来看一下上图(没错,又是从百度上抠下来的?_?),View发送指令给PresenterPresenter获取指令后,调用响应的Model进行业务逻辑处理,然后返回数据给PresenterPresenter根据Model返回的数据再来调用相应的View

MVVM

MVVM

看上图(盗图狂魔就是我@_@),在MVVM中有个ViewModel,它的作用就是与View进行双向绑定,当View或者ViewModel有一方变动时,另一方也会跟着改变,其实就是观察者模式,同时ViewModel也会处理一些轻量的业务逻辑,具有和MVP中的Presenter的一些类似功能。当用户对View进行操作时,ViewModel就会直接收到指令,然后调用Model处理业务逻辑,当Model返回数据给ViewModel时,因为ViewModelView双向绑定的缘故,ViewModel接收到数据后,View也会跟着改变,省去了ViewModel特意调用View来改变View的状态这一步骤!有兴趣的同学,可以去体验一下Android Studio中的databinding简单感受一下。

为什么用MVP

在Android中,对于Activity并没有明确的说它是属于View还是Controller的范畴,Activity既有View的性质,也具有Controller的性质,所以导致MVC在Android中很难明确分工使用,导致Activity很重。而且MVC中View会与Model直接交互,所以Activity与Model的耦合性很高,当后期维护时,稍有变动,可能Model、Activity、XML都会跟着改变,工作量很大,成本太高。

而MVP与MVC最大的不同之处是,MVP将M与V分隔开来,通过P交互,这样在Android中,就可以明确的把Activity当作View处理,虽然可能还有一点逻辑在其中,但是已经无伤大雅;View和Model不直接交互,当View有变动或者Model有变动时,不会相互影响,有太大变动,,耦合性低,对于后期维护来说,特别是项目越来越庞大时,可以很快的理清项目结构,找到需要修改的地方,大大的缩短了工作量。而且,因为View与Model分离的缘故,Model可以单独进行单元测试。

对于MVVM,其实很多框架中都使用到了它,比如说Vue.js、AngularJs都使用到了这种模式,在Android中也有DataBinding这个原生插件可以使用,来达到双向绑定的作用,但只是使用了DataBinding并不是完全的MVVM模式,个人认为还必须有个中间层类似与Presenter一样的层,来处理一些交互;说实话,在Android中使用了MVVM后,的确大大的提高了开发效率,省去了很多代码,但是如果只是使用纯粹的MVVM,当项目变得庞大后,还是有些吃不消的,各种改动的工作量不是闹着玩的,所以个人认为MVVM只是适合中小型项目,对于大项目还是有点吃紧的。

所以,最后个人认为如果你的项目会越来越庞大,但是又想体验MVVM那种便利,不妨试试MVP+DataBinding,其实这就有点类似于MVPVM模式了,方便快捷,即使项目庞大,改动时也不需要太多重构。

MVPLoader

好了,上面说了那么多,我们还是来点实际的吧,下面是本人在项目中对MVP的处理方式,有不同见解的,欢迎大家提出。

==================================View=======================================

所有的view(Activity、FragmentActivity、Fragment...)都必须实现这个接口

public interface IView {
    // 此方法是为了当Presenter中需要获取上下文对象时,传递上下文对象,而不是让Presenter直接持有上下  文对象
    Activity getSelfActivity();
}

这是Activity的基类:

public abstract class BaseActivity<P extends IPresenter> extends Activity implements IView {
    // Presenter对象
    protected P MvpPre;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MvpPre = bindPresenter();
    }
    
    // 绑定Presenter
    protected abstract P bindPresenter();

    public <T> T $(int resId) {
        return (T) findViewById(resId);
    }

    public <T> T $(int resId, View parent) {
        return (T) parent.findViewById(resId);
    }

    @Override
    public Activity getSelfActivity() {
        return this;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        /**
         * 在生命周期结束时,将presenter与view之间的联系断开,防止出现内存泄露
         */
        if (MvpPre != null) {
            MvpPre.detachView();
        }
    }
}

==================================Presenter=======================================
public interface IPresenter {
    void detachView();
}

Presenter的基类:

public abstract class BasePresenter<V extends IView> implements IPresenter {
    // 此处使用弱引用是因为,有时Activity关闭不一定会走onDestroy,所以这时使用弱引用可以及时回收IView
    protected Reference<V> MvpRef;

    public BasePresenter(V view) {
        attachView(view);
    }

    private void attachView(V view) {
        MvpRef = new WeakReference<V>(view);
    }

    protected V getView() {
        if (MvpRef != null) {
            return MvpRef.get();
        }
        return null;
    }

    /**
     * 主要用于判断IView的生命周期是否结束,防止出现内存泄露状况
     *
     * @return
     */
    protected boolean isViewAttach() {
        return MvpRef != null && MvpRef.get() != null;
    }

    /**
     * Activity生命周期结束时,Presenter也清除IView对象,不在持有
     */
    @Override
    public void detachView() {
        if (MvpRef != null) {
            MvpRef.clear();
            MvpRef = null;
        }
    }
}

===================================demo========================================
接口:
/**
 * 创建一个类作为纽带,将view、presenter、model的接口方法都串联在一起,更加便于管理
 */
public final class MainContacts {
    public interface IMain extends IView {
        void showTips(boolean isSucceess);
    }

    public interface IMainPre extends IPresenter {
        void login(String username, String password);
    }

    public interface IMainLgc {
        boolean login(String username, String password);
    }
}


Model部分:

public class MainLogic implements MainContacts.IMainLgc {

    public boolean login(String username, String password) {
        if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
            return false;
        }
        return true;
    }
}


View部分:
public class MainActivity extends BaseActivity<MainPresnter> implements MainContacts.IMain {
    private EditText editT_username, editT_password;
    private Button btn_login;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initUI();
        addListeners();
    }

    @Override
    protected MainPresnter bindPresenter() {
        return new MainPresnter(this);
    }

    private void initUI() {
        editT_username = $(R.id.editT_username);
        editT_password = $(R.id.editT_password);
        btn_login = $(R.id.btn_login);
    }

    private void addListeners() {
        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MvpPre.login(editT_username.getText().toString(), editT_password.getText().toString());
            }
        });
    }

    @Override
    public void showTips(boolean isSucceess) {
        if (isSucceess) {
            Toast.makeText(this, "登录成功!", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "登录失败!", Toast.LENGTH_SHORT).show();
        }
    }
}


Presenter部分:
public class MainPresnter extends BasePresenter<MainContacts.IMain> implements MainContacts.IMainPre {
    private MainLogic mMainLogic;

    public MainPresnter(MainContacts.IMain view) {
        super(view);
        this.mMainLogic = new MainLogic();
    }

    @Override
    public void login(String username, String password) {
        // 判断activity的生命周期是否结束,不判断的话在极端情况下可能会出现内存泄露
        if (isViewAttach()) {
            MvpRef.get().showTips(mMainLogic.login(username, password));
        }
    }
}

好了,以上便是本人对MVP模式的一些理解,如果你不想自己在重新搭建MVP框架,可以直接使用MVPLoader项目做依赖:

Step 1:

      allprojects {
          repositories {
              ...
              maven { url 'https://jitpack.io' }
          }
      }
      
Step 2:

      dependencies {
          compile 'com.github.albert-lii:MVPLoader:1.0.6'
      }

如果你有不同的理解,也欢迎提出,大家一起进步。

Github地址

https://github.com/albert-lii/MVPLoader

如果觉得不错,给个赞吧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,448评论 25 707
  • 转载至:http://www.jianshu.com/p/9a6845b26856 “Android MVP 详解...
    SnowDragonYY阅读 10,319评论 5 241
  • 原文出处:选择恐惧症的福音!教你认清MVC,MVP和MVVM/** ** 相信大家对MVC,MVP和MVVM都不陌...
    zzj丶阅读 1,409评论 7 39
  • 作者:李旺成 时间:2016年4月3日 “Android MVP 详解(下)”已经发布,欢迎大家提建议。 MVP ...
    diygreen阅读 128,814评论 86 1,321
  • 1.原著阅读第16天,看书20页,每隔几天有人因为各种理由离开的我都一概不做挽留,因为真正想要看书的是不会因为工作...
    Jane小白917阅读 658评论 0 2