最新更新的版本:https://github.com/wj576038874/MvpDemo
java版: https://github.com/wj576038874/mvp-rxjava-retrofit-okhttp
kotlin版: https://github.com/wj576038874/MvpKotlin
项目有所更新,以下代码可能有所变化,最新代码以上面Github为准
项目结构:
mvp的原理,v层抽象出接口,供P层调用,M层进行数据处理,抽象出接口,供P调用,P层中可拿到M和V 的接口引用,进行方法调用等逻辑处理,再利用接口回调的方式将解析好的数据返回给V层,这样就打到M层不直接和V层打交道,实现解耦和的效果
mvp模式会存在一个内存泄漏的隐患,如何解决,我们在p层写一个解绑和绑定的方法,最后在Activity中创建Presenter时进行绑定,在onDestroy中进行解绑,这样我们就解决了内存泄露的问题,我们可以抽取出一个基类的Presenter和一个基类的Activity来做这个事情,让子类不用在写这些重复的代码。但是问题又来了,既然是基类,肯定不止有一个子类来继承基类,那么也就是说子类当中定义的View接口和需要创建的Presenter都不相同,我们肯定在基类当中不能写死吧,那就使用泛型来设计。
1.创建一个基类View,让所有View接口都必须实现,这个View可以什么都不做只是用来约束类型的
2.创建一个基类的Presenter,在类上规定View泛型,然后定义绑定和解绑的抽象方法,让子类去实现
3.创建一个基类的Activity,声明一个创建Presenter的抽象方法,因为要帮子类去绑定和解绑那么就需要拿到子类的Presenter才行,但是又不能随便一个类都能绑定的,因为只有基类的Presenter中才定义了绑定和解绑的方法,所以同样的在类上可以声明泛型在,方法上使用泛型来达到目的。
4.修改Presenter和Activity中的代码,各自继承自己的基类并去除重复代码
实现步骤:
1.创建一个基类View,让所有View接口都必须实现
package com.winfo.wenjie.mvp.base;
/**
* ProjectName: MvpRxjavaRetrofitDemo
* PackageName com.winfo.wenjie.mvp.base
* FileName: com.winfo.wenjie.mvp.base.IBaseMvpView.java
* Author: wenjie
* Date: 2016-12-12 14:47
* Description: IBaseMvpView
*/
public interface IBaseMvpView {
}
**2.创建一个基类的Presenter,在类上规定View泛型,然后定义绑定和解绑的方法,对外在提供一个获取View的方法,让子类直接通过方法来获取View使用即可 **
package com.winfo.wenjie.mvp.base;
import java.lang.ref.WeakReference;
/**
* ProjectName: MvpRxjavaRetrofitDemo
* PackageName com.winfo.wenjie.mvp.base
* FileName: com.winfo.wenjie.mvp.base.BaseMvpPresenter.java
* Author: wenjie
* Date: 2016-12-12 14:47
* Description: BaseMvpPresenter
*/
public class BaseMvpPresenter<V extends IBaseMvpView> {
/**
* v层泛型引用
*/
protected V mView;
private WeakReference<V> weakReferenceView;
public void attachMvpView(V view) {
weakReferenceView = new WeakReference<>(view);
this.mView = weakReferenceView.get();
}
public void detachMvpView() {
weakReferenceView.clear();
weakReferenceView = null;
mView = null;
}
}
3.创建一个基类的Activity,声明一个创建Presenter的抽象方法,因为要帮子类去绑定和解绑那么就需拿到子类的Presenter才行,但是又不能随便一个类都能绑定的,因为只有基类的Presenter中才定义了绑定解绑的方法,所以同样的在类上可以声明泛型在方法上使用泛型来达到目的
package com.winfo.wenjie.mvp.base;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
/**
* ProjectName: MvpRxjavaRetrofitDemo
* PackageName com.winfo.wenjie.mvp.base
* FileName: com.winfo.wenjie.mvp.base.BaseMvpActivity.java
* Author: wenjie
* Date: 2016-12-12 14:47
* Description: BaseMvpActivity
*/
public abstract class BaseMvpActivity<V extends IBaseMvpView, P extends BaseMvpPresenter<V>> extends AppCompatActivity implements IBaseMvpView {
protected P mPresenter;
@SuppressWarnings("unchecked")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mPresenter == null) {
mPresenter = createPresenter();
}
mPresenter.attachMvpView((V) this);
}
protected abstract P createPresenter();
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.detachMvpView();
}
}
}
4、新建自己的prsenter继承presenter基类
package com.winfo.wenjie.mvp.presenter;
import android.text.TextUtils;
import com.winfo.wenjie.domain.Token;
import com.winfo.wenjie.mvp.base.BaseMvpPresenter;
import com.winfo.wenjie.mvp.model.OnLoadDatasListener;
import com.winfo.wenjie.mvp.model.impl.LoginModel;
import com.winfo.wenjie.mvp.view.ILoginView;
/**
* ProjectName: MvpRxjavaRetrofitDemo
* PackageName com.winfo.wenjie.mvp.presenter
* FileName: com.winfo.wenjie.mvp.presenter.LoginPresenter.java
* Author: wenjie
* Date: 2016-12-12 14:12
* Description: p层
*/
public class LoginPresenter extends BaseMvpPresenter<ILoginView> {
/**
* m层
*/
private LoginModel loginModel;
/**
* mvp模式 p层持有 v 和m 的接口引用 来进行数据的传递 起一个中间层的作用
*/
public LoginPresenter() {
/*
*示例化loginmodel对象 固定写法 Retrofit.create(Class);
*/
this.loginModel = new LoginModel();
}
/**
* 登陆
*/
public void login() {
if (mView == null) return;
if (TextUtils.isEmpty(mView.getUserName()) || TextUtils.isEmpty(mView.getPassword())) {
mView.showMsg("用户名或密码不能为空");
return;
}
loginModel.login(mView.getDialog(), "", "", "password", mView.getUserName(), mView.getPassword(), new OnLoadDatasListener<Token>() {
@Override
public void onSuccess(Token token) {
//请求成功服务器返回的数据s
mView.setText(token.getAccess_token());
}
@Override
public void onFailure(String eroor) {
//请求成功服务器返回的错误信息
mView.showMsg(eroor);
}
});
}
}
5、新建activity继承activity基类
package com.winfo.wenjie.mvp.view.impl;
import android.app.Dialog;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.winfo.wenjie.R;
import com.winfo.wenjie.mvp.base.BaseMvpActivity;
import com.winfo.wenjie.mvp.presenter.LoginPresenter;
import com.winfo.wenjie.mvp.view.ILoginView;
import com.winfo.wenjie.utils.DialogUtils;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
/**
* ProjectName: MvpRxjavaRetrofitDemo
* PackageName com.winfo.wenjie.mvp.view.impl
* FileName: com.winfo.wenjie.mvp.view.impl.MainActivity.java
* Author: wenjie
* Date: 2016-12-12 14:47
* Description: v层
*/
public class MainActivity extends BaseMvpActivity<ILoginView, LoginPresenter> implements ILoginView {
@BindView(R.id.username)
EditText etUserName;
@BindView(R.id.password)
EditText etPassword;
@BindView(R.id.result)
TextView textView;
@BindView(R.id.login)
Button btnLogin;
private Dialog dialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
dialog = DialogUtils.createLoadingDialog(this, "登陆中...");
}
@Override
public Dialog getDialog() {
return dialog;
}
@Override
public String getUserName() {
return etUserName.getText().toString();
}
@Override
public String getPassword() {
return etPassword.getText().toString();
}
@Override
public void showMsg(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void setText(String result) {
textView.setText("登录成功!Token:\n"+result);
}
@OnClick(R.id.login)
public void onClick() {
/*
* 调用登录方法进行登陆
*/
mPresenter.login();
}
@Override
protected LoginPresenter createPresenter() {
return new LoginPresenter();
}
}
6、抽象出View的一个接口,提供M层所需要的参数数据,不直接将Activity传递到P,并继承v层基类
package com.winfo.wenjie.mvp.view;
import android.app.Dialog;
import com.winfo.wenjie.mvp.base.IBaseMvpView;
/**
* ProjectName: MvpRxjavaRetrofitDemo
* PackageName com.winfo.wenjie.mvp.view.impl
* FileName: com.winfo.wenjie.mvp.view.impl.ILoginView.java
* Author: wenjie
* Date: 2016-12-12 14:11
* Description: view层的接口 由view来实现也就是mainactivity来实现该接口
*/
public interface ILoginView extends IBaseMvpView{
/**
* 获取view层的dialog
* @return retuen
*/
Dialog getDialog();
/**
* 获取用户名 参数
* @return username
*/
String getUserName();
/**
* 获取密码
* @return password
*/
String getPassword();
/**
* 弹出消息
* @param msg msg
*/
void showMsg(String msg);
/**
* 将数据返回给view
* @param result resuklt
*/
void setText(String result);
}
7、新建m层接口和m层实现类实现解耦
package com.winfo.wenjie.mvp.model;
import android.app.Dialog;
import com.winfo.wenjie.domain.Token;
import com.winfo.wenjie.mvp.model.impl.LoginModel;
/**
* ProjectName: MvpRxjavaRetrofitDemo
* PackageName: com.winfo.wenjie.mvp.model
* FileName: com.winfo.wenjie.mvp.model.ILoginModel.java
* Author: wenjie
* Date: 2017-01-03 13:54
* Description: m层接口
*/
public interface ILoginModel {
/**
* 登陆方法
* @param dialog 对话框这里传递到model不是很好,但是也没办法,因为要做一个对话框消失,同时取消请求的操作
* @param client_id client_id
* @param client_secret client_secret
* @param grant_type grant_type
* @param username 用户名
* @param password 用户密码
* @param onLoadDatasListener 监听函数
*/
void login(Dialog dialog,String client_id, String client_secret, String grant_type, String username, String password, OnLoadDatasListener<Token> onLoadDatasListener);
}
package com.winfo.wenjie.mvp.model;
/**
* ProjectName: DiycodeApp
* PackageName: com.wenjie.diycode.mvp.model
* FileName: com.wenjie.diycode.mvp.model.OnLoadDatasListener.java
* Author: wenjie
* Date: 2017-08-29 11:20
* Description:
*/
public interface OnLoadDatasListener<T> {
/**
* 成功
* @param t 数据
*/
void onSuccess(T t);
/**
* 失败
* @param eroor 错误信息
*/
void onFailure(String eroor);
}
M层实现类
package com.winfo.wenjie.mvp.model.impl;
import android.app.Dialog;
import com.winfo.wenjie.domain.Token;
import com.winfo.wenjie.mvp.model.ILoginModel;
import com.winfo.wenjie.mvp.model.OnLoadDatasListener;
import com.winfo.wenjie.request.ApiService;
import com.winfo.wenjie.request.DialogSubscriber;
import com.winfo.wenjie.request.OkHttpUtils;
import com.winfo.wenjie.request.ResponseResult;
import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
/**
* ProjectName: MvpRxjavaRetrofitDemo
* PackageName com.winfo.wenjie.mvp.model
* FileName: com.winfo.wenjie.mvp.model.impl.LoginModel.java
* Author: wenjie
* Date: 2016-12-12 14:47
* Description: m层实现类
*/
public class LoginModel implements ILoginModel {
@Override
public void login(Dialog dialog, String client_id, String client_secret, String grant_type, String username, String password, final OnLoadDatasListener<Token> onLoadDatasListener) {
/*
* 被订阅者
*/
Observable<Token> observable = OkHttpUtils.getRetrofit().create(ApiService.class).getToken(client_id, client_secret, grant_type, username, password);
/*
* 订阅者
*/
Subscriber<Token> subscriber = new DialogSubscriber<Token>(dialog , true) {
@Override
protected void onSuccess(Token token) {
onLoadDatasListener.onSuccess(token);
}
@Override
protected void onFailure(String msg) {
onLoadDatasListener.onFailure(msg);
}
};
observable.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
}
rxjava和retrofit+okhttp具体应用在M层中,通过rxjava的异步,以及okhttp网络请求结合获取数据请求接口
下面封装了一个网络请求
ApiService主要是项目中所有需要调用的的接口,具体为什么这么写,不做多介绍了
package com.winfo.wenjie.request;
import com.winfo.wenjie.domain.Token;
import com.winfo.wenjie.utils.Constant;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
import rx.Observable;
/**
* ProjectName: MvpRxjavaRetrofitDemo
* PackageName: com.winfo.wenjie.request
* FileName: com.winfo.wenjie.request.ApiService.java
* Author: wenjie
* Date: 2017-01-17 16:54
* Description:
*/
public interface ApiService {
/**
* 获取 Token (一般在登录时调用)
*
* @param client_id 客户端 id
* @param client_secret 客户端私钥
* @param grant_type 授权方式 - 密码
* @param username 用户名
* @param password 密码
* @return Token 实体类
*/
@POST("oauth/token")
@FormUrlEncoded
Observable<Token> getToken(
@Field("client_id") String client_id, @Field("client_secret") String client_secret,
@Field("grant_type") String grant_type, @Field("username") String username,
@Field("password") String password);
}
package com.winfo.wenjie.request;
/**
* ProjectName: MvpRxjavaRetrofitDemo
* PackageName com.winfo.wenjie.request
* FileName: com.winfo.wenjie.request.DialogCancelListener.java
* Author: wenjie
* Date: 2016-12-12 14:32
* Description: 对话框隐藏或者消失之后取消请求
*/
public interface DialogCancelListener {
/**
* 取消网络请求
*/
void onCancel();
}
创建一个Handler操作对话框,可以取消请求
package com.winfo.wenjie.request;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Message;
/**
* ProjectName: MvpRxjavaRetrofitDemo
* PackageName com.winfo.wenjie.request
* FileName: com.winfo.wenjie.request.DialogHandler.java
* Author: wenjie
* Date: 2016-12-12 14:28
* Description: 创建一个dialoghandler类来操作dialog加载进度框 以便于 请求取消的处理
*/
public class DialogHandler extends Handler {
/**
* 显示加载框
*/
static final int SHOW_PROGRESS_DIALOG = 1;
/**
* 隐藏加载框
*/
static final int DISMISS_PROGRESS_DIALOG = 2;
private Dialog loadingDialog;
private DialogCancelListener dialogCancelListener;
/**
* 构造方法接收一个加载框的对象 由各个view层创建之后传进来 因为每个对话框所提示的内容有所不同
* @param dialog dialog
*/
DialogHandler(Dialog dialog, DialogCancelListener dialogCancelListener) {
this.loadingDialog = dialog;
this.dialogCancelListener = dialogCancelListener;
initDialogDismissListenner();
}
private void initDialogDismissListenner() {
loadingDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
dialogCancelListener.onCancel();
}
});
}
/**
* 显示加载框
*/
private void showLodingDialog() {
loadingDialog.show();
}
/**
* 隐藏加载框
*/
private void dismissLodingDialog() {
loadingDialog.dismiss();
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PROGRESS_DIALOG:
showLodingDialog();
break;
case DISMISS_PROGRESS_DIALOG:
dismissLodingDialog();
break;
}
}
}
封装订阅者
package com.winfo.wenjie.request;
import android.app.Dialog;
import android.text.TextUtils;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import rx.Subscriber;
/**
* ProjectName: MvpRxjavaRetrofitDemo
* PackageName com.winfo.wenjie.request
* FileName: com.winfo.wenjie.request.DialogSubscriber.java
* Author: wenjie
* Date: 2016-12-12 14:23
* Description: 订阅者
*/
public abstract class DialogSubscriber<T> extends Subscriber<T> implements DialogCancelListener {
private boolean isShowDialog;
/**
* 定义一个请求成功的抽象方法 子类必须实现并在实现中进行处理服务器返回的数据
*
* @param t 服务器返回的数据
*/
protected abstract void onSuccess(T t);
/**
* 定义一个请求失败的抽象方法 子类必须实现并在实现中进行服务器返回数据的处理
*
* @param msg 服务器返回的错误信息
*/
protected abstract void onFailure(String msg);
private DialogHandler dialogHandler;
/**
*
* @param dialog 对话框
* @param isShowDialog 是否显示加载的对话框
*/
protected DialogSubscriber(Dialog dialog, boolean isShowDialog) {
this.isShowDialog = isShowDialog;
dialogHandler = new DialogHandler(dialog , this);
}
/**
* 显示对话框 发送一个显示对话框的消息给dialoghandler 由他自己处理(也就是dialog中hanldermesage处理该消息)
*/
private void showProgressDialog() {
if (dialogHandler != null) {
dialogHandler.obtainMessage(DialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget();
}
}
/**
* 隐藏对话框 ....
*/
private void dismissProgressDialog() {
if (dialogHandler != null) {
dialogHandler.obtainMessage(DialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget();
dialogHandler = null;
}
}
/**
* 请求开始
* 先判断isShowDialog的值,如果为false就不显示对话框,为true才显示
*/
@Override
public void onStart() {
if(isShowDialog){
showProgressDialog();
}
}
/**
* 请求完成,隐藏对话框
*/
@Override
public void onCompleted() {
dismissProgressDialog();
}
/**
* 请求出错
* 这里异常处理的不是很完善,你们自己多写一些请求可能出现的异常
* 进行捕获,这样可以直接将异常信息返回到view层可见页面,开发时一眼也可以看出具体的问题
* @param e e
*/
@Override
public void onError(Throwable e) {
dismissProgressDialog();
String msg;
if (e instanceof SocketTimeoutException) {
msg = "请求超时。请稍后重试!";
} else if (e instanceof ConnectException) {
msg = "请求超时。请稍后重试!";
} else {
msg = "请求未能成功,请稍后重试!";
}
if (!TextUtils.isEmpty(msg)) {
onFailure(msg);
}
}
/**
* 请求成功
*
* @param t t
*/
@Override
public void onNext(T t) {
/*
* 请求成功将数据发出去
*/
onSuccess(t);
}
/**
* 请求被取消
*/
@Override
public void onCancel() {
OkHttpUtils
package com.winfo.wenjie.request;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* ProjectName: MvpRxjavaRetrofitDemo
* PackageName: com.winfo.wenjie.request
* FileName: com.winfo.wenjie.request.OkHttpUtils.java
* Author: wenjie
* Date: 2016-12-12 14:17
* Description: 网络请求的工具类
*/
public class OkHttpUtils {
/**
* okhttp
*/
private static OkHttpClient okHttpClient;
/**
* Retrofit
*/
private static Retrofit retrofit;
/**
* 获取Retrofit的实例
*
* @return retrofit
*/
public static Retrofit getRetrofit() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl("https://www.diycode.cc/")
.addConverterFactory(GsonConverterFactory.create())
.client(getOkHttpClient())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
return retrofit;
}
private static OkHttpClient getOkHttpClient() {
if (okHttpClient == null) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(15, TimeUnit.SECONDS);
okHttpClient = builder.build();
}
return okHttpClient;
}
}