java版:https://github.com/wj576038874/mvp-rxjava-retrofit-okhttp
kotlin版:https://github.com/wj576038874/MvpKotlin
代碼如下:
項目結構:

至于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() {
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;
}
}