BasePresenter的優化
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基類。目的是統一處理綁定和解綁 * 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()内容,每次請求資料都重複建立一個Retrofit,絕對不能接受的,是以我們要對retrofit進行統一的封裝,其中需使用單例模式。類中注釋比較清楚,直接看下面内容:
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; // 讀取資料逾時時間 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;
/** * 建立單例 */
public static RetrofitManager getInstace() {
if (instance == null) {
synchronized (RetrofitManager.class) {
instance = new RetrofitManager();
}
}
return instance;
}
/** * 擷取retrofit */
public Retrofit getRetrofit() {
return mRetrofit;
}
/** * 建立服務類 * @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實作類裡重複的寫下面方法
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()的問題,在此大緻的說一下思路了,建立一個ProgressSubscriber.java讓它繼承自Subscriber,并添加網絡請求時所需要的加載框,在onStart()方法中顯示加載框【加載框我會傳一個參數來控制是否需要加載】,在onCompleted()、onError()隐藏加載框,再通過接口回調将onNext()中産生的資料回調給presenter中去通知UI更新就行,
這裡一共建立了5個類,如下:
ProgressCancelListener
主要是dialog取消時的回調時的接口,會在取消時執行解綁操作
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回調,統一歸結為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;
/** 并添加網絡請求時所需要的加載框,異常情況統一處理 * 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, "但是拒絕執行它");
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, "網絡連接配接失敗,請檢查是否聯網");
} else {
errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_UNKNOWN, "未知錯誤");
}
mSubscriberOnResponseListenter.error(errorBean);
}
//成功執行下一步
@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對傳回的結果進行預處理。也是鍊式結構中重要的一環。具體請看注釋:
package com.example.framework.appframework.http;
import com.example.framework.dataframework.model.BaseResultBean;
import rx.functions.Func1;
/**預處理: * 由于我們每次請求的時候有可能會出現一些請求的錯誤, * 若傳回碼非正确,且伺服器傳回的錯誤資訊可能是使用者讀不懂的資訊。 * 比如豆瓣一個錯誤碼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("參數不全");
break;
//這裡不再全部寫出,實際根據情況寫自己的業務處理
}
}
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) {
//擷取資料
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頁面是否列印出請求結果!