一.介紹
目前使用較為廣泛的網(wǎng)絡(luò)請(qǐng)求框架 MVP+Retrofit2+okhttp3+Rxjava2,我于2017年也加入了使用行列,在網(wǎng)上找了許多案例,實(shí)際項(xiàng)目開發(fā)中解決了一些所謂的坑,總結(jié)了些內(nèi)容與大家共享一下。
1.什么是MVP?

在圖中有三個(gè)模塊view(界面),presenter(控制層),model(數(shù)據(jù)源)。他們?cè)谶@個(gè)項(xiàng)目中中擔(dān)任什么角色呢?
2. MVP運(yùn)行的過程
- Model: 數(shù)據(jù)層,負(fù)責(zé)與網(wǎng)絡(luò)層和數(shù)據(jù)庫層的邏輯交互。
- View: UI層,顯示數(shù)據(jù), 并向Presenter報(bào)告用戶行為。
- Presenter: 從Model拿數(shù)據(jù),應(yīng)用到UI層,管理UI的狀態(tài),響應(yīng)用戶的行為。
- 用戶在view層告訴presenter我要數(shù)據(jù)
- presenter告訴model我要數(shù)據(jù)
- model訪問網(wǎng)絡(luò)得到了數(shù)據(jù)再通知presenter給你我取到的數(shù)據(jù)
- presenter 處理好數(shù)據(jù) 再把數(shù)據(jù)傳遞給view
- 最后view將拿到的數(shù)據(jù)顯示出來給用戶觀看
3.MVP和MVC的區(qū)別
MVC首先就是理解比較容易,技術(shù)含量不高,這對(duì)開發(fā)和維護(hù)來說成本較低也易于維護(hù)與修改。表現(xiàn)層與業(yè)務(wù)層分離各司其職,對(duì)開發(fā)來說很有利,但是MVC的每個(gè)構(gòu)件在使用之前都需要經(jīng)過徹底的測(cè)試,代碼難以復(fù)用。
在MVP里,Presenter完全把Model和View進(jìn)行了分離,主要的程序邏輯在Presenter里實(shí)現(xiàn),而且Presenter與具體的view是沒有一點(diǎn)關(guān)聯(lián)的,而是通過定義好的接口進(jìn)行交互,從而使得在變更View的同時(shí)可以保持Presenter不變,可以復(fù)用。
在MVP模式里,View只應(yīng)該有簡(jiǎn)單的Set/Get方法,用戶輸入和設(shè)置顯示的內(nèi)容,除此不應(yīng)該有更多的內(nèi)容,絕不允許直接訪問Model,這就是與MVC最大的不同之處。
二.框架的搭建
1.搭建框架的依賴
采用Retrofit2+Rxjava2+Rxandroid+okhttp3 搭建網(wǎng)絡(luò)請(qǐng)求框架
implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
2.創(chuàng)建工具類:RetrofitUtils、OkHttp3Utils
- RetrofitUtils和OkHttp3Utils的特性:
- 使用okhttp3作為請(qǐng)求接口;
- 以觀察者模式創(chuàng)建實(shí)例;
- 使用gson作為數(shù)據(jù)轉(zhuǎn)換器;
- 添加各種攔截器,如日志攔截,請(qǐng)求頭攔截,請(qǐng)求參數(shù)攔截等等
- 開啟數(shù)據(jù)緩存,無網(wǎng)絡(luò)時(shí)可從緩存讀取數(shù)據(jù);
- 輔助類靜態(tài)方法獲取OkHttp3Utils實(shí)例。
詳細(xì)代碼如下:
RetrofitUtils工具類封裝
封裝可以設(shè)置多個(gè)BaseUrl,應(yīng)對(duì)項(xiàng)目對(duì)接多業(yè)務(wù)方的需求
public abstract class RetrofitUtils {
private Retrofit mRetrofit = null;
private Retrofit mRetrofit2 = null;
private OkHttpClient mOkHttpClient;
/**
* 獲取Retrofit對(duì)象
*
* @return
*/
public Retrofit getRetrofit() {
if (null == mRetrofit) {
if (null == mOkHttpClient) {
OkHttp3Utils okHttp3Utils = new OkHttp3Utils();
mOkHttpClient = okHttp3Utils.getOkHttpClient();
}
mRetrofit = new Retrofit.Builder()
.baseUrl(BaseUrlUtil.BaseServiceUrl)
.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(mOkHttpClient)
.build();
}
return mRetrofit;
}
/**
* 獲取Retrofit對(duì)象
*這個(gè)主要是為了應(yīng)對(duì)多個(gè)BaseUrl而準(zhǔn)備的
* @return
*/
public Retrofit getRetrofit2() {
if (null == mRetrofit2) {
if (null == mOkHttpClient) {
OkHttp3Utils okHttp3Utils = new OkHttp3Utils();
mOkHttpClient = okHttp3Utils.getOkHttpClient();
}
mRetrofit2 = new Retrofit.Builder()
.baseUrl(BaseUrlUtil.BaseServiceUrl2)
.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(mOkHttpClient)
.build();
}
return mRetrofit2;
}
public class NullOnEmptyConverterFactory extends Converter.Factory {
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
return new Converter<ResponseBody, Object>() {
@Override
public Object convert(ResponseBody body) throws IOException {
if (body.contentLength() == 0) return null;
return delegate.convert(body);
}
};
}
}
}
OkHttp3Utils工具類封裝
自定義攔截器,可以按照自己的需求設(shè)置請(qǐng)求頭的參數(shù),同時(shí)對(duì)cookies做了自動(dòng)化管理,對(duì)cookers管理更方便
public class OkHttp3Utils {
private OkHttpClient mOkHttpClient;
Activity activity = AppManager.topActivity();
private Handler updateHandler = new Handler() {
@Override
public void dispatchMessage(Message msg) {
super.dispatchMessage(msg);
if (msg.what == 401) {
//401 token失效
if (activity != null && !activity.isDestroyed()) {
try {
PreferenceHelper.write(PreferenceHelper.DEFAULT_FILE_NAME, AppConfig.PREFER_TOKEN_TAG, "");
DialogView dialogView = new DialogView(activity, 180, 180, R.layout.my_dialog, R.style.dialog) {
@Override
public void isdismiss(int tag) {
if (tag == DialogView.CANCEL_BUTTON_CLICK) {
}
}
};
dialogView.showdialog2("溫馨提示", "登錄失效,請(qǐng)重新登錄", "去登錄", "");
} catch (Exception es) {
es.printStackTrace();
}
}
} else if (msg.what == 300) {
Toast.makeText(activity, "暫無網(wǎng)絡(luò)", Toast.LENGTH_SHORT).show();
}
}
};
//設(shè)置緩存目錄
private File cacheDirectory = new File(MyApplication.getInstance().getApplicationContext().getCacheDir().getAbsolutePath(), "MyCache");
private Cache cache = new Cache(cacheDirectory, 10 * 1024 * 1024);
/**
* 獲取OkHttpClient對(duì)象
*
* @return
*/
public OkHttpClient getOkHttpClient() {
if (null == mOkHttpClient) {
//同樣okhttp3后也使用build設(shè)計(jì)模式
mOkHttpClient = new OkHttpClient.Builder()
//添加攔截器
.addInterceptor(new MyIntercepter())
//設(shè)置一個(gè)自動(dòng)管理cookies的管理器
.cookieJar(new CookiesManager())
//添加網(wǎng)絡(luò)連接器
// .addNetworkInterceptor(new CookiesInterceptor(MyApplication.getInstance().getApplicationContext()))
//設(shè)置請(qǐng)求讀寫的超時(shí)時(shí)間
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.cache(cache)//設(shè)置緩存
.retryOnConnectionFailure(true)//自動(dòng)重試
.build();
}
return mOkHttpClient;
}
/**
* 攔截器
*/
private class MyIntercepter implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!isNetworkReachable(MyApplication.instance.getApplicationContext())) {
updateHandler.sendEmptyMessage(300);
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)//無網(wǎng)絡(luò)時(shí)只從緩存中讀取
.build();
}
Request.Builder RequestBuilder = request.newBuilder();
Request build;
build = RequestBuilder
.removeHeader("User-Agent")
.addHeader("User-Agent", getUserAgent())
.addHeader("Authorization", "")
.build();
Response response = chain.proceed(build);
int code = response.code();
//對(duì)個(gè)別鏈接地址做處理
HttpUrl url = response.request().url();
System.out.println("我的網(wǎng)址"+url);
updateHandler.sendEmptyMessage(code);
if (code == 401) {
//跳轉(zhuǎn)到登錄頁面
updateHandler.sendEmptyMessage(401);
} else if (code == 402) {
//跳轉(zhuǎn)到開戶審核中界面
updateHandler.sendEmptyMessage(402);
} else if (code == 403) {
//跳轉(zhuǎn)到開戶界面
updateHandler.sendEmptyMessage(403);
}
return response;
}
}
private static String getUserAgent() {
String userAgent = "";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
try {
userAgent = WebSettings.getDefaultUserAgent(MyApplication.getInstance().getApplicationContext());
} catch (Exception e) {
userAgent = System.getProperty("http.agent");
}
} else {
userAgent = System.getProperty("http.agent");
}
StringBuffer sb = new StringBuffer();
for (int i = 0, length = userAgent.length(); i < length; i++) {
char c = userAgent.charAt(i);
if (c <= '\u001f' || c >= '\u007f') {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* 自動(dòng)管理Cookies
*/
private class CookiesManager implements CookieJar {
private final PersistentCookieStore cookieStore = new PersistentCookieStore(MyApplication.getInstance().getApplicationContext());
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
if (cookies != null && cookies.size() > 0) {
for (Cookie item : cookies) {
cookieStore.add(url, item);
}
}
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
List<Cookie> cookies = cookieStore.get(url);
return cookies;
}
}
/**
* 判斷網(wǎng)絡(luò)是否可用
*
* @param context Context對(duì)象
*/
@RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
public Boolean isNetworkReachable(Context context) {
ConnectivityManager cm = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo current = cm.getActiveNetworkInfo();
if (current == null) {
return false;
}
return (current.isAvailable());
}
}
線程切換操作的封裝
public class BaseNetWork extends RetrofitUtils{
/**https://github.com/r17171709/Retrofit2Demo
* 插入觀察者
* @param observable
* @param observer
* @param <T>
*/
public <T> void setSubscribe(Observable<T> observable, Observer<T> observer) {
observable.subscribeOn(Schedulers.io())
.subscribeOn(Schedulers.newThread())//子線程訪問網(wǎng)絡(luò)
.observeOn(AndroidSchedulers.mainThread())//回調(diào)到主線程
.subscribe(observer);
}
}
下面就是實(shí)體接口的調(diào)用
public class UserNetWork extends BaseNetWork {
protected final NetService service = getRetrofit().create(NetService.class);
private interface NetService {
//獲取首頁輪播圖
@GET("api/AppPubilc/get_lunbotu")
Observable<LunBoTuEntity> toGetLunBoTuEntity();
}
//首頁輪播圖
public void toGetLunBoTuEntity(Observer<LunBoTuEntity> observer) {
setSubscribe(service.toGetLunBoTuEntity(), observer);
}
}
以上就是Retrofit2+Rxjava2+Rxandroid+okhttp3的高度封裝的網(wǎng)絡(luò)框架,自定義攔截器可以攔截請(qǐng)求地址,動(dòng)態(tài)添加請(qǐng)求頭里的參數(shù),同時(shí)對(duì)網(wǎng)絡(luò)請(qǐng)求響應(yīng)code碼做相應(yīng)的操作。面對(duì)一個(gè)項(xiàng)目對(duì)接多個(gè)業(yè)務(wù)方,存在多個(gè)BaseUrl,該網(wǎng)絡(luò)框架封裝了可以設(shè)置多個(gè)BaseUrl的。
那么Retrofit2網(wǎng)絡(luò)框架的網(wǎng)絡(luò)框架搭建完了,下面來看一下MVP架構(gòu)的設(shè)計(jì)吧,不要走開!
下面是項(xiàng)目的整體架構(gòu)圖

創(chuàng)建BaseView基類,用于添加自定義回調(diào),根據(jù)需求可做擴(kuò)展,此處只封裝了些最為常用的方法
public interface BaseView {
void showLoadingDialog(String msg);
void dismissLoadingDialog();
/**
* 顯示錯(cuò)誤信息
*
* @param msg
*/
void showError(String msg);
/**
* 錯(cuò)誤碼
*/
void onErrorCode(BaseModel model);
}
創(chuàng)建Presenter基類,提供M層和V層通訊橋梁
public interface BasePresenter {
//默認(rèn)初始化
void start();
//Activity關(guān)閉把view對(duì)象置為空
void detach();
//將網(wǎng)絡(luò)請(qǐng)求的每一個(gè)disposable添加進(jìn)入CompositeDisposable,再退出時(shí)候一并注銷
void addDisposable(Disposable subscription);
//注銷所有請(qǐng)求
void unDisposable();
}
創(chuàng)建一個(gè)PresenterImpl,用于統(tǒng)一處理網(wǎng)絡(luò)請(qǐng)求的生命周期,在activity退出時(shí)統(tǒng)一注銷觀察者模式,解綁觀察者的情況下調(diào)用unDisposable()統(tǒng)一解綁,防止Rx造成的內(nèi)存泄漏。
/**
* 總控制層
* @param <V>
*/
public abstract class BasePresenterImpl<V extends BaseView> implements BasePresenter {
protected V view;
public BasePresenterImpl(V view) {
this.view = view;
start();
}
@Override
public void detach() {
this.view = null;
unDisposable();
}
@Override
public void start() {
}
//將所有正在處理的Subscription都添加到CompositeSubscription中。統(tǒng)一退出的時(shí)候注銷觀察
private CompositeDisposable mCompositeDisposable;
/**
* 將Disposable添加
*
* @param subscription
*/
@Override
public void addDisposable(Disposable subscription) {
//csb 如果解綁了的話添加 sb 需要新的實(shí)例否則綁定時(shí)無效的
if (mCompositeDisposable == null || mCompositeDisposable.isDisposed()) {
mCompositeDisposable = new CompositeDisposable();
}
mCompositeDisposable.add(subscription);
}
/**
* 在界面退出等需要解綁觀察者的情況下調(diào)用此方法統(tǒng)一解綁,防止Rx造成的內(nèi)存泄漏
*/
@Override
public void unDisposable() {
if (mCompositeDisposable != null) {
mCompositeDisposable.dispose();
}
}
}
創(chuàng)建實(shí)體類基類,統(tǒng)一處理后臺(tái)接口返回的數(shù)據(jù),做統(tǒng)一處理
public class TradeSimpleResult implements Serializable{
/**
* Success : false
* StatusCode : 500
* Message : 處理失敗
* ErrorInfo : {"ErrorMessage":"請(qǐng)輸入真實(shí)的身份證姓名信息","ErrorCode":"-1"}
*/
private boolean Success;
private int StatusCode;
private String Message;
private ErrorInfoBean ErrorInfo;
public boolean isSuccess() {
return Success;
}
public void setSuccess(boolean Success) {
this.Success = Success;
}
public int getStatusCode() {
return StatusCode;
}
public void setStatusCode(int StatusCode) {
this.StatusCode = StatusCode;
}
public String getMessage() {
return Message;
}
public void setMessage(String Message) {
this.Message = Message;
}
public ErrorInfoBean getErrorInfo() {
return ErrorInfo;
}
public void setErrorInfo(ErrorInfoBean ErrorInfo) {
this.ErrorInfo = ErrorInfo;
}
public static class ErrorInfoBean {
/**
* ErrorMessage : 請(qǐng)輸入真實(shí)的身份證姓名信息
* ErrorCode : -1
*/
private String ErrorMessage;
private String ErrorCode;
public String getErrorMessage() {
return ErrorMessage;
}
public void setErrorMessage(String ErrorMessage) {
this.ErrorMessage = ErrorMessage;
}
public String getErrorCode() {
return ErrorCode;
}
public void setErrorCode(String ErrorCode) {
this.ErrorCode = ErrorCode;
}
}
}
好了,以上就是MVP架構(gòu)的搭建以及接口返回?cái)?shù)據(jù)的統(tǒng)一處理。代碼比較多,但是前后邏輯是很連貫的。那么下面就來做一個(gè)實(shí)際的接口請(qǐng)求看看效果。
public class MainActivity extends LifecycleBaseActivity<TestContact.presenter> implements TestContact.view {
private TextView textView;
private HashMap<Object, Object> map;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
map = new HashMap<>();
initView();
initData();
}
private void initData() {
presenter.getData(map, "first");
}
private void initView() {
textView = (TextView) findViewById(R.id.main_text);
}
/**
* 初始化presenter
*
* @return 對(duì)應(yīng)的presenter
*/
@Override
public TestContact.presenter initPresenter() {
return new TestPresenter(this, MainActivity.this);
}
/**
* 設(shè)置數(shù)據(jù)
* 刷新界面
*
* @param lunBoTuEntity 數(shù)據(jù)源
*/
@Override
public void setData(LunBoTuEntity lunBoTuEntity, String tag) {
if ("LunBoTu".equals(tag)) {
String imageUrl = lunBoTuEntity.getResult().getList().get(0).getImageUrl();
System.out.println("圖片地址:" + imageUrl);
}
}
@Override
public void ErrorData(Throwable e) {
}
@Override
public void showLoadingDialog(String msg) {
textView.setText(msg);
}
@Override
public void dismissLoadingDialog() {
}
}
以上的LifecycleBaseActivity 和 LifecycleBaseFragment大家可以在下面的鏈接地址里面去看,這個(gè)是Goolge官方架構(gòu)AAC(Android Architecture Component)的生命周期管理框架,Lifecycle類持有Activity 或 Fragment等組件的生命周期信息,并且允許其他對(duì)象觀察這些信息。Lifecycle內(nèi)部使用了兩個(gè)枚舉來跟蹤其關(guān)聯(lián)組件的生命周期狀態(tài):Event和State。祥見下面分析。可以通過調(diào)用Lifecycle類的 addObserver() 方法來添加觀察者,如下:
getLifecycle().addObserver(new TestLifeCycle());
我在LifecycleBaseActivity做了部分處理,使用起來更加的便捷、易懂。
現(xiàn)在Retrofit2+Rxjava2+Rxandroid+okhttp3+Lifecycle 的MVP網(wǎng)絡(luò)框架,結(jié)合了Google官方AAC框架,實(shí)現(xiàn)APP生命周期的管理整體架構(gòu)就做好了,文章里涉及帶網(wǎng)絡(luò)框架、MVP架構(gòu)和生命周期的管理,那么一套完整的App框架就搭建好了。
2019.05.09功能新增:
1.新增token過期自動(dòng)刷新token,刷新后再請(qǐng)求一次接口的功能
github地址:https://github.com/zengweitao/Treasure
CSDN地址:https://blog.csdn.net/weitao_666/article/details/89235135
源碼下載地址:https://download.csdn.net/download/weitao_666/11110313