Android MVP设计 定制你的减肥模式

什么是MVP

现在,设计一款新的APP,对于Android而言,至少都是什么Material Design,然后可能又是什么MVP什么什么的。在MVCMVVM都还没有弄明白的时候,又出来了新的MVP,是不是有种淡淡的忧伤??
那么问题来了,到底什么是MVP呢?

MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。作为一种新的模式,MVPMVC 有着一个重大的区别:在MVPView 并不直接使用Model,它们之间的通信是通过 Presenter (MVC 中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVCView会直接从Model中读取数据而不是通过 Controller
MVC里,View是可以直接访问Model的!从而,View里会包含Model信息,不可避免的还要包括一些业务逻辑。 在MVC模型里,更关注的Model的不变,而同时有多个对Model的不同显示,及View。所以,在MVC模型里,Model不依赖于View,但是View是依赖于Model的。不仅如此,因为有一些业务逻辑在View里实现了,导致要更改View也是比较困难的,至少那些业务逻辑是无法重用的。

以上是百度百科MVP设计优势的一些解释。结合之前的MVC设计模式的话,其实MVP就是更加简化了ActivityView的职责,让他们交出了数据管理以及相关业务逻辑处理的权利,而是纯粹的经行UI相关的操作!!(比如说控制View的显示和隐藏,数据的相关展现。)

MVP的好处

MVC

MVP


根据上面两个图示可以看出,MVPMVC的区别在于功能模块访问的权限,在MVP中,View层和Mode数据层是不能直接有联系的。而是必须通过Presenter层来完成一个统一的调度,Presenter充当了View层和Mode层的中间桥梁,这样的好处就是实现了View和数据业务的完全解耦,是的逻辑层次更加分明,利于代码的维护!这个也满足设计中要求的单一职责原则。

MVP设计示例

大概说完了好处,那么就要说说刚入手MVP设计时的一些感觉不爽的地方,采用MVP设计之后,整体的项目层级大概就是酱紫啦!

package

data就是Model层相关的封装,包括api请求等等,presenters就是管理ViewModel的中间层,view就是Activity相关的封装。
可以看到在presenterview中都有相关的接口,在MVP设计中,其精髓就在于面向接口编程,不依赖与具体实现。
具体来讲就是通过View的相关接口定义出view具体实现Activity)的一些基本方法,比如说(onShowLoading(),onShowDialog()),在相关的presenter接口中也需要定义一系列的行为事件或者就叫抽象方法嘛!比如说(onLoginButuonClick(),onItemClicked(int position))这些都是需要去适应的,使用MVP的模式编写代码,就要求我们更加注重于抽象,怎么解释呢,还是比如说登陆页面,一个输入用户名一个输入密码,一个登陆按钮。
大致流程就是点击登陆按钮,显示loading页面,获取两个输入框的文本内容,经行相关判断和检查(本地trim()去空格,提交服务器check是否OK),关闭loading页面,然后根据返回内容做相关提示:登陆成功,跳转相关页面,登陆失败。。

采用MVP设计模式处理这些问题那么就需要如下进行:

  • Model层应该封装用户名和密码(UserInfoBean)等,完成相关的api(checkUserInfo(UserInfoBean bean))请求。

  • View层应该有showLoading();closeLoading();showErrorView(String msg); showHomeView();getUserName(); getUserPasswrod();

  • Presenter层应该有(onLoginButtonClick():响应View登陆按钮的点击 )
    OK,那么接下这三者到底怎么互相协调经行相关操作呢?
    首先LoginActivity我们的登陆页面,实现View的接口,依然是在onCreate()里面初始化相关的View
    PresenterView的交互中,在View的初始化的时候new出对应的Presenter。并通过Presenter的构造函数将view的引用传入Presenter的具体实现类中。

    final LoginPresenter presenter = new LoginPresenterImpl(this);

PresenterImpl的中:

private final LoginView view;
private final Activity activity;
public LoginPresenterImpl(LoginView view) {
    this.view = view;
    this.activity = (Activity) view;
}

这样就完成了ActivityPresenter的交互回调了。

Activity的点击事件中:

 Button mSignInButton = (Button) findViewById(R.id.email_sign_in_button);
    mSignInButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            presenter.onLoginClick();
        }
    });

通过 presenter.onLoginClick(),将相关的处理逻辑调度给对应的Presenter来处理了!

而在Presenter的具体实现类LoginPresenterImpl中:

@Override
public void onLoginClick() {
    // Reset errors.
    view.setPasswordError(null);
    view.setUserNameError(null);
    // Store values at the time of the login attempt.
    String password = view.getPassWord();
    String userName = view.getUserName();
    // Check for a valid email address.
    if (TextUtils.isEmpty(userName)) {
        view.setUserNameError(activity.getString(R.string.error_field_required));
        view.setUserNameFocus();
        return;
    }

    // Check for a valid password, if the user entered one.
    if (TextUtils.isEmpty(password) ||!isPasswordValid(password)) {
        view.setPasswordError(activity.getString(R.string.error_invalid_password));
        view.setPasswordFocus();
        return;
    }


    UserLoginTask mLoginTask = new UserLoginTask(this, userName, password);
    if (!isRun) {
        mLoginTask.execute((Void) null);
    }


}

可以看到,在LoginPresenterImpl中,持有的View的引用其实就是对应的LogingActivity,在这里,先是调用view.setPasswordError(null),重置相关的错误提示,然后通过 String password = view.getPassWord()来获取对应的密码输入。接下来就是密码的非空判断,如果异常,那么就调用view.setUserNameError(String msg),来通知Activity相关的错误信息。最后如果没有问题的话,那么就开启子线程去走网络请求。
Model层中,定义的LoginTask的接口:

public interface LoginTask {
  void onLoginSuccess();

  void onLoginFailed(String msg);

  void onLoginCancelled();

  void onLoginTaskStart();
}

这里直接让对应的Presenter实现该接口,也是通过相关的接口,处理对应的逻辑!
比如说:

@Override
public void onLoginCancelled() {
    view.showProgress(false);
    isRun = false;
}

@Override
public void onLoginTaskStart() {
    isRun = true;
    view.showProgress(true);
}

这分别是任务开始和取消的情况。

最后总结起来就是view定义出对应的Activity的相关行为(显现进度条、隐藏对话框等。)Presenter定义出对应的处理行为动作(Button的点击事件,滑动事件其实还有生命周期相关的比如onResume()onDestroy()还有api请求的相关方法。)最后就保证Presenter拥有绝对的控制和调动权,Model层的相关结果通过Presenter层调度之后调用View的相关方法传递给具体的View实现类,从而保证View层和Model层的完全解耦。

一些思考

  • 1、View层的一些小动作也一定要报告给Presenter吗?比如说我在点击某个Button之后要让某个widget隐藏了!!??

如果真的就是很简单的如上面的这种情况,那么View层可以自己处理这情况,但是其实一般都没有这么简单纯粹的情况,就像在公司做事儿一样,要养成向领导汇报的好习惯,可能很小的事情或者你觉得领导很忙这种事情完全可以不请示他,那么可要小心了,一旦这个事情变大,领导追究责任时。。所以这个度你要自己体会!!!

  • 2、PresenterView的对应关系?是一对一?是多对一?还是一对多?

这个关系我觉得都是可以出现的,但是一般情况会是一对一的,然后P->V 一对多也是有这种情况的,比如说在RecycleView中出现了几种布局,每种布局的相关逻辑要在P中经行处理,那么就会出现一对多的情况!至于多对一的情况,我觉得这个相对还要少一些吧!处理中心应该是独一的一个!

  • 3、采用MVP时不知道对应的ViewPresenter应该定义那些抽象的方法??

这个问题其实也并不是问题,不要就是在那里空想,其实你把层次分好了,缺什么方法到需要的时候自然就知道了,这个时候再添加到对应的接口中就好了!也没有人能一次性就把所有的情况都考虑完全,所以多想多写,多积累相关经验。

  • 4、采用MVP会精简代码吗?

我一开始想的肯定能减少很多代码的。但是真的使用之后,你会发现,其实代码并没有减少,相反,可能还要多了!因为你要创建相关的抽象,直观的第一个体现就是一个项目的class变多了!但是它的层次结构和相关逻辑更加清晰了,尤其是要你去维修之前的代码,那个效率提升真的很高,至少改动View,你可以不用担心把相关的业务逻辑代码改挂了!所以也是有利必有弊吧!

  • 5 采用MVP会导致内存泄露等问题吗?

内存泄露的问题,这里主要是说在View层(Activity)相关的生命周期结束之后,由于某些引用一直存在,导致其无法被正常的销毁和回收,典型的问题出现就是在P层出现了静态的引用,这也是千万别出现的写法!

  • 6 需要在Destroy的时候再P层将V层的引用置空吗?

详细说就是在P层增加onDestroy()的方法回调,在这里将View层相关引用置空,这样做貌似很应该,但是 ,如果一些耗时的操作在onDestroy()方法执行之后又执行到V层的逻辑,而此时V的引用已经置空了,那么必定出现空指针咯!

  • 7 LoginTask网络请求相关的一定要定义成独立的接口吗?

我觉得这个没有太多的必要,因为定义的越多,其实你的代码就会越来越臃肿,所以,你可以在Presenter中直接定义出相关网络请求等的方法就好!

  • 8 在P层一定需要将相关的View转换为对应的Activity吗?

首先即使转换了也不要直接去使用Activity去调用相关的方法,这样就违背了面向抽象的原则了。为什么有时候需要呢?是因为在P层有些操作你是需要使用到上下文的,这样写就是可以方便使用这个上下文。(Activity的跳转,sp dp px的转换,SP保存相关数据等等都要使用到上下文。)

写在最后

任何一种模式都不是100%的完美,MVP也是!而且任何一种模式都不应该束缚相关的开发!而是应该让程序简洁(这个不意味着是代码少的简洁),易于维护和扩展! MVP也没有固定的写法,MVP就是更加强化了面向接口编程的一种具体体现。我们定义相关抽象的接口,具体的子类实现相关的方法实现相关的业务逻辑,对于习惯了一条线写下去的编码方式,刚开始的确会有点儿不习惯。因为撸着撸着,突然你要告诉自己,这里我们要交给Presenter处理了。然后又到Presenter里面,撸着撸着,我们这里要通知View了!!
上面这个例子,只是一个入门的(很复杂的我其实也还没有写过呢!)。但是必须强调,这里的Model层其实是比较弱化的。在实际开发中,数据库和网络请求更加强化,对应的Model层就更复杂,比如说可能我们获取的相关json数据,还要在Presenter里面进行一些处理,然后才能分发给View去更新相关界面!另外就是子线程和UI线程的频频切换,不仅是切换很不爽,而且相关匿名内部类的代码估计看着也让人很抓狂的!!
说了这么多,想说个撒呢?其实就是对应的网络请求,建议大家使用Retrofit ,异步操作就可以用 Rxjava来搞定!

Rxjava,超级牛的异步请求解决方案,轻松解决你的子线程UI线程切换问题,链式调用,逻辑特别清楚。
而对于Retrofit,http网络请求的最佳实践,并且可以结合Rxjava,写出来的代码那是一个飘逸!!!

相关下载

登陆示例

知否 MVP+Retrofit

知否
知否

如有问题欢迎留言,喜欢记得点赞赞赞哟!!

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

推荐阅读更多精彩内容