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 implements IPresenter {
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 create(Class service) {
return mRetrofit.create(service);
}
/**
* rx订阅
*/
public void toSubscribe(Observable o, Subscriber 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 void toSubscribe(Observable o, Subscriber 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 {
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 extends Subscriber 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 implements Func1 {
@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 implements Presenter {
public TestPresenterImpl(Context mContext) {
this.mContext = mContext;
}
@Override
public void getMovieListData(int start, int count) {
//获取数据
HashMap map=new HashMap<>();
map.put("start", StringUtils.getString(start));
map.put("count", StringUtils.getString(count));
rx.Observable observable = RetrofitManager.getInstace().create(ApiService.class).getMovieListData(map).map((new HttpResultFunc()));
Subscription rxSubscription = new ProgressSubscriber<>(new SubscriberOnResponseListenter() {
@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页面是否打印出请求结果!
相关链接
(五)安卓框架搭建之BaseFragment,MainActivity, Toolbar细化
github源码地址