Android中的MVP开发思想、Retrofit2网络请求

前言

什么是MVP,为什么要用MVP?
网上介绍MVP的很多,百度一下你就知道,至于为什么要用MVP,当然是由于它的优势了:

  1. 代码易读

    虽然这种模式增加了代码量,但是代码的可读性大大提升。

  2. 降低耦合,方便维护

    MVP的使用,使Activity中的网络请求剥离出来 成为model、presenter。

MVP顾名思义 Model、View、Presenter

  • Model:负责网络的请求
  • View:进行界面的展示
  • Presenter:负责处理请求网络后的数据处理:加载中 成功 or 失败 取消加载

那Contract是什么用呢?

这就涉及到MVP的缺点了,MVP在实现代码易读的同时,额外增加了大量的接口、类,不方便进行管理,于是Contract就登场了。

Contract 如其名,是一个契约,将Model、View、Presenter 进行约束管理,方便后期类的查找、维护。

具体代码

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//butterknife
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
//okhttp3
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation "com.squareup.okhttp3:logging-interceptor:3.10.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'
//rxjava2
implementation "io.reactivex.rxjava2:rxjava:2.1.12"
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
//AutoDispose解决RxJava内存泄漏
implementation 'com.uber.autodispose:autodispose:0.7.0'
implementation 'com.uber.autodispose:autodispose-android:0.7.0'
implementation 'com.uber.autodispose:autodispose-android-archcomponents:0.7.0'

基类

BaseActivity

如果使用Kotlin及其插件可以不用butterknife

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
import android.arch.lifecycle.Lifecycle;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

import com.uber.autodispose.AutoDispose;
import com.uber.autodispose.AutoDisposeConverter;
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider;

import butterknife.ButterKnife;
import butterknife.Unbinder;

public abstract class BaseActivity extends AppCompatActivity {
private Unbinder unbinder;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(this.getLayoutId());
unbinder = ButterKnife.bind(this);
initView();
}

@Override
protected void onDestroy() {
unbinder.unbind();
super.onDestroy();
}

/**
* 设置布局
*
* @return
*/
public abstract int getLayoutId();

/**
* 初始化视图
*/
public abstract void initView();
}

BaseFragment

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
import android.app.Fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import butterknife.ButterKnife;
import butterknife.Unbinder;

public abstract class BaseFragment extends Fragment {

private Unbinder unBinder;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(this.getLayoutId(), container, false);
unBinder = ButterKnife.bind(this, view);
initView(view);
return view;
}

@Override
public void onDestroyView() {
super.onDestroyView();
unBinder.unbind();
}

/**
* 初始化视图
*
* @param view
*/
protected abstract void initView(View view);

protected abstract int getLayoutId();
}

BaseView

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
import com.uber.autodispose.AutoDisposeConverter;
public interface BaseView {

/**
* 显示加载中
*/
void showLoading();

/**
* 隐藏加载
*/
void hideLoading();

/**
* 数据获取失败
* @param throwable
*/
void onError(Throwable throwable);

/**
* 绑定Android生命周期 防止RxJava内存泄漏
*
* @param <T>
* @return
*/
<T> AutoDisposeConverter<T> bindAutoDispose();
}

BasePresenter

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
public class BasePresenter<V extends BaseView> {
protected V mView;

/**
* 绑定view,一般在初始化中调用该方法
*
* @param view view
*/
public void attachView(V view) {
this.mView = view;
}

/**
* 解除绑定view,一般在onDestroy中调用
*/
public void detachView() {
this.mView = null;
}

/**
* View是否绑定
*
* @return
*/
public boolean isViewAttached() {
return mView != null;
}
}

BaseMvpActivity

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
import android.arch.lifecycle.Lifecycle;
import android.os.Bundle;
import android.support.annotation.Nullable;

import com.uber.autodispose.AutoDispose;
import com.uber.autodispose.AutoDisposeConverter;
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider;

public abstract class BaseMvpActivity<T extends BasePresenter> extends BaseActivity implements BaseView {

protected T mPresenter;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}


@Override
protected void onDestroy() {
if (mPresenter != null) {
mPresenter.detachView();
}
super.onDestroy();
}

/**
* 绑定生命周期 防止MVP内存泄漏
*
* @param <T>
* @return
*/
@Override
public <T> AutoDisposeConverter<T> bindAutoDispose() {
return AutoDispose.autoDisposable(AndroidLifecycleScopeProvider
.from(this, Lifecycle.Event.ON_DESTROY));
}
}

BaseMvpFragment

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
import android.arch.lifecycle.Lifecycle;

import com.uber.autodispose.AutoDispose;
import com.uber.autodispose.AutoDisposeConverter;
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider;

public abstract class BaseMvpFragment<T extends BasePresenter> extends BaseFragment implements BaseView {

protected T mPresenter;

@Override
public void onDestroyView() {
super.onDestroyView();
if (mPresenter != null) {
mPresenter.detachView();
}
super.onDestroyView();
}

/**
* 绑定生命周期 防止MVP内存泄漏
*
* @param <T>
* @return
*/
@Override
public <T> AutoDisposeConverter<T> bindAutoDispose() {
return AutoDispose.autoDisposable(AndroidLifecycleScopeProvider
.from(this, Lifecycle.Event.ON_DESTROY));
}
}

线程调度

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
import org.reactivestreams.Publisher;

import io.reactivex.Flowable;
import io.reactivex.FlowableTransformer;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.ObservableTransformer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.schedulers.Schedulers;

public class RxScheduler {

/**
* 统一线程处理
*
* @param <T> 指定的泛型类型
* @return FlowableTransformer
*/
public static <T> FlowableTransformer<T, T> Flo_io_main() {
return new FlowableTransformer<T, T>() {
@Override
public Publisher<T> apply(@NonNull Flowable<T> upstream) {
return upstream.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
}

/**
* 统一线程处理
*
* @param <T> 指定的泛型类型
* @return ObservableTransformer
*/
public static <T> ObservableTransformer<T, T> Obs_io_main() {
return new ObservableTransformer<T, T>() {
@Override
public ObservableSource<T> apply(Observable<T> upstream) {
return upstream.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
}
}

登录模块

LoginContract

首先,创建一个登陆的Contract:

1
2
3
4
5
public interface LoginContract {
interface Model { }
interface View extends BaseView { }
interface Presenter { }
}

其次创建Presenter、Model、View 对应Contract中的接口;

1
2
3
public class LoginPresenter implements  LoginContract.Presenter{} 
public class LoginModel implements LoginContract.Model{}
public class LoginActivity implements LoginContract.View {}

具体代码

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
public interface LoginContract {
interface Model {
Flowable<BaseObjectBean<LoginBean>> login(String username, String password);
}

interface View extends BaseView {
@Override
void showLoading();

@Override
void hideLoading();

@Override
void onError(Throwable throwable);

void onSuccess(BaseObjectBean<LoginBean> bean);
}

interface Presenter {
/**
* 登陆
*
* @param username
* @param password
*/
void login(String username, String password);
}
}

在LoginContract 中

  • Model接口 创建对应的联网请求的方法,将Presenter提交的字段放到联网请求中,发送给服务器
  • View 接口 创建在界面上显示加载中、取消加载以及登陆成功、失败的方法
  • Presenter 接口 创建 登陆的方法,以及需要提交的字段 (username、password)

LoginModel

1
2
3
4
5
6
7
8
import io.reactivex.Flowable;

public class LoginModel implements LoginContract.Model {
@Override
public Flowable<BaseObjectBean<LoginBean>> login(String username, String password) {
return RetrofitClient.getInstance().getApi().login(username,password);
}
}

LoginPresenter

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
public class LoginPresenter extends BasePresenter<LoginContract.View> implements LoginContract.Presenter {

private LoginContract.Model model;

public LoginPresenter() {
model = new LoginModel();
}

@Override
public void login(String username, String password) {
if (!isViewAttached()) {
return;
}
mView.showLoading();
model.login(username, password)
.compose(RxScheduler.Flo_io_main())
.as(mView.bindAutoDispose())
.subscribe(new Consumer<BaseObjectBean<LoginBean>>() {
@Override
public void accept(BaseObjectBean<LoginBean> bean) throws Exception {
mView.onSuccess(bean);
mView.hideLoading();
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
mView.onError(throwable);
mView.hideLoading();
}
});
}
}

LoginPresenter 实现LoginContract.Presenter 接口中的 login(String username, String password) 方法

实例化Model,在LoginPresenter login(String username, String password)方法中,调用model的网络请求,将username、password放在model的login()方法中,进行请求服务器。
请求服务器前 使用LoginContract.View中的 mView.showLoading()方法,进行显示加载中;在成功失败的回调中,使用对应的方法,以及取消加载。

其中BasePresenter、BaseView 是对Presenter以及View进行的封装

LoginActicity

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
public class LoginActivity extends BaseMvpActivity<LoginPresenter> implements LoginContract.View {

@BindView(R.id.et_username_login)
TextInputEditText etUsernameLogin;
@BindView(R.id.et_password_login)
TextInputEditText etPasswordLogin;

@Override
public int getLayoutId() {
return R.layout.activity_Login;
}

@Override
public void initView() {
mPresenter = new LoginPresenter();
mPresenter.attachView(this);
}

/**
* @return 帐号
*/
private String getUsername() {
return etUsernameLogin.getText().toString().trim();
}

/**
* @return 密码
*/
private String getPassword() {
return etPasswordLogin.getText().toString().trim();
}

@Override
public void onSuccess(BaseObjectBean bean) {
Toast.makeText(this, bean.getErrorMsg(), Toast.LENGTH_SHORT).show();
}

@Override
public void showLoading() {
ProgressDialog.getInstance().show(this);
}

@Override
public void hideLoading() {
ProgressDialog.getInstance().dismiss();
}

@Override
public void onError(Throwable throwable) {

}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

@OnClick(R.id.btn_signin_login)
public void onViewClicked() {
if (getUsername().isEmpty() || getPassword().isEmpty()) {
Toast.makeText(this, "帐号密码不能为空", Toast.LENGTH_SHORT).show();
return;
}
mPresenter.login(getUsername(), getPassword());
}
}

LoginActivity 中实现 LoginContract.View中的方法 ,在实现的方法中,进行进度条加载、和登陆成功or失败的UI的展示:

1
2
3
4
5
6
7
8
9
10
@Override
void showLoading();

@Override
void hideLoading();

@Override
void onError(Throwable throwable);

void onSuccess(BaseObjectBean<LoginBean> bean);

MVPHelper

MVP快速生成类的插件:https://github.com/githubwing/MVPHelper

该插件主要是为了生成ContractModelPresenter

使用快捷键 Alt + Insert , 点击MvpHelper,自动生成文件

注意

  • 只支持Java,不支持Kotlin

  • 可以对后缀为ContractPresenter的Java源文件中进行生成,

    Contract所处的位置中必须有至少一个包以 contract 结尾,

    Presenter所处的位置中必须有至少一个包以 presenter 结尾,否则生成报错

  • 重复生成将删除旧文件并重新生成,所以不要在已写代码后重新生成,会导致代码丢失

  • 强制 Contractinterface

  • Contract 中的三个接口不包含多余的修饰符

作用就是

在contract内创建如下接口

1
2
public interface GoodsInfoContract {
}

该文件会自动生成如下格式

1
2
3
4
5
6
7
8
9
10
public interface GoodsInfoContract {
interface Model {
}

interface View {
}

interface Presenter {
}
}

会自动创建如下文件

model下

1
2
public class GoodsInfoModel implements GoodsInfoContract.Model {
}

presenter下

1
2
public class GoodsInfoPresenter implements GoodsInfoContract.Presenter {
}

或者

使用Presenter生成

1
2
public class GoodsInfoPresenter{
}

该文件会自动生成如下格式

1
2
public class GoodsInfoPresenter implements GoodsInfoContract.Presenter {
}

同时生成其它两个文件

相关