代码已上传到Github,因为接口都是模拟无法进行测试,明白大概的逻辑就行了!

欢迎浏览我的博客——https://pushy.site

1. MVP模式

1.1 介绍

如果熟悉MVP模式架构的话,对下图各组件的调用关系应该不陌生:

和其他传统模式相比,MVP有以下的几个特点:

  • View不再负责同步的逻辑,而是由Presenter来负责。
  • View需要提供操作界面的接口给Presenter进行调用。
  • 打破了View原来对于Model的依赖。

那么这三者的分工分别是什么呢?

Model:处理业务逻辑,工作职责:加载远程网络或者本地的数据。

View:视图,工作职责:控制显示数据的方式。

Presenter:中间者,工作职责:绑定Model、View。

1.2 结构

我们仿造GitHub中谷歌官方例子中的安卓架构蓝图来搭建Android中MVP架构模式的结构:

下面,我们详细来说明MVP中各个组件的含义和调用方式:

Contract

你可能会好奇,MVP中不是只有三个组件吗?为什么多了一个!没错,多出来的这个出现正是LoginContract,在MVP模式中,Presenter与View需要提供各自的接口供其他组件调用。通常,我们把Presenter和View的接口都定义在*Contract类中:

public class LoginContract {

    interface View {
void setLoading(boolean v); // 显示加载中
} interface Presenter {
void login(); // 登录逻辑调用
}
}

Model

在Android中,Model层主要的职责是用来加载数据。在这里,我们通常请求远程网络的数据或者加载本地缓存的数据:

public class LoginModel {

    public void login() {
/* 请求网络数据 */
}
}

Presenter

MVP中,Presenter主要用于绑定View和Model,并组织调用不同层提供的接口。所以在Presenter层必须持有View和Model对象。

所以我们让Presenter实现Contract.Presenter的接口,并提供构造函数注入View的实现类和Model对象:

public class LoginPresenter implements LoginContract.Presenter {

    private LoginContract.View view;
private LoginModel model; public LoginPresenter(LoginContract.View view, LoginModel model) {
this.view = view;
this.model = model;
} @Override
public void login() {
view.setLoading(true); // 显示加载中
model.login(); // 向服务器请求登录
}
}

View

在Android中,Activity往往当成是View的实现类。因此我们让LoginActivity实现Contract.View接口:

public class LoginActivity extends AppCompatActivity implements LoginContract.View {

    private LoginPresenter presenter;

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); presenter = new LoginPresenter(this, new LoginModel());
} @Override
public void setLoading(boolean v) {
/* 显示加载中的UI操作 */
}
}

并且,我们在onCreate方法中实例化出LoginPresenter对象,并注入View的实现类(即当前这个Activity)和Model对象。

这样,当用户触发按钮的点击事件时,我们就可以调用Presenter提供的接口来向远程服务器进行登录的请求:

@Override
public void onClick(View v) {
presenter.login(name, password);
}

2. 封装Retrofit + RxJava

下面,我们来正式地讲解Retrofit + RxJava的封装过程,并将上面的MVP中各层具体逻辑替换。

首先,我们先在app/build.gradle中添加retrofit2rxJava库的相关依赖:

// rxJava相关依赖
implementation 'io.reactivex.rxjava2:rxjava:2.2.2'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
// retrofit2相关依赖和插件
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'

首先,我们定义RetrofitServiceManager统一生成接口实例的管理类:

public class RetrofitServiceManager {

    private static final String BASE_URL = "https://api.example.com";

    private Retrofit mRetrofit;

    public RetrofitServiceManager() {
// 初始化OkHttpClient对象,并配置相关的属性
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS) // 设置超时时间
.build();
mRetrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create()) // 支持Gson自动解析JSON
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava
.build();
} private static class SingletonHolder{
private static final RetrofitServiceManager INSTANCE = new RetrofitServiceManager();
} public static RetrofitServiceManager getInstance() {
// 返回一个单例对象
return SingletonHolder.INSTANCE;
} public <T> T create(Class<T> service) {
// 返回Retrofit创建的接口代理类
return mRetrofit.create(service);
} }

下一步,我们修改LoginModel里的具体请求逻辑。在默认构造函数中通过RetrofitServiceManager创建LoginModelService的代理对象,并定义公共的login方法让Presenter来调用:

public class LoginModel extends BaseModel {

    private LoginModelService service;

    public LoginModel() {
this.service = RetrofitServiceManager.getInstance().create(LoginModelService.class);
} public Observable<BaseResponse<String>> login(LoginBody body) {
// 调用父类BaseModel的observe方法进行请求
return observe(service.login(body));
} interface LoginModelService { @POST("/login")
Observable<BaseResponse<String>> login(@Body LoginBody body); } }

另外,我们让LoginModel继承了BaseModel。在该类中,做了线程切换的操作,因此在请求时只需要简单地嗲用父类的observe即可:

public class BaseModel {

    protected  <T> Observable<T> observe(Observable<T> observable){
return observable
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}

现在,在LoginPresenterlogin方法中,我们就可以同时操作viewmodel来控制登录的UI和请求的逻辑了:

@Override
public void login(String name, String password) {
LoginBody body = new LoginBody();
body.name = name;
body.password = password; view.setLoading(true); model.login(body)
.subscribe(response -> {
view.callback(true); // 成功回调
view.setLoading(false); }, throwable -> {
view.callback(false); // 失败回调
view.setLoading(false);
});
}

可以看到,Presenter对于不同的请求成功或失败的接口调用View提供的接口,展示给用户登录或者失败的结果。因此我们只需要在LoginActivity中定义不同结果的提示即可:

@Override
public void callback(boolean v) {
if (v) {
Toast.makeText(this, "登录成功", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "登录失败", Toast.LENGTH_LONG).show();
}
}

最后,我们只需要完成以下登录的UI视图和调用Presenter提供接口的简单逻辑,就可以实现完整的登录逻辑了:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> <EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content" /> <EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content" /> <Button
android:id="@+id/btn_submit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="登录"/> </LinearLayout>

在登录按钮的点击事件逻辑中调用Presenter的login方法请求登录:

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_submit:
presenter
.login(etName.getText().toString(), etPassword.getText().toString());
break;
}
}

3. 错误处理

假设服务器返回的基本数据为:

// 成功返回的数据
{
"code":200,
"data": "Hello World",
"message": ""
} // 失败返回的数据
{
"code":401,
"data": "",
"message": "Unauthorized"
}

我们针对这种返回数据,BaseResponse提供一个isSuccess方法来判断的结果是否有错:

public class BaseResponse<T> {

    public int code;

    public String message;

    public T data;

    /* 是否成功 */
public boolean isSuccess() {
return code == 200;
} }

然后修改LoginModellogin方法,通过Map的操作符来处理错误抛出异常,并进一步封装返回的数据:

public Observable<BaseResponse<String>> login(LoginBody body) {
return observe(service.login(body))
.map(new PayLoad<>())
}

PayLoad类中,判断请求数据是否成功,如果失败,则抛出一个错误,否则返回成功的数据:

public class PayLoad<T> implements Function<BaseResponse<T>, BaseResponse<T>> {

    private static final String TAG = "PayLoad";

    @Override
public BaseResponse<T> apply(BaseResponse<T> response) throws Exception {
if (!response.isSuccess()) {
/* 服务器端返回errno失败 */
throw new ServerException(response.code, response.message);
}
/* 成功获取 */
return response;
} }

Presenter中的订阅回调方法中就可以捕捉到ServerException异常:

model.login(body)
.subscribe(response -> {
view.callback(true); // 成功回调
view.setLoading(false);
}, throwable -> {
ServerException exception = (ServerException) throwable;
view.errorCallback(exception);
view.setLoading(false);
});

同时,在Activity中也可以根据服务端返回的不同状态码来向用户展示不同的错误结果:

@Override
public void errorCallback(ServerException e) {
switch (e.code) {
case ServerError.NO_USER:
Toast.makeText(this, "没有该用户", Toast.LENGTH_LONG).show();
break;
case ServerError.UNAUTHORIZED:
Toast.makeText(this, "密码错误", Toast.LENGTH_LONG).show();
break;
}
}

Android MVP开发模式及Retrofit + RxJava封装的更多相关文章

  1. 理解 Android MVP 开发模式

    /***************************************************************************************** * 理解 Andr ...

  2. 再谈MV*(MVVM MVP MVC)模式的设计原理—封装与解耦

    精炼并增补于:界面之下:还原真实的MV*模式 图形界面的应用程序提供给用户可视化的操作界面,这个界面提供给数据和信息.用户输入行为(键盘,鼠标等)会执行一些应用逻辑,应用逻辑(application ...

  3. Android应用中MVP开发模式

    所谓MVP(Model-View-Presenter)模式.是将APP的结构分为三层: view - UI显示层 view 层主要负责: 提供UI交互 在presenter的控制下修改UI. 将业务事 ...

  4. Android MVP框架模式

    结合前一篇MVC框架模式 为了更好地细分视图(View)与模型(Model)的功能,让View专注于处理数据的可视化以及与用户的交互,同时让Model只关系数据的处理,基于MVC概念的MVP(Mode ...

  5. 高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM

    简介 这是一个使用Java(以后还会推出Kotlin版本)语言,从0开发一个Android平台,接近企业级的项目(我的云音乐),包含了基础内容,高级内容,项目封装,项目重构等知识:主要是使用系统功能, ...

  6. MVP开发模式的理解

    1.MVP是什么 如果从层次关系来讲,MVP属于Presentation层的设计模式.对于一个UI模块来说,它的所有功能被分割为三个部分,分别通过Model.View和Presenter来承载.Mod ...

  7. retrofit+rxjava封装

    public class RetrofitHelper { private static OkHttpClient okHttpClient; private static ServiceAPI se ...

  8. 基于Retrofit+RxJava的Android分层网络请求框架

    目前已经有不少Android客户端在使用Retrofit+RxJava实现网络请求了,相比于xUtils,Volley等网络访问框架,其具有网络访问效率高(基于OkHttp).内存占用少.代码量小以及 ...

  9. 我的Cocos2dx开发模式

    编程环境: 1.window 7 32bit 2.cocos2dx 3.0 3.python 2.7 (注意不要使用3.0以上版本,除非cocos2dx推荐使用) 4.apache-ant-1.9.3 ...

随机推荐

  1. POJ 1169

    #include<iostream> #include<algorithm> #include<vector> #include<set> #defin ...

  2. 剑指offer二十九之最小的K个数

    一.题目 输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,. 二.思路 详解代码. 三.代码 import java.util. ...

  3. android学习-Activity和Service的生命周期

    详细请跳转原网页Activity和Service的生命周期(图) 不解释,不懂算我输 Activity的生命周期(图) Service的声明周期

  4. Spring Boot 不使用默认的 parent,改用自己的项目的 parent

    在初学spring boot时,官方示例中,都是让我们继承一个spring的 spring-boot-starter-parent 这个parent: <parent> <group ...

  5. static & abstract

    不能放在一起的修饰符:final和abstract,private和abstract,static和abstract,因为abstract修饰的方法是必须在其子类中 实现(覆盖),才能以多态方式调用, ...

  6. idea中maven项目程序包找不到解决办法之一

    首先检查maven配置对不对,包括被settings文件以及资源库的位置,maven版本等. 如果不行的话再进行下面的操作: 第一种方案: 在终端terminal中项目目录下,输入“mvn idea: ...

  7. 如何编写makefile文件

    最近一直在学习makefile是如何编写的.       当我们写的程序文件比较少的时候,敲入gcc /g++,当你在大型工程中,在一个个编译文件的话,你可能就会很郁闷.linux有一个自带的make ...

  8. eclipse配置tomcat Mac平台

    1.到 apache官方主页 http://tomcat.apache.org 下载 Mac 版本的完整 tar.gz文件包.解压拷贝到 /Library 目录下,并命名为Tomcat,其他目录也可以 ...

  9. 【转】MyBatis接口的简单实现原理

    MyBatis接口的简单实现原理 用过MyBatis3的人可能会觉得为什么MyBatis的Mapper接口没有实现类,但是可以直接用? 那是因为MyBatis使用Java动态代理实现的接口. 这里仅仅 ...

  10. Dijkstra Java

    https://leetcode.com/problems/network-delay-time/ /* Java program to find a Pair which has maximum s ...