BasePresenter的優(yōu)化
RxJava也需要管理生命周期王悍,即添加訂閱和解除訂閱。這里我們使之與presenter的addtachView()和detachView()同步鲤妥,修改BasePresenter里面內容如下:
package com.example.burro.demo.appframework.mvp.presenter;
import com.example.burro.demo.appframework.mvp.view.BaseView;
import rx.Subscription;
import rx.subscriptions.CompositeSubscription;
/**
* Presenter基類歌溉。目的是統(tǒng)一處理綁定和解綁
* Created by ex.zhong on 2017/9/23.
*/
public class BasePresenter<T extends BaseView> implements IPresenter<T> {
protected T mView;
protected CompositeSubscription mCompositeSubscription;
@Override
public void attachView(T mView) {
mView = mView;
}
@Override
public void detachView() {
mView = null;
unSubscribe();
}
//增加訂閱者
protected void addSubscrebe(Subscription subscription) {
if (mCompositeSubscription == null) {
mCompositeSubscription = new CompositeSubscription();
}
mCompositeSubscription.add(subscription);
}
//解綁訂閱者
protected void unSubscribe() {
if (mCompositeSubscription != null) {
mCompositeSubscription.unsubscribe();
}
}
}
RetrofitManager的封裝
看下先前TestPresenterImpl中的getMovieListData()內容云芦,每次請求數(shù)據(jù)都重復新建一個Retrofit厘擂,絕對不能接受的,因此我們要對retrofit進行統(tǒng)一的封裝嚼吞,其中需使用單例模式幔嫂。類中注釋比較清楚,直接看下面內容:
package com.example.burro.demo.appframework.http;
/**
* Created by ex.zhong on 2017/9/24.
*/
import com.example.burro.demo.appframework.BaseApplication;
import com.example.burro.demo.appframework.util.FileUtils;
import com.example.burro.demo.appframework.util.LogUtils;
import com.example.burro.demo.appframework.util.NetWorkUtils;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
/**
* retrofit封裝方法
* Created by ex.zhong on 2017/8/13.
*/
public class RetrofitManager {
private static RetrofitManager instance;
public static int MAXSTALE = 60 * 60 * 24 * 28; // 無網絡時誊薄,設置超時為4周
public static int CONNECT_OUTTIME = 10; // 鏈接超時時間 unit:S
public static int READ_OUTTIME = 20; // 讀取數(shù)據(jù)超時時間 unit:S
public static int WRITE_OUTTIME = 20; // 寫入超時時間 unit:S
public static long CACHE_SIZE = 1024*1024*50; // 緩存大小 50M
private final OkHttpClient mOkHttpClient;
private final Retrofit mRetrofit;
/**
* 創(chuàng)建單例
*/
public static RetrofitManager getInstace() {
if (instance == null) {
synchronized (RetrofitManager.class) {
instance = new RetrofitManager();
}
}
return instance;
}
/**
* 獲取retrofit
*/
public Retrofit getRetrofit() {
return mRetrofit;
}
/**
* 創(chuàng)建服務類
* @return
*/
public <T> T create(Class<T> service) {
return mRetrofit.create(service);
}
/**
* rx訂閱
*/
public <T> void toSubscribe(Observable<T> o, Subscriber<T> s) {
o.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s);
}
private RetrofitManager() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
//打印日志
if(LogUtils.LOG_FLAG){
// https://drakeet.me/retrofit-2-0-okhttp-3-0-config
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(loggingInterceptor);
}
//設置緩存
File cacheFile=new File(FileUtils.getInstance().getHttpCachePath());
Cache cache=new Cache(cacheFile,CACHE_SIZE);
Interceptor cacheInterceptor=new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!NetWorkUtils.isOnline(BaseApplication.getAppContext())) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response response = chain.proceed(request);
if (NetWorkUtils.isOnline(BaseApplication.getAppContext())) {
int maxAge = 0;
response.newBuilder()
.header("Cache-Control", "public, max-age=" + maxAge)
.removeHeader("Pragma")
.build();
} else {
response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + MAXSTALE)
.removeHeader("Pragma")
.build();
}
return response;
}
};
//設置緩存
builder.addNetworkInterceptor(cacheInterceptor);
builder.addInterceptor(cacheInterceptor);
builder.cache(cache);
//設置超時
builder.connectTimeout(CONNECT_OUTTIME, TimeUnit.SECONDS);
builder.readTimeout(READ_OUTTIME, TimeUnit.SECONDS);
builder.writeTimeout(WRITE_OUTTIME, TimeUnit.SECONDS);
//錯誤重連
builder.retryOnConnectionFailure(true);
mOkHttpClient = builder.build();
mRetrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(HttpConfig.BASE_URL)
.client(mOkHttpClient)
.build();
}
}
這里注意的是我把rxjava的訂閱也放在了RetrofitManager內履恩,這樣不必每次在presenter實現(xiàn)類里重復的寫下面方法
public <T> void toSubscribe(Observable<T> o, Subscriber<T> s) {
o.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s);
}
Subscriber的封裝
我們再看時解決每次重寫Subscriber的這四個onstart()、onCompleted()呢蔫、onError()切心、onNext()的問題,在此大致的說一下思路了片吊,創(chuàng)建一個ProgressSubscriber.java讓它繼承自Subscriber绽昏,并添加網絡請求時所需要的加載框,在onStart()方法中顯示加載框【加載框我會傳一個參數(shù)來控制是否需要加載】俏脊,在onCompleted()全谤、onError()隱藏加載框,再通過接口回調將onNext()中產生的數(shù)據(jù)回調給presenter中去通知UI更新就行爷贫,
這里一共新建了5個類认然,如下:
ProgressCancelListener
主要是dialog取消時的回調時的接口补憾,會在取消時執(zhí)行解綁操作
package com.example.burro.demo.appframework.http;
/*progressDialog,消失時回調
*Created by ex.zhong on 2017/9/25.
*/
public interface ProgressCancelListener {
void onCancelProgress();
}
SubscriberOnResponseListenter
主要是Subscriber回調時封裝的接口,即在回調中只處理onNext()onError()
package com.example.burro.demo.appframework.http;
import com.example.burro.demo.dataframework.model.BaseResultBean;
/**Subscriber回調卷员,統(tǒng)一歸結為next()【成功】 error()【失敗】
* Created by ex.zhong on 2017/9/25
*/
public interface SubscriberOnResponseListenter<T> {
void next(T t);
void error(BaseResultBean t);
}
ProgressDialogHandler
ProgressDialogHandler實為handler盈匾,內部主要控制dialog的顯示和隱藏
package com.example.burro.demo.appframework.http;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.os.Handler;
import android.os.Message;
import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.Theme;
/** 加載框展示,隱藏控制
* Created by ex.zhong on 2017/9/25.
*/
public class ProgressDialogHandler extends Handler {
public static final int SHOW_PROGRESS_DIALOG = 1;
public static final int DISMISS_PROGRESS_DIALOG = 2;
private Context context;
private boolean cancelable;
private ProgressCancelListener mProgressCancelListener;
private MaterialDialog mProgressDialog;
public ProgressDialogHandler(Context context, ProgressCancelListener mProgressCancelListener, boolean cancelable) {
super();
this.context = context;
this.mProgressCancelListener = mProgressCancelListener;
this.cancelable = cancelable;
}
//顯示dialog
private void initProgressDialog() {
if (mProgressDialog == null) {
mProgressDialog = new MaterialDialog.Builder(context)
.canceledOnTouchOutside(cancelable)
.content("正在加載...")
.progress(true, 0)
.theme(Theme.LIGHT)
.build();
mProgressDialog.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
mProgressCancelListener.onCancelProgress();
}
});
if (!mProgressDialog.isShowing()) {
mProgressDialog.show();
}
}
}
//隱藏dialog
private void dismissProgressDialog() {
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PROGRESS_DIALOG:
initProgressDialog();
break;
case DISMISS_PROGRESS_DIALOG:
dismissProgressDialog();
break;
}
}
}
ProgressSubscriber
ProgressSubscriber繼承Subscriber,是對Subscriber的進一步封裝毕骡,即在請求開始時提示dialog【可控制】削饵,完成時,隱藏dialog等
package com.example.burro.demo.appframework.http;
import android.content.Context;
import android.net.ParseException;
import com.example.burro.demo.dataframework.model.BaseResultBean;
import com.google.gson.JsonParseException;
import org.apache.http.conn.ConnectTimeoutException;
import org.json.JSONException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import retrofit2.adapter.rxjava.HttpException;
import rx.Subscriber;
/** 并添加網絡請求時所需要的加載框未巫,異常情況統(tǒng)一處理
* Created by ex.zhong on 2017/9/25.
*/
public class ProgressSubscriber<T> extends Subscriber<T> implements ProgressCancelListener {
private SubscriberOnResponseListenter mSubscriberOnResponseListenter;
private ProgressDialogHandler mProgressDialogHandler;
private boolean isShowProgress;
public ProgressSubscriber(SubscriberOnResponseListenter mSubscriberOnResponseListenter, Context context, boolean isShowProgress) {
this.mSubscriberOnResponseListenter = mSubscriberOnResponseListenter;
this.isShowProgress = isShowProgress;
mProgressDialogHandler = new ProgressDialogHandler(context, this, false);
}
/**
* 開始訂閱的時候顯示加載框
*/
@Override
public void onStart() {
if (isShowProgress)
showProgressDialog();
}
@Override
public void onCompleted() {
dismissProgressDialog();
}
@Override
public void onError(Throwable e) {
dismissProgressDialog();
BaseResultBean errorBean;
//錯誤碼要以服務器返回錯誤碼為準窿撬。此處只是舉例
if (e instanceof HttpException) { //HTTP 錯誤
HttpException httpException = (HttpException) e;
switch (httpException.code()) {
case BaseResultBean.ERROR_CODE_UNAUTHORIZED:
errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_UNAUTHORIZED, "當前請求需要用戶驗證");
break;
case BaseResultBean.ERROR_CODE_FORBIDDEN:
errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_FORBIDDEN, "但是拒絕執(zhí)行它");
break;
case BaseResultBean.ERROR_CODE_NOT_FOUND:
errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_NOT_FOUND, "服務器異常,請稍后再試");
break;
default:
//其它均視為網絡錯誤
errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_FORBIDDEN, "網絡錯誤");
break;
}
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) {
errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_PARSE_JSON, "解析錯誤");
} else if (e instanceof ConnectException || e instanceof SocketTimeoutException || e instanceof ConnectTimeoutException) {
errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_NETWORK, "網絡連接失敗叙凡,請檢查是否聯(lián)網");
} else {
errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_UNKNOWN, "未知錯誤");
}
mSubscriberOnResponseListenter.error(errorBean);
}
//成功執(zhí)行下一步
@Override
public void onNext(T t) {
mSubscriberOnResponseListenter.next(t);
}
@Override
public void onCancelProgress() {
if (!this.isUnsubscribed()) {
this.unsubscribe();
}
}
//顯示dialog
private void showProgressDialog() {
if (mProgressDialogHandler != null) {
mProgressDialogHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget();
}
}
//隱藏dialog
private void dismissProgressDialog() {
if (mProgressDialogHandler != null) {
mProgressDialogHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget();
}
}
}
HttpResultFunc
HttpResultFunc對返回的結果進行預處理尤仍。也是鏈式結構中重要的一環(huán)。具體請看注釋:
package com.example.framework.appframework.http;
import com.example.framework.dataframework.model.BaseResultBean;
import rx.functions.Func1;
/**預處理:
* 由于我們每次請求的時候有可能會出現(xiàn)一些請求的錯誤,
* 若返回碼非正確狭姨,且服務器返回的錯誤信息可能是用戶讀不懂的信息。
* 比如豆瓣一個錯誤碼1001 msg信息為uri_not_found苏遥,此時我們可以轉換成用戶可以讀懂的文字饼拍,即可設置msg為“資源不存在”
* Created by ex.zhong on 2017/9/25.
*/
public class HttpResultFunc<T> implements Func1<T,T> {
@Override
public T call(T t) {
if(t!=null&&t instanceof BaseResultBean){
int code=((BaseResultBean) t).getCode();
switch (code){
case 1001:
((BaseResultBean) t).setMessage("資源不存在");
break;
case 1002:
((BaseResultBean) t).setMessage("參數(shù)不全");
break;
//這里不再全部寫出,實際根據(jù)情況寫自己的業(yè)務處理
}
}
return t;
}
}
好了田炭,封裝部分全部結束师抄,我們回頭過來看看TestPresenterImpl
package com.example.burro.demo.appbiz.test;
import android.content.Context;
import com.example.burro.demo.appframework.http.HttpResultFunc;
import com.example.burro.demo.appframework.http.ProgressSubscriber;
import com.example.burro.demo.appframework.http.RetrofitManager;
import com.example.burro.demo.appframework.http.SubscriberOnResponseListenter;
import com.example.burro.demo.appframework.mvp.presenter.BasePresenter;
import com.example.burro.demo.appbiz.test.TestContract.*;
import com.example.burro.demo.appframework.util.StringUtils;
import com.example.burro.demo.databiz.model.test.MovieListBean;
import com.example.burro.demo.databiz.service.ApiService;
import com.example.burro.demo.dataframework.model.BaseResultBean;
import java.util.HashMap;
import rx.Subscriber;
import rx.Subscription;
/**測試presenter
* Created by ex.zhong on 2017/9/23.
*/
public class TestPresenterImpl extends BasePresenter<View> implements Presenter {
public TestPresenterImpl(Context mContext) {
this.mContext = mContext;
}
@Override
public void getMovieListData(int start, int count) {
//獲取數(shù)據(jù)
HashMap<String,String> map=new HashMap<>();
map.put("start", StringUtils.getString(start));
map.put("count", StringUtils.getString(count));
rx.Observable<MovieListBean> observable = RetrofitManager.getInstace().create(ApiService.class).getMovieListData(map).map((new HttpResultFunc<MovieListBean>()));
Subscription rxSubscription = new ProgressSubscriber<>(new SubscriberOnResponseListenter<MovieListBean>() {
@Override
public void next(MovieListBean testBean) {
mView.setMovieListData(testBean);
}
@Override
public void error(BaseResultBean errResponse) {
mView.showError(errResponse);
}
},mContext,false);
RetrofitManager.getInstace().toSubscribe(observable, (Subscriber) rxSubscription);
addSubscrebe(rxSubscription);
}
}
是不是簡單了許多!教硫!注意封裝完以后要查看在TestActivity頁面是否打印出請求結果叨吮!