MVP架构学习

M:数据层(数据库,文件,网络等...)

V:UI层(Activity,Fragment,View以及子类,Adapter以及子类)

P:中介,关联UI层和数据层,因为V和M是相互看不到对方的,简单而言就是不能相互持有对方的引用

MVP只是一种思想,不要把它认为是一种规范,要学会灵活用户,下面就带大家走进MVP模式的学习

需求

需求很简单,我们就做一个简单的登录功能,当点击界面上的Login按钮,会向后台发送登录请求

布局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="xdysite.cn.testdemo.MainActivity"> <Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="login"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:onClick="login"/>
</android.support.constraint.ConstraintLayout>

注:按钮点击会调用login方法

方案1

本方案中给出了最朴素的实现方式

public class MainActivity extends AppCompatActivity {
static final String TAG = "MainActivity"; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} public void login(View v) {
OkHttpClient client = new OkHttpClient();
RequestBody body = new FormBody.Builder().add("username", "admin").add("password", "12345").build();
Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.i(TAG, "onFailure: " + call.toString());
} @Override
public void onResponse(Call call, Response response) throws IOException {
Log.i(TAG, "onResponse: " + response.body().string());
}
});
}
}

上图中当我们点击login按钮的时候会调用MainActivity中的login方法,在login方法中会向服务器发送请求,并等待返回结果(这里使用了OKHttp,使用异步的方式发送请求)。

小结

这种设计简单明了,直观具体,但是它有个局限性,它将所有的功能全部在一个类中完成,那只适合单人作战。我们想象一下,按照上面的方案,如果我们让一个人写界面(布局文件),让一个人写登录功能(Activity),那么写登录功能的人某一天将login函数改为了login2了,但他忘记告诉了写界面的人,那是不是就是出现了问题。即使他告诉了写界面的人说“你将界面的上的login换为login2”,人家愿不愿换还是一回事呢!!!

方案2

本方案中引入MVP思想,对上面的设计优化一下。

Model层

Model我们就让其与服务器打交道,来实现登录功能的逻辑,我们实现了一个LoginModel类。

public class LoginModel {

    void login(String username, String password, final OnResultListener listener) {
OkHttpClient client = new OkHttpClient();
RequestBody body = new FormBody.Builder().add("username", username).add("password", password).build();
Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
} @Override
public void onResponse(Call call, Response response) throws IOException {
listener.onResult(response.body().string());
}
});
} public interface OnResultListener {
void onResult(String result);
}
}

在LoginModel类对外暴露了一个login的方法来供别人调用,传入的参数为用户名、密码和监听器。监听器作用是当服务器返回结果时调用。

监听器是LoginModel类的内部接口,需要调用者去实现该接口。

View层

View层就是我们的Activity和布局文件。View层将持有的P层的引用。下面是改造后的MainActivity类

public class MainActivity extends AppCompatActivity {
static final String TAG = "MainActivity";
LoginPresenter mLoginPresener; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLoginPresener = new LoginPresenter(this);
} public void login(View v) {
mLoginPresener.login("admin", "12345");
} public void showResult(String result) {
Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
}
}

在MainActivity类中持有了P层(LoginPresenter)的引用,当用户点击登录时,它会调用P层的login方法,并且这里还提供了一个showResult来显示登录结果(成功/失败)

Presenter层

在P层充当中介的身份,它同时需了解V层和M层的情况,因此P层将会持有V层和M层的引用。

public class LoginPresenter {
LoginModel mLoginModel = new LoginModel();
MainActivity mLoginView; public LoginPresenter(MainActivity loginView) {
mLoginView = loginView;
} public void login(String usernanem, String password) {
mLoginModel.login(usernanem, password, new LoginModel.OnResultListener() {
@Override
public void onResult(String result) {
mLoginView.showResult(result);
}
});
}
}

在LoginPresenter类中同时持有了MainActivity的引用和LoginModel的引用,当在MainActivity中调用LoginPresenter的login方法时,LoginPresener会调用LoginModel中的login方法,然后在回调中还是调用MainActivity的showResult方法。这样LoginPresenter就完成了中介的职责。

小结

通过MVP我们将界面的处理和与服务器交互逻辑分离的开来,如果View层代码被修改了,那么M层的代码将不会受任何影响,反之依然。这样就解决了方案一种出现的争端。这是MVP最简单的运用了,说它简单那么存在不完善的地方。就拿V和P来说,LoginPresenter中调用了MainActivity的showResult方法,当这个方法被改名的话,在LoginPresenter中也要做同样的修改。而且在创建LoginPresenter时也只能接受MainActivity对象,当你的老板有天说我们的登录换成LoginFragment了,那你又要再写一个接受LoginFragment对象的LoginPresenter了。下来我们继续优化上的设计

方案3

为了更好的解耦,那么我们将会在View层引入接口类,接口的一大特性就是解耦。因为接口意味着规范,接口中的方法必须要实现,而且接口类一旦确定,那么很少发生修改了。

Model层

Model层的代码我们一点都不动

public class LoginModel {

    void login(String username, String password, final OnResultListener listener) {
OkHttpClient client = new OkHttpClient();
RequestBody body = new FormBody.Builder().add("username", username).add("password", password).build();
Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
} @Override
public void onResponse(Call call, Response response) throws IOException {
listener.onResult(response.body().string());
}
});
} public interface OnResultListener {
void onResult(String result);
}
}

View层

在View层中我们定义了接口类LoginView,目的就是为了让V和P解耦。

接口类
public interface LoginView {
void showResult(String result);
}
具体类
public class MainActivity extends AppCompatActivity implements LoginView {
static final String TAG = "MainActivity";
LoginPresenter mLoginPresener; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLoginPresener = new LoginPresenter(this);
} public void login(View v) {
mLoginPresener.login("admin", "12345");
} @Override
public void showResult(String result) {
Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
}
}

具体类实现了LoginView接口,则表明showResult方法不是MainActivity所有了,它属于接口类了,成为了一种规范。

Presenter

P层现在持有的不是一个具体的View层对象了,它是面向接口的。不管你是Activity还是Fragment,只要你实现了LoginView接口就好。而且它也不用担心View层胡乱改的问题了,只要你实现LoginView这个接口。

public class LoginPresenter {
LoginModel mLoginModel = new LoginModel();
LoginView mLoginView; public LoginPresenter(LoginView loginView) {
mLoginView = loginView;
} public void login(String usernanem, String password) {
mLoginModel.login(usernanem, password, new LoginModel.OnResultListener() {
@Override
public void onResult(String result) {
mLoginView.showResult(result);
}
});
}
}
小结

我们将V和P通过接口的方式进行解耦了,我们在MVP架构上又往前走了一步。但是,如果仔细研究代码的话,我们会发现有内存泄露的问题。当网络请求发出去后,如果我们立马关闭Activity,那么Activity会得到释放吗?答案是不会,这个留给大家去思考。

补充

补充部分是对方案3的小优化,主要解决Activity引起的内存泄露的问题。

对Presenter优化

引入attcheView和detachView方法来绑定视图和解除视图,现在这两个方法都比较单薄,但是我们在这个方法中可以根据业务需要添加别的逻辑了。

public class LoginPresenter {
LoginModel mLoginModel = new LoginModel();
LoginView mLoginView; public void login(String usernanem, String password) {
mLoginModel.login(usernanem, password, new LoginModel.OnResultListener() {
@Override
public void onResult(String result) {
if (mLoginView != null)
mLoginView.showResult(result);
}
});
} public void attcheView(LoginView loginView) {
mLoginView = loginView;
} public void detachView() {
mLoginView = null;
}
}
对MainActivity进行改进
public class MainActivity extends AppCompatActivity implements LoginView {
static final String TAG = "MainActivity";
LoginPresenter mLoginPresener; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLoginPresener = new LoginPresenter();
mLoginPresener.attcheView(this);
} public void login(View v) {
mLoginPresener.login("admin", "12345");
} @Override
public void showResult(String result) {
Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
} @Override
protected void onDestroy() {
super.onDestroy();
mLoginPresener.detachView();
mLoginPresener = null;
}
}

在onCreate中我们创建了LoginPresenter对象,并将自己与其绑定。当Acitvity要销毁时,我们就解除绑定,这样就防止发送内存泄露了。

方案4

前面的方案将View层进行了处理,使用了接口来使用V和P进行解耦,能不能做进一步处理,比如使用泛型。P层不用关心V层具体是什么类型。甚至对V层和P层之间的数据交互也做泛型处理。这样的话耦合程度更小了。

通用接口

将View层和Presenter层做接口化处理,当然Model层也能做接口化处理,由于时间有限,这里只实现前两个。

View接口
public interface IView<D> {
void showResult(D result);
}

D是表示数据,这样showResult可以处理任意类型的数据了

prestener接口
public interface IPresenter<D, V extends IView<D>> {
void attcheView(V view); void detachView();
}

在Presenter中我们对V也做了泛型处理,这样Presenter可以既可以绑定Activity,又可以绑定Fragment

通用抽象类

抽象类是对接口做了一点点实现,这个看个人需求了,如果你感觉类太多的话可以把接口剔除掉,直接使用抽象类来做。

Presenter抽象类
public abstract class AbsPresenter<D, V extends IView<D>> implements IPresenter<D, V> {
private V mView; @Override
public void attcheView(V view) {
mView = view;
} @Override
public void detachView() {
mView = null;
} public V getView() {
return mView;
}
}

AbsPresenter类对Presenter接口做了些实现

Activity抽象类
public abstract class BaseActivity<D, V extends IView<D>, P extends AbsPresenter<D, V>> extends AppCompatActivity{
private P presenter; @Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (presenter == null) {
presenter = bindPresenter();
presenter.attcheView((V)this);
}
} public abstract P bindPresenter(); public P fetchPresenter() {
return presenter;
} @Override
protected void onDestroy() {
super.onDestroy();
if (presenter != null)
presenter.detachView();
}
}

BaseActivity类对将视图的绑定与解除抽了出来

具体实现

前面的两部分很抽象了,可以应对各种场景了,下面我们就应用到登录场景中。

首先是LoginModel

其实Model和Presenter直接也可以解耦的,可以定义一个Model接口出来,而Presenter持有Model接口的引用即可。但是由于时间关系。我们直接上Model了

public class LoginModel {

    public void login(String username, String password, final OnResultListener listener) {
OkHttpClient client = new OkHttpClient();
RequestBody body = new FormBody.Builder().add("username", username).add("password", password).build();
Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
} @Override
public void onResponse(Call call, Response response) throws IOException {
listener.onResult(response.body().string());
}
});
} public interface OnResultListener {
void onResult(String result);
}
}

下来是View

public interface LoginView extends MvpView<String>{

}

public class MainActivity extends BaseActivity<String, LoginView, LoginPresenter> implements LoginView{

    @Override
public LoginPresenter bindPresenter() {
return new LoginPresenter();
} @Override
public void showResult(final String result) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
}
});
} public void login(View v) {
LoginPresenter presenter = fetchPresenter();
if (presenter != null)
presenter.login("admin", "12345");
}
}

定义了LoginView接口,并在MainActivity中对泛型做了具体化处理,比如数据类型指定为String类型。而且我们发现MainActivity中的代码更少了。

最后是Presenter

public class LoginPresenter extends AbsPresenter<String, LoginView> {
LoginModel mLoginModel = new LoginModel(); public void login(String username, String password) {
mLoginModel.login(username, password, new LoginModel.OnResultListener() {
@Override
public void onResult(String result) {
MvpView<String> loginView = getView();
if (loginView != null)
loginView.showResult(result);
}
});
}
}

总结

我们通过递进的方式,一步一步对MVP架构进行完善,而且这是MVP架构中的一种体现方式。其核心思想急就是分离,这种思想在方案2已经体现出来了,所以不要拘泥于某种模版或规范,要灵活运用。

MVP架构学习的更多相关文章

  1. MVP架构。。。。

    Model-View-Presenter(MVP)概述    MVC模式已经出现了几十年了,在GUI领域已经得到了广泛的应用,由于微软ASP.NET MVC Framework的出现,致使MVC一度成 ...

  2. 转:Android开发中的MVP架构(最后链接资源不错)

    Android开发中的MVP架构 最近越来越多的人开始谈论架构.我周围的同事和工程师也是如此.尽管我还不是特别深入理解MVP和DDD,但是我们的新项目还是决定通过MVP来构建. 这篇文章是我通过研究和 ...

  3. 转: Android开发中的MVP架构详解(附加链接比较不错)

    转: http://www.codeceo.com/article/android-mvp-artch.html 最近越来越多的人开始谈论架构.我周围的同事和工程师也是如此.尽管我还不是特别深入理解M ...

  4. 设计模式笔记之二:Android开发中的MVP架构(转)

    写在前面,本博客来源于公众号文章:http://mp.weixin.qq.com/s?__biz=MzA3MDMyMjkzNg==&mid=402435540&idx=1&sn ...

  5. MVP架构在xamarin android中的简单使用

    好几个月没写文章了,使用xamarin android也快接近两年,还有一个月职业生涯就到两个年了,从刚出来啥也不会了,到现在回头看这个项目,真jb操蛋(真辛苦了实施的人了,无数次吐槽怎么这么丑),怪 ...

  6. 死磕安卓前序:MVP架构探究之旅—基础篇

    前言 了解相关更多技术,可参考<我就死磕安卓了,怎么了?>,接下来谈一谈我们来学习一下MVP的基本认识. 大家对MVC的架构模式再熟悉不过.今天我们就学习一下MVP架构模式. MVC和MV ...

  7. 浅谈MVP架构及开发模式

    Model-View-Presenter(MVP)概述    MVC模式已经出现了几十年了,在GUI领域已经得到了广泛的应用,由于微软ASP.NET MVC Framework的出现,致使MVC一度成 ...

  8. 【从零开始学BPM,Day1】工作流管理平台架构学习

    [课程主题] 主题:5天,一起从零开始学习BPM [课程形式] 1.为期5天的短任务学习 2.每天观看一个视频,视频学习时间自由安排. [第一天课程] Step 1 软件下载:H3 BPM10.0全开 ...

  9. Mybatis架构学习

    Mybatis架构学习 MyBatis 是支持定制化 SQL.存储过程以及高级映射的持久层框架.MyBatis 封装了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.可以对配置和原生Map使用 ...

随机推荐

  1. java泛型之泛型边界

    http://blog.csdn.net/renwuqiangg/article/details/51296621

  2. print多重打印

    遇见有趣的问题必须记录下来,当时的想法思路也要记下来 以下两行代码打印出来的结果会是什么 print('2 * 3 = %d' % (2 * 3)) print('2 * 3 = %d' % 2 * ...

  3. weX5如何绑定KO对象

    define(function(require){ var $ = require("jquery"); var justep = require("$UI/system ...

  4. 五 Android Studio打包Eegret App (包名和签名,打出正式包)

    一 定义包名 如下图,在AndroidManifest.xml中的package就是包名 二 创建keystore 选择Build->Generate Signed APK 选择create n ...

  5. 【BZOJ3831】[Poi2014]Little Bird 单调队列

    [BZOJ3831][Poi2014]Little Bird Description In the Byteotian Line Forest there are   trees in a row. ...

  6. 06.Curator Barrier

        分布式Barrier是这样一个类: 它会阻塞所有节点上的等待进程,知道某一个被满足, 然后所有的节点继续进行.     比如赛马比赛中, 等赛马陆续来到起跑线前. 一声令下,所有的赛马都飞奔而 ...

  7. 项目中整合第三方插件与SpringMVC数据格式化关于ip地址

    一.Bootstrap 响应式按钮 <div calss="col-sm-2"> <button class="btn btn-default btn- ...

  8. JAVA基础之multipart,urlencoded以及JSON

    一.(enctype) 表单的默认编码方式  ajpplication/x-www-form-urlencoded 上传文件的编码方式  multipart/form-data 互联网应用常用编码   ...

  9. Android ImageView 获取图片信息后进行比较

    ImageView a=(ImageView)findViewById(R.id.imageView2); //获取当前图片ConstantState类对象 Drawable.ConstantStat ...

  10. datasnap rest Windows客户端编写

    首先吐槽一下XE关于datasnap的资料真的是太少了... 服务端用DSHTTPService1控件可以实现http接口方式的调用,返回的都是json格式数据,适用于跨平台解析. 这里着重强调一下d ...