抓住人生中的一分一秒,胜过虚度中的一月一年!
小做个动图开篇引题
前言
目前较火的网络框架有MVP+Retrofit2+okhttp3+Rxjava2,于是也加入了使用行列,本框架为Retrofit基本写法及特殊情况处理衍生,为大家学习使用提供帮助,本次优化对使用过程中所遇到问题进行总结,基本满足实际开发需求,有不足地方我将继续完善
相关业务需求及解决方案 |
---|
一、 MVP+Retrofit2+okhttp3+Rxjava2框架基本搭建及使用 |
二、 对BaseActivity、BaseFragment封装协调框架更好使用 |
三、 Android部分手机4G网第一次请求很慢(wifi正常)解决方案 |
四、 Retrofit运行时动态改变BaseUrl解决方案 |
五、 Retrofit文件上传(本片文章介绍中包含进度条) |
六、 Retrofit文件下载(含进度条) |
七、 Retrofit,Gson解析,请求返回的类型不统一,假如double返回的是null |
八、 Retrofit实现cookie自动化管理 |
九、 路由判断第二种解决方案(文章为旧版,提供思路) |
十、 Retrofit配置及各情况处理(缓存拦截、日志打印、替换接口内容、参数添加等) |
十一、 后记 |
十二、 本文譩在一篇文章搞定所有,上述描述文章都有讲解 |
一、MVP+Retrofit2+okhttp3+Rxjava2框架基本搭建
1、 相关依赖引用
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
//ConverterFactory的Gson依赖包
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
//CallAdapterFactory的Rx依赖包
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
//cookie管理
implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
//日志
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
2、 创建接口类ApiServer,定义接口方法
public interface ApiServer {
@FormUrlEncoded
@POST("/api/table_list/")
Observable> getCeShi(@FieldMap HashMap params);
}
3、 创建Retrofit
public class ApiRetrofit {
private static ApiRetrofit mApiRetrofit;
private Retrofit retrofit;
private ApiServer apiServer;
private static final int DEFAULT_TIMEOUT = 15;
public static String mBaseUrl = BaseContent.baseUrl;
public ApiRetrofit() {
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder
.cookieJar(new CookieManger(App.getContext()))
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
retrofit = new Retrofit.Builder()
.baseUrl(mBaseUrl)
.addConverterFactory(GsonConverterFactory.create())
//支持RxJava2
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(httpClientBuilder.build())
.build();
apiServer = retrofit.create(ApiServer.class);
}
public static ApiRetrofit getInstance() {
if (mApiRetrofit == null) {
synchronized (Object.class) {
if (mApiRetrofit == null) {
mApiRetrofit = new ApiRetrofit();
}
}
}
return mApiRetrofit;
}
public ApiServer getApiService() {
return apiServer;
}
}
4、 定义常用的接口,如网络请求开始,结束,进度条加载,错误码等
public interface BaseView {
//显示dialog
void showLoading();
//隐藏 dialog
void hideLoading();
//显示错误信息
void showError(String msg);
//错误码
void onErrorCode(BaseModel model);
//进度条显示
void showProgress();
//进度条隐藏
void hideProgress();
//文件下载进度监听
void onProgress(int progress);
}
5、 BaseModel封装
封装理由:一个项目一般情况下json返回格式外层都是统一的
public class BaseModel implements Serializable {
private String reason;
private int error_code;
private T result;
public BaseModel(String reason, int error_code) {
this.reason = reason;
this.error_code = error_code;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public int getError_code() {
return error_code;
}
public void setError_code(int error_code) {
this.error_code = error_code;
}
public T getResult() {
return result;
}
public void setResult(T result) {
this.result = result;
}
}
6、 BasePresenter封装,协调m层v层的中间信使通用代码封装
public class BasePresenter {
private CompositeDisposable compositeDisposable;
public V baseView;
protected ApiServer apiServer = ApiRetrofit.getInstance().getApiService();
public BasePresenter(V baseView) {
this.baseView = baseView;
}
/**
* 解除绑定
*/
public void detachView() {
baseView = null;
removeDisposable();
}
/**
* 返回 view
*
* @return
*/
public V getBaseView() {
return baseView;
}
public void addDisposable(Observable> observable, BaseObserver observer) {
if (compositeDisposable == null) {
compositeDisposable = new CompositeDisposable();
}
compositeDisposable.add(observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(observer));
}
public void addFileDisposable(Observable> observable, FileObserver observer) {
if (compositeDisposable == null) {
compositeDisposable = new CompositeDisposable();
}
compositeDisposable.add(observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(observer));
}
public void removeDisposable() {
if (compositeDisposable != null) {
compositeDisposable.dispose();
}
}
}
6、 BaseObserver封装,数据等异常处理路由
封装理由:处理业务逻辑路由,与错误信息处理
注:在正常开发中,前后台会约定相关字段如code的值代表各情况,在此路由通道,另一种路由方案是重写Gson解析类,文章不做体现,demo中有相关代码
public abstract class BaseObserver extends DisposableObserver> {
protected BaseView view;
/**
* 网络连接失败 无网
*/
public static final int NETWORK_ERROR = 100000;
/**
* 解析数据失败
*/
public static final int PARSE_ERROR = 1008;
/**
* 网络问题
*/
public static final int BAD_NETWORK = 1007;
/**
* 连接错误
*/
public static final int CONNECT_ERROR = 1006;
/**
* 连接超时
*/
public static final int CONNECT_TIMEOUT = 1005;
/**
* 其他所有情况
*/
public static final int NOT_TRUE_OVER = 1004;
public BaseObserver(BaseView view) {
this.view = view;
}
public BaseObserver() {
}
@Override
protected void onStart() {
if (view != null) {
view.showLoading();
}
}
@Override
public void onNext(BaseModel o) {
// T t = o.getData();
try {
if (view != null) {
view.hideLoading();
}
if (o.getError_code() == BaseContent.basecode) {
onSuccess(o);
} else {
if (view != null) {
view.onErrorCode(o);
}
//非 true的所有情况
onException(PARSE_ERROR, o.getReason());
}
} catch (Exception e) {
e.printStackTrace();
onError(e.toString());
}
}
@Override
public void onError(Throwable e) {
if (view != null) {
view.hideLoading();
}
if (e instanceof HttpException) {
// HTTP错误
onException(BAD_NETWORK, "");
} else if (e instanceof ConnectException
|| e instanceof UnknownHostException) {
// 连接错误
onException(CONNECT_ERROR, "");
} else if (e instanceof InterruptedIOException) {
// 连接超时
onException(CONNECT_TIMEOUT, "");
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) {
// 解析错误
onException(PARSE_ERROR, "");
e.printStackTrace();
} else {
if (e != null) {
onError(e.toString());
} else {
onError("未知错误");
}
}
}
private void onException(int unknownError, String message) {
switch (unknownError) {
case CONNECT_ERROR:
onError("连接错误");
break;
case CONNECT_TIMEOUT:
onError("连接超时");
break;
case BAD_NETWORK:
onError("网络超时");
break;
case PARSE_ERROR:
onError("数据解析失败");
break;
//非true的所有情况
case NOT_TRUE_OVER:
onError(message);
break;
default:
break;
}
}
//消失写到这 有一定的延迟 对dialog显示有影响
@Override
public void onComplete() {
/* if (view != null) {
view.hideLoading();
}*/
}
public abstract void onSuccess(BaseModel o);
public abstract void onError(String msg);
}
如上,相关框架已封装完毕,下面看下如何使用
8、 定义MainView,并继承BaseView
public interface MainView extends BaseView {
void onTextSuccess(BaseModel o);
}
9、 定义MainPresenter,并继承BasePresenter
public class MainPresenter extends BasePresenter {
public MainPresenter(MainView baseView) {
super(baseView);
}
/**
* 写法好多种 怎么顺手怎么来
*/
public void getTextApi() {
HashMap params = new HashMap<>();
params.put("type", "junshi");
params.put("key", "2c1cb93f8c7430a754bc3ad62e0fac06");
addDisposable(apiServer.getText(params), new BaseObserver(baseView) {
@Override
public void onSuccess(BaseModel o) {
baseView.onTextSuccess((BaseModel) o);
}
@Override
public void onError(String msg) {
if (baseView != null) {
baseView.showError(msg);
}
}
});
}
}
10、 在Activity中进行网络请求,如下
public class MainActivity extends AppCompatActivity implements MainView {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainPresenter presenter = new MainPresenter(this);
//网络请求
presenter.getTextApi();
}
@Override
public void onTextSuccess(BaseModel o) {
//我是网络请求成功后的结果
}
@Override
public void showLoading() {
//网络开始请求时我会执行
}
@Override
public void hideLoading() {
//网络请求完毕时我会执行
}
@Override
public void showError(String msg) {
//异常情况下我会提示内容
}
@Override
public void onErrorCode(BaseModel model) {
//在异常时候我会回调
}
@Override
public void showProgress() {
//需要显示进度条时候我是开始标识
}
@Override
public void hideProgress() {
//需要隐藏进度条时候我是结束标识
}
@Override
public void onProgress(int progress) {
//进度条最主要的是我
}
}
第一章结束(MVP+Retrofit2+okhttp3+Rxjava2框架基本搭建及使用)
activity内容太多了,阅读性差,引发了强烈的需求对Activity封装与Fragment封装,请往下看
二、对BaseActivity、BaseFragment封装协调框架更好使用
浅谈BaseActivity写法,促使我们更高效开发
Fragment懒加载实现,BaseFragment封装
有兴趣的撸友们可以转战我其他俩篇文章,本文意在一篇掌握网络请求,如下继续介绍如何封装
1、 BaseActivity相关内容进行封装,BaseFragment可到demo中查看
public abstract class BaseActivity extends AppCompatActivity implements BaseView {
protected final String TAG = this.getClass().getSimpleName();
public Context mContext;
protected P mPresenter;
protected abstract P createPresenter();
private LoadingDialog loadingDialog;
private ProgressDialog progressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
setContentView(getLayoutId());
mPresenter = createPresenter();
setStatusBar();
this.initData();
}
/**
* 获取布局ID
*
* @return
*/
protected abstract int getLayoutId();
/**
* 数据初始化操作
*/
protected abstract void initData();
/**
* 此处设置沉浸式地方
*/
protected void setStatusBar() {
StatusBarUtil.setTranslucentForImageViewInFragment(this, 0, null);
}
/**
* 封装toast方法(自行定制实现)
*
* @param str
*/
public void showToast(String str) {
ToastUtils.show(str);
}
public void showLongToast(String str) {
ToastUtils.show(str);
}
@Override
public void showError(String msg) {
showToast(msg);
}
/**
* 返回所有状态 除去指定的值 可设置所有(根据需求)
*
* @param model
*/
@Override
public void onErrorCode(BaseModel model) {
if (model.getError_code() == 10000000) {
//处理些后续逻辑 如果某个页面不想实现 子类重写这个方法 将super去掉 自定义方法
// App.put();
// startActivity(LoginActivity.class);
}
}
@Override
public void showLoading() {
showLoadingDialog();
}
@Override
public void hideLoading() {
dissMissDialog();
}
public void showLoadingDialog() {
showLoadingDialog("加载中...");
}
/**
* 加载 黑框...
*/
public void showLoadingDialog(String msg) {
if (loadingDialog == null) {
loadingDialog = new LoadingDialog(this);
}
loadingDialog.setMessage(msg);
if (!loadingDialog.isShowing()) {
loadingDialog.show();
}
}
/**
* 消失 黑框...
*/
public void dissMissDialog() {
if (loadingDialog != null) {
loadingDialog.dismiss();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.detachView();
}
if (loadingDialog != null) {
loadingDialog.dismiss();
}
if (progressDialog != null) {
progressDialog.dismiss();
}
if (mPresenter != null) {
mPresenter.detachView();
}
}
/**
* 进度条显示
*/
@Override
public void showProgress() {
if (progressDialog == null) {
progressDialog = new ProgressDialog(this);
}
progressDialog.getProgressBar().performAnimation();
if (!progressDialog.isShowing()) {
progressDialog.show();
}
}
/**
* 进度条隐藏
*/
@Override
public void hideProgress() {
if (progressDialog != null) {
progressDialog.getProgressBar().releaseAnimation();
progressDialog.dismiss();
}
}
/**
* 进度条 回调
* @param progress
*/
@Override
public void onProgress(int progress) {
if (progressDialog != null) {
progressDialog.updateProgress(progress);
}
}
}
2、 定义MainView,并继承BaseView(同上)
3、 定义MainPresenter,并继承BasePresenter(同上)
4、 在Activity中进行网络请求,如下
public class MainActivity extends BaseActivity implements MainView {
@Override
protected MainPresenter createPresenter() {
return new MainPresenter(this);
}
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
@Override
protected void initData() {
//网络请求
mPresenter.getTextApi();
}
@Override
public void onTextSuccess(BaseModel o) {
//我是网络请求成功后的结果
}
}
三、Android部分手机4G网第一次请求很慢(wifi正常)解决方案
Android部分手机4G网第一次请求很慢(wifi正常)解决方案
1、出现此类问题场景
经测试,一般手机都没有发现网络请求慢现象,只有部分手机会出现,如(小米手机)
2、出现此类问题现象
手机4G网网络请求特别慢,第一次进入app加载网络会出现30s+延迟现象,只有第一次慢,第二次网络访问回归正常,但重新进入又会出现网络延迟30s+
3、出现此类问题排查
通过网上查阅资料,都趋向于ipv4、ipv6地址问题,经对应手机测试发现,DNS 解析的 IP 地址①.连接到wifi,只解析到 ipv4 地址
,②.连接到4G网,解析到了ipv4、ipv6俩个地址
,但是ipv6默认为集合中的第一个,是否我们可以尝试修改集合第一个为ipv4呢?
4、出现此类问题解决方案
解决方案:集合中ipv4,ipv6调换位置,将ipv4当到集合首位
调换集合中ipv4 ipv6位置,将ipv4当到集合首位
import okhttp3.Dns;
public class ApiDns implements Dns {
@Override
public List lookup(String hostname) throws UnknownHostException {
if (hostname == null) {
throw new UnknownHostException("hostname == null");
} else {
try {
List mInetAddressesList = new ArrayList<>();
InetAddress[] mInetAddresses = InetAddress.getAllByName(hostname);
for (InetAddress address : mInetAddresses) {
if (address instanceof Inet4Address) {
mInetAddressesList.add(0, address);
} else {
mInetAddressesList.add(address);
}
}
return mInetAddressesList;
} catch (NullPointerException var4) {
UnknownHostException unknownHostException = new UnknownHostException("Broken system behaviour");
unknownHostException.initCause(var4);
throw unknownHostException;
}
}
}
}
第二步,将自定义方法插入到okhttp中
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
ClearableCookieJar cookieJar =
new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(AppUMS.mContent));
httpClientBuilder
.cookieJar(cookieJar)
.addInterceptor(interceptor)
.addInterceptor(new HeadUrlInterceptor())
//设置请求超时时长
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.dns(new ApiDns());//添加如下方法
四、Retrofit运行时动态改变BaseUrl解决方案
Retrofit运行时动态改变BaseUrl解决方案
1、出现此类问题场景
Android
正式项目中可能会涉及到多个BaseUrl
,使用Retrofit开发者可能会遇到多BaseUrl
不是很好处理情况
2、第一种解决方案
简单粗暴解决方案,利用Retrofit
请求优先级,因为Retrofit
支持全路径,比如
@GET("http://www.baidu.com")
Observable
3、第二种解决方案
Retrofit
默认只能设置一个BaseUrl
,没有提供其Api
去修改,所以我们只能通过其他方案去实现,网上也有很多介绍的,但尝试用了下感觉很不理想,于是自己稍加封装了下,思路其实简单。
思路:一个Retrofit
只能设置一个BaseUrl
,这样我们可以创建多个Retrofit
不就可以了吗?但如果一个请求创建一个Retrofit
必然是不理想的,所以我们可以有几个BaseUrl
创建几个,有人会说这样不会造成内存的开销吗?答案是不会的,一个项目中也不会出现N
多个BaseUrl
,所以这点开销不用过于纠结
代码实现:在代码设计时可以尽可能去优化,所以当我们用到此BaseUrl
时,再去创建,用不到不创建,这样便会出现个问题,怎样知道我应该使用哪个Retrofit
和Retrofit
怎么去保存等问题,本人思路是创建成功便添加到集合缓存下载,使用的时候去比对集合中BaseUrl
和当前是否匹配,如果一致从集合中获取,如果不一致去创建新的,如果使用没有传入BaseUrl
便用默认的,最基本的判断,实现代码如下
4、正常创建Retrofit
public class ApiRetrofit {
private static ApiRetrofit mApiRetrofit;
private Retrofit retrofit;
private ApiServer apiServer;
public static String mBaseUrl = BaseContent.baseUrl;
public ApiRetrofit() {
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.retryOnConnectionFailure(true);//错误重联
retrofit = new Retrofit.Builder()
.baseUrl(mBaseUrl )
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(httpClientBuilder.build())
.build();
apiServer = retrofit.create(ApiServer.class);
}
public static ApiRetrofit getInstance() {
if (mApiRetrofit == null) {
synchronized (Object.class) {
if (mApiRetrofit == null) {
mApiRetrofit = new ApiRetrofit();
}
}
}
return mApiRetrofit;
}
}
5、对创建Retrofit稍加封装,已适应我们的需求
新建保存对象的集合
private static List mRetrofitList = new ArrayList<>();
private static List mApiRetrofitList = new ArrayList<>();
修改创建时候的逻辑,如果请求接口时传入BaseUrl
,检测BaseUrl
是否为空,如果为空使用默认接口,如果不为空,再从缓存的Retrofit
中查找是否已经才创建过了,如果创建了用缓存的,如果没有创建则创建
注:这块可以用正则检查下传入的url
是否为正规的域名,再做下判断
//创建Retrofit代码中加入
apiServer = retrofit.create(ApiServer.class);
mRetrofitList.add(retrofit);
public static ApiRetrofit getInstance() {
mBaseUrl = BaseContent.baseUrl;
int mIndex = -1;
for (int i = 0; i < mRetrofitList.size(); i++) {
if (BaseContent.baseUrl.equals(mRetrofitList.get(i).baseUrl().toString())) {
mIndex = i;
break;
}
}
//新的baseUrl
if (mIndex == -1) {
synchronized (Object.class) {
mApiRetrofit = new ApiRetrofit();
mApiRetrofitList.add(mApiRetrofit);
return mApiRetrofit;
}
} else {
//以前已经创建过的baseUrl
return mApiRetrofitList.get(mIndex);
}
}
public static ApiRetrofit getInstance(String baseUrl) {
if (!TextUtils.isEmpty(baseUrl)) {
mBaseUrl = baseUrl;
} else {
mBaseUrl = BaseContent.baseUrl;
}
int mIndex = -1;
for (int i = 0; i < mRetrofitList.size(); i++) {
if (baseUrl.equals(mRetrofitList.get(i).baseUrl().toString())) {
mIndex = i;
break;
}
}
//新的baseUrl
if (mIndex == -1) {
synchronized (Object.class) {
mApiRetrofit = new ApiRetrofit();
mApiRetrofitList.add(mApiRetrofit);
return mApiRetrofit;
}
} else {
//以前已经创建过的baseUrl
return mApiRetrofitList.get(mIndex);
}
}
6、使用时写法
地址可以写成常量,不要我这样写,写成常量判断准确
ApiRetrofit.getInstance("http://www.baidu.com/").getApiService().getCeShi(params)
五、Retrofit文件上传(含进度条)
Retrofit文件上传
文件上传已封装到框架中,目的是让写法更简便
1、 FileObserver封装,文件上传下载时所用
上述文章有BaseObserver,现封装FileObserver单独用来文件上传下载时候所用,内容大同小异
public abstract class FileObserver extends DisposableObserver {
protected BaseView view;
/**
* 网络连接失败 无网
*/
public static final int NETWORK_ERROR = 100000;
/**
* 解析数据失败
*/
public static final int PARSE_ERROR = 1008;
/**
* 网络问题
*/
public static final int BAD_NETWORK = 1007;
/**
* 连接错误
*/
public static final int CONNECT_ERROR = 1006;
/**
* 连接超时
*/
public static final int CONNECT_TIMEOUT = 1005;
/**
* 其他所有情况
*/
public static final int NOT_TRUE_OVER = 1004;
public FileObserver(BaseView view) {
this.view = view;
}
@Override
protected void onStart() {
if (view != null) {
view.showProgress();
}
}
@Override
public void onNext(T t) {
onSuccess(t);
}
@Override
public void onError(Throwable e) {
if (view != null) {
view.hideProgress();
}
if (view != null) {
view.hideLoading();
}
if (e instanceof HttpException) {
// HTTP错误
onException(BAD_NETWORK, "");
} else if (e instanceof ConnectException
|| e instanceof UnknownHostException) {
// 连接错误
onException(CONNECT_ERROR, "");
} else if (e instanceof InterruptedIOException) {
// 连接超时
onException(CONNECT_TIMEOUT, "");
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) {
// 解析错误
onException(PARSE_ERROR, "");
e.printStackTrace();
} else {
if (e != null) {
onError(e.toString());
} else {
onError("未知错误");
}
}
}
private void onException(int unknownError, String message) {
switch (unknownError) {
case CONNECT_ERROR:
onError("连接错误");
break;
case CONNECT_TIMEOUT:
onError("连接超时");
break;
case BAD_NETWORK:
onError("网络超时");
break;
case PARSE_ERROR:
onError("数据解析失败");
break;
//非true的所有情况
case NOT_TRUE_OVER:
onError(message);
break;
default:
break;
}
}
@Override
public void onComplete() {
if (view != null) {
view.hideProgress();
}
}
public abstract void onSuccess(T o);
public abstract void onError(String msg);
}
2、定义接口
public interface ApiServer {
@Multipart
@POST("/wxapp/public/upload")
Observable> getUpload(@PartMap Map map,
@Part MultipartBody.Part parts
);
}
3、 定义MainView,并继承BaseView
public interface MainView extends BaseView {
void onUpLoadImgSuccess(BaseModel
4、 定义ProgressRequestBody,监听上传进度
public class ProgressRequestBody extends RequestBody {
private File mFile;
private String mPath;
private String mMediaType;
private BaseView mListener;
private int mEachBufferSize = 1024;
public ProgressRequestBody(final File file, String mediaType, BaseView baseView) {
mFile = file;
mMediaType = mediaType;
mListener = baseView;
}
public ProgressRequestBody(final File file, String mediaType, int eachBufferSize, BaseView baseView) {
mFile = file;
mMediaType = mediaType;
mEachBufferSize = eachBufferSize;
mListener = baseView;
}
@Override
public MediaType contentType() {
// i want to upload only images
return MediaType.parse(mMediaType);
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
long fileLength = mFile.length();
byte[] buffer = new byte[mEachBufferSize];
FileInputStream in = new FileInputStream(mFile);
long uploaded = 0;
try {
int read;
Handler handler = new Handler(Looper.getMainLooper());
while ((read = in.read(buffer)) != -1) {
// update progress on UI thread
handler.post(new ProgressUpdater(uploaded, fileLength));
uploaded += read;
sink.write(buffer, 0, read);
}
} finally {
in.close();
}
}
private class ProgressUpdater implements Runnable {
private long mUploaded;
private long mTotal;
public ProgressUpdater(long uploaded, long total) {
mUploaded = uploaded;
mTotal = total;
}
@Override
public void run() {
mListener.onProgress((int) (100 * mUploaded / mTotal));
}
}
}
5、 定义MainPresenter,并继承BasePresenter
public class MainPresenter extends BasePresenter {
public MainPresenter(MainView baseView) {
super(baseView);
}
/**
* 演示 文件上传进度监听
*
* @param url
*/
public void upLoadVideoApi(String url) {
HashMap params = new HashMap<>();
params.put("fileType", RetrofitUtil.convertToRequestBody("video"));
MultipartBody.Part parts = MultipartBody.Part.createFormData("file", new File(url).getName(), new ProgressRequestBody(new File(url),"video/mpeg", baseView));
ApiServer apiServer = ApiRetrofit.getBaseUrlInstance("https://bjlzbt.com/").getApiService();
addFileDisposable(apiServer.getUpload(params, parts), new FileObserver(baseView) {
@Override
public void onSuccess(Object o) {
baseView.onUpLoadImgSuccess((BaseModel
6、 在Activity中进行网络请求,如下
public class MainActivity extends BaseActivity implements MainView {
@Override
protected MainPresenter createPresenter() {
return new MainPresenter(this);
}
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
@Override
protected void initData() {
//文件上传
mPresenter.upLoadVideoApi(BaseContent.baseFileName+"ceshi.mp4");
}
@Override
public void onUpLoadImgSuccess(BaseModel
7、 有人会问,说好的进度条去哪了?
进度条已封装到BaseActivity中了,相关代码如下
@Override
public void showProgress() {
if (progressDialog == null) {
progressDialog = new ProgressDialog(this);
}
progressDialog.getProgressBar().performAnimation();
if (!progressDialog.isShowing()) {
progressDialog.show();
}
}
@Override
public void hideProgress() {
if (progressDialog != null) {
progressDialog.getProgressBar().releaseAnimation();
progressDialog.dismiss();
}
}
@Override
public void onProgress(int progress) {
if (progressDialog != null) {
progressDialog.updateProgress(progress);
}
}
六、Retrofit文件下载(含进度条)
1、 FileObserver封装,文件上传下载时所用
如上
2、定义接口
public interface ApiServer {
/**
* 大文件官方建议用 @Streaming 来进行注解,不然会出现IO异常,小文件可以忽略不注入
*/
@Streaming
@GET
Observable downloadFile(@Url String fileUrl);
}
3、 定义MainView,并继承BaseView
public interface MainView extends BaseView {
void onFileSuccess(File file);
}
4、 定义MainPresenter,并继承BasePresenter
public class MainPresenter extends BasePresenter {
public MainPresenter(MainView baseView) {
super(baseView);
public void downFile(String url, final String destFileDir, final String destFileName) {
ApiServer apiServer = ApiRetrofit.getFileInstance(baseView).getApiService();
Observable observable = apiServer.downloadFile(url).map(new Function() {
@Override
public String apply(ResponseBody body) throws Exception {
File file = FileUtil.saveFile(destFileDir+destFileName, body);
return file.getPath();
}
});
addFileDisposable(observable, new FileObserver(baseView) {
@Override
public void onSuccess(Object o) {
baseView.onFileSuccess(new File(o.toString()));
}
@Override
public void onError(String msg) {
if (baseView != null) {
baseView.showError(msg);
}
}
});
}
}
}
5、创建Retrofit来实现进度条
说明:本人思路是通过okhttp拦截器拦截来检测文件下载进度,相关代码已放入到创建Retrofit中,详情请看Demo,demo封装为只有文件下载okhttp才会添加ProgressInterceptor下载进度监听,如下所示(okhttp添加)
/**
* 文件处理
*
* @param httpClientBuilder
*/
public void initFileClient(OkHttpClient.Builder httpClientBuilder) {
/**
* 处理文件下载进度展示所需
*/
httpClientBuilder.addNetworkInterceptor(new ProgressInterceptor());
}
/**
* 文件下载进度拦截
*/
public class ProgressInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (mBaseView != null) {
Response response = chain.proceed(request);
return response.newBuilder().body(new ProgressResponseBody(response.body(),
new ProgressResponseBody.ProgressListener() {
@Override
public void onProgress(long totalSize, long downSize) {
int progress = (int) (downSize * 100 / totalSize);
if (mBaseView != null) {
mBaseView.onProgress(progress);
L.e("文件下载速度 === " + progress);
}
}
})).build();
} else {
return chain.proceed(request);
}
}
}
6、 在Activity中进行网络请求,如下
public class MainActivity extends BaseActivity implements MainView {
@Override
protected MainPresenter createPresenter() {
return new MainPresenter(this);
}
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
@Override
protected void initData() {
//文件上传
String url = "https://bjlzbt.com/upload/default//20190725//c13948258c6ef6a36cbe2d3322b98f5c.mp4";
if (FileUtils.createOrExistsDir(BaseContent.baseFileName)) {//删除此行代码也可以
mPresenter.downFile(url, BaseContent.baseFileName, "ceshi.mp4");
}
}
@Override
public void onUpLoadImgSuccess(BaseModel
七、Retrofit,Gson解析,请求返回的类型不统一,假如double返回的是null
对应文章解析
现实开发中,往往会遇到后台返回数据格式不规范情况,比如前端字段原本定义为int类型,而数据返回为空,如果用Gson解析会导致解析失败,比如字段定义为double类型,而返回的格式为字符串null,导致解析失败等等(只在后台返回数据格式不规范情况下出现,如果后台返回格式规范并不用考虑此问题)
1、 实现目标
1、格式化数据不规范【格式化int类型数据】
2、格式化数据不规范【格式化Long类型数据】
3、格式化数据不规范【格式化Double类型数据】
4、格式化数据不规范【格式化String类型数据】
5、格式化数据不规范【格式化Null类型数据】
2、 添加格式化工具方法到Gson解析中
if (gson == null) {
gson = new GsonBuilder()
.registerTypeAdapter(Integer.class, new IntegerDefaultAdapter())
.registerTypeAdapter(int.class, new IntegerDefaultAdapter())
.registerTypeAdapter(Double.class, new DoubleDefaultAdapter())
.registerTypeAdapter(double.class, new DoubleDefaultAdapter())
.registerTypeAdapter(Long.class, new LongDefaultAdapter())
.registerTypeAdapter(long.class, new LongDefaultAdapter())
.registerTypeAdapter(String.class, new StringNullAdapter())
.create();
}
return gson;
}
public ApiRetrofit() {
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.retryOnConnectionFailure(true);//错误重联
retrofit = new Retrofit.Builder()
.baseUrl(BASE_SERVER_URL)
.addConverterFactory(GsonConverterFactory.create(buildGson()))//添加json转换框架buildGson()根据需求添加
//支持RxJava2
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(httpClientBuilder.build())
.build();
apiServer = retrofit.create(ApiServer.class);
}
3、 对double类型处理,返回“”,或“null”,动态更改为默认值0.00,新建DoubleDefaultAdapter类
public class DoubleDefault0Adapter implements JsonSerializer, JsonDeserializer {
@Override
public Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定义为double类型,如果后台返回""或者null,则返回0.00
return 0.00;
}
} catch (Exception ignore) {
}
try {
return json.getAsDouble();
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
@Override
public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src);
}
}
4、 对int类型处理,返回“”,或“null”,动态更改为默认值0,新建DoubleDefaultAdapter类
public class IntegerDefaultAdapter implements JsonSerializer, JsonDeserializer {
@Override
public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
try {
if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定义为int类型,如果后台返回""或者null,则返回0
return 0;
}
} catch (Exception ignore) {
}
try {
return json.getAsInt();
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
@Override
public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src);
}
}
5、 对Long类型处理,返回“”,或“null”,动态更改为默认值0,新建DoubleDefaultAdapter类
public class LongDefault0Adapter implements JsonSerializer, JsonDeserializer {
@Override
public Long deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
try {
if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定义为long类型,如果后台返回""或者null,则返回0
return 0l;
}
} catch (Exception ignore) {
}
try {
return json.getAsLong();
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
@Override
public JsonElement serialize(Long src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src);
}
}
5、 重点说一下String类型
根据上边其他类型处理代码可以看出,String也就是把上述类中代码改成String就可以了,答案是可以的,如下,处理的内容为如果服务器返回字符串类型“null”,我们将其格式化成“”,空类型,但是我们为什么不直接写,请往下看
public class StringDefaultConverter implements JsonSerializer, JsonDeserializer {
@Override
public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
if (json.getAsString().equals("null")) {
return "";
}
} catch (Exception ignore) {
}
try {
return json.getAsJsonPrimitive().getAsString();
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
@Override
public JsonElement serialize(String src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src);
}
}
但是有种比较常见的不规范数据返回,为null,不是字符串的"null",是这个null,如果返回null,会进入到上边这个类吗,经过测试,返回null的直接跳过,所以出现了个问题,null到底是什么类型?
通过读源码可知,我们可以自定义TypeAdapter,将其放入facotries中,并且gson在解析json时使用对应的TypeAdapter来的,而我们手动添加的TypeAdapter会优先于预设的TypeAdapter被使用。
于是乎找到了一种其他方法来解决这个问题
新建个类来集成TypeAdapter,这样就便优先于预设的TypeAdapter
public class StringNullAdapter extends TypeAdapter {
@Override
public String read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return "";//原先是返回Null,这里改为返回空字符串
}
String jsonStr = reader.nextString();
if(jsonStr.equals("null")) {
return "";
}else {
return jsonStr;
}
}
@Override
public void write(JsonWriter writer, String value) throws IOException {
if (value == null) {
writer.nullValue();
return;
}
writer.value(value);
}
}
定义的类型为String,这样为null的情况会都归这个类来处理,但是String的所有情况也会走里边的方法,所以为了同样的类型不执行俩遍,String和null都在此类处理,就没必要写上边那个方法了, 处理所有情况为返回null,或字符串"null",格式化为"" 空
八、Retrofit实现cookie自动化管理
对应文章解析
在现实开发中,我们可能会遇到这样的需求,需要保持长登陆状态,登陆失效为服务器判断,在我们不想往接口添加任何参数处理时,我们便想到cookie
最终实现效果为:登录成功后将将服务器返回的cookie保存到本地(每次接口请求成功,更新本地保存Cookie值,目的让本地的cookie值一直为最新的),下次请求接口时将本地最新cookie带上,用来告诉哪个用户与服务器之间的交互
1、 第一种实现方方法(第三方库实现Cookie自动化管理)
(1)依赖第三方库
implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
(2)创建OkHttpClient时添加cookieJar
PersistentCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context));
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.addInterceptor(new LoginInterceptor())
.cookieJar(cookieJar)// 设置封装好的cookieJar
.build();
2、 第二种实现方方法(手写cookie管理类),自我操控性强
(1)创建CookieManger类
public class CookieManger implements CookieJar {
private static Context mContext;
private static PersistentCookieStore cookieStore;
public CookieManger(Context context) {
mContext = context;
if (cookieStore == null) {
cookieStore = new PersistentCookieStore(mContext);
}
}
@Override
public void saveFromResponse(HttpUrl url, List cookies) {
if (cookies != null && cookies.size() > 0) {
for (Cookie item : cookies) {
cookieStore.add(url, item);
if (item.name() != null && !TextUtils.isEmpty(item.name()) &&
item.value() != null && !TextUtils.isEmpty(item.value())) {
/*保存cookie到sp地方 可能会用到 */
// PrefUtils.setString(mContext, "cookie_name", item.name());
// PrefUtils.setString(mContext, "cookie_value", item.value());
}
}
}
}
@Override
public List loadForRequest(HttpUrl url) {
List cookies = cookieStore.get(url);
for (int i = 0; i < cookies.size(); i++) {
Log.e("", "拿出来的cookies name()==" + cookies.get(i).name());
Log.e("", "拿出来的cookies value()==" + cookies.get(i).value());
}
return cookies;
}
}
(2)创建OkHttpCookies类
public class OkHttpCookies implements Serializable {
private transient final Cookie cookies;
private transient Cookie clientCookies;
public OkHttpCookies(Cookie cookies) {
this.cookies = cookies;
}
public Cookie getCookies() {
Cookie bestCookies = cookies;
if (clientCookies != null) {
bestCookies = clientCookies;
}
return bestCookies;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(cookies.name());
out.writeObject(cookies.value());
out.writeLong(cookies.expiresAt());
out.writeObject(cookies.domain());
out.writeObject(cookies.path());
out.writeBoolean(cookies.secure());
out.writeBoolean(cookies.httpOnly());
out.writeBoolean(cookies.hostOnly());
out.writeBoolean(cookies.persistent());
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
String name = (String) in.readObject();
String value = (String) in.readObject();
long expiresAt = in.readLong();
String domain = (String) in.readObject();
String path = (String) in.readObject();
boolean secure = in.readBoolean();
boolean httpOnly = in.readBoolean();
boolean hostOnly = in.readBoolean();
boolean persistent = in.readBoolean();
Cookie.Builder builder = new Cookie.Builder();
builder = builder.name(name);
builder = builder.value(value);
builder = builder.expiresAt(expiresAt);
builder = hostOnly ? builder.hostOnlyDomain(domain) : builder.domain(domain);
builder = builder.path(path);
builder = secure ? builder.secure() : builder;
builder = httpOnly ? builder.httpOnly() : builder;
clientCookies =builder.build();
}
}
(3)创建PersistentCookieStore类
public class PersistentCookieStore {
private static final String LOG_TAG = "PersistentCookieStore";
private static final String COOKIE_PREFS = "Cookies_Prefs";
private final Map> cookies;
private final SharedPreferences cookiePrefs;
public PersistentCookieStore(Context context) {
cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
cookies = new HashMap<>();
//将持久化的cookies缓存到内存中 即map cookies
Map prefsMap = cookiePrefs.getAll();
for (Map.Entry entry : prefsMap.entrySet()) {
String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");
for (String name : cookieNames) {
String encodedCookie = cookiePrefs.getString(name, null);
if (encodedCookie != null) {
Cookie decodedCookie = decodeCookie(encodedCookie);
if (decodedCookie != null) {
if (!cookies.containsKey(entry.getKey())) {
cookies.put(entry.getKey(), new ConcurrentHashMap());
}
cookies.get(entry.getKey()).put(name, decodedCookie);
}
}
}
}
}
protected String getCookieToken(Cookie cookie) {
return cookie.name() + "@" + cookie.domain();
}
public void add(HttpUrl url, Cookie cookie) {
String name = getCookieToken(cookie);
//将cookies缓存到内存中 如果缓存过期 就重置此cookie
if (!cookie.persistent()) {
if (!cookies.containsKey(url.host())) {
cookies.put(url.host(), new ConcurrentHashMap());
}
cookies.get(url.host()).put(name, cookie);
} else {
if (cookies.containsKey(url.host())) {
cookies.get(url.host()).remove(name);
}
}
//讲cookies持久化到本地
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
prefsWriter.putString(name, encodeCookie(new OkHttpCookies(cookie)));
prefsWriter.apply();
}
public List get(HttpUrl url) {
ArrayList ret = new ArrayList<>();
if (cookies.containsKey(url.host())) {
ret.addAll(cookies.get(url.host()).values());
}
return ret;
}
public boolean removeAll() {
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
prefsWriter.clear();
prefsWriter.apply();
cookies.clear();
return true;
}
public boolean remove(HttpUrl url, Cookie cookie) {
String name = getCookieToken(cookie);
if (cookies.containsKey(url.host()) && cookies.get(url.host()).containsKey(name)) {
cookies.get(url.host()).remove(name);
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
if (cookiePrefs.contains(name)) {
prefsWriter.remove(name);
}
prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
prefsWriter.apply();
return true;
} else {
return false;
}
}
public List getCookies() {
ArrayList ret = new ArrayList<>();
for (String key : cookies.keySet()) {
ret.addAll(cookies.get(key).values());
}
return ret;
}
/**
* cookies 序列化成 string
*
* @param cookie 要序列化的cookie
* @return 序列化之后的string
*/
protected String encodeCookie(OkHttpCookies cookie) {
if (cookie == null) {
return null;
}
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ObjectOutputStream outputStream = new ObjectOutputStream(os);
outputStream.writeObject(cookie);
} catch (IOException e) {
Log.d(LOG_TAG, "IOException in encodeCookie", e);
return null;
}
return byteArrayToHexString(os.toByteArray());
}
/**
* 将字符串反序列化成cookies
*
* @param cookieString cookies string
* @return cookie object
*/
protected Cookie decodeCookie(String cookieString) {
byte[] bytes = hexStringToByteArray(cookieString);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
Cookie cookie = null;
try {
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
cookie = ((OkHttpCookies) objectInputStream.readObject()).getCookies();
} catch (IOException e) {
Log.d(LOG_TAG, "IOException in decodeCookie", e);
} catch (ClassNotFoundException e) {
Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);
}
return cookie;
}
/**
* 二进制数组转十六进制字符串
*
* @param bytes byte array to be converted
* @return string containing hex values
*/
protected String byteArrayToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte element : bytes) {
int v = element & 0xff;
if (v < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(v));
}
return sb.toString().toUpperCase(Locale.US);
}
/**
* 十六进制字符串转二进制数组
*
* @param hexString string of hex-encoded values
* @return decoded byte array
*/
protected byte[] hexStringToByteArray(String hexString) {
int len = hexString.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
}
return data;
}
}
(4)创建OkHttpClient时添加cookieJar
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.addInterceptor(new LoginInterceptor())
.cookieJar(new CookieManger (context))// 设置封装好的cookieJar
.build();
九、路由判断第二种解决方案(文章为旧版,只提供思路)
参考连接如下
上述文章提到了路由这个概念,其实自己命名的,实际意义和这个此比较契合,本文章目的只服务器下发数据,当我们得到的值代表数据正常,(比如code=0数据正常),我们会正常执行我们的解析数据并处理显示数据内容等操作,如果服务器下发的数据为异常信息,前端只需要个提示操作,这样我们就没必要执行解析+显示等操作,所以我们会想到怎样可以一次性判断,终身不用管走向呢?
1、 第一种判断方法,在Rxjava的OnNext中判断
@Override
public void onNext(BaseModel o) {
T t = o.getData();
try {
/* if (t!=null){
L.e("返回数据="+o.toString());
}else {
L.e("返回数据=null");
}*/
if (view != null) {
view.hideLoading();
}
if (o.getErrcode() == mSuccessCode) {
onSuccess(t, o.getMsg(), o.getErrcode());
} else {
view.onErrorCode(o);
}
} catch (Exception e) {
e.printStackTrace();
onError(e.toString());
}
}
当我们执行到OnNext方法中,此时已经执行了Gson解析代码,所以我们是否可以将判断提前到Gson解析时候判断呢? 请看第二种方法
2、 第二种判断方法,Gson解析期间判断
如果想通过Gson解析期间判断,这样必然会设计到Gson源码如果走向,我们通过更改源码来自定义操作,通过阅读源码我们会发现解析数据会涉及到三个类,GsonConverterFactory
,GsonRequestBodyConverter
,GsonResponseBodyConverter
这三个类,我们需要重写这个三个类,阅读代码会返现主要执行解析代码在GsonResponseBodyConverter
中,所以我们的目标便是这里。
思路:Gosn解析数据时,如果出现服务器下发非正常标识,此刻我们已判断服务器返回数据不是我们需要展示的,那我们解析到这一步已不用再向下解析,可以通过抛异常来释放当前任务代码如下
@Override
public T convert(ResponseBody value) throws IOException {
String response = value.string();
BaseResult re = gson.fromJson(response, BaseResult.class);
//关注的重点,自定义响应码中非0的情况,一律抛出ApiException异常。
//这样,我们就成功的将该异常交给onError()去处理了。
if (re.getCode() != BaseContent.basecode) {
value.close();
throw new ApiException(re.getCode(), re.getMessage());
}
MediaType mediaType = value.contentType();
Charset charset = mediaType != null ? mediaType.charset(UTF_8) : UTF_8;
ByteArrayInputStream bis = new ByteArrayInputStream(response.getBytes());
InputStreamReader reader = new InputStreamReader(bis, charset);
JsonReader jsonReader = gson.newJsonReader(reader);
try {
return adapter.read(jsonReader);
} finally {
value.close();
}
}
异常已成功抛出,那异常信息到哪里了呢?答案是到Rxjava的OnError中,异常我们抛的是自定义实体类ApiException
,内含code,message,那我们到Rxjava中OnError获取到异常信息 e,e instanceof ApiException
通过分析异常是否为我们自定义实体类来判断下一步如何操作,此方法为路由的第二种判断,示例如下
@Override
public void onError(Throwable e) {
if (view != null) {
view.hideLoading();
}
if (e instanceof HttpException) {
// HTTP错误
onException(BAD_NETWORK, "");
} else if (e instanceof ConnectException
|| e instanceof UnknownHostException) {
// 连接错误
onException(CONNECT_ERROR, "");
} else if (e instanceof InterruptedIOException) {
// 连接超时
onException(CONNECT_TIMEOUT, "");
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) {
// 解析错误
onException(PARSE_ERROR, "");
e.printStackTrace();
//这里
} else if (e instanceof ApiException) {
ApiException exception = (ApiException) e;
int code = exception.getErrorCode();
switch (code) {
//未登录(此处只是案例 供理解)
case CONNECT_NOT_LOGIN:
view.onErrorCode(new BaseModel(exception.getMessage(), code));
onException(CONNECT_NOT_LOGIN, "");
break;
//其他不等于0 的所有状态
default:
onException(OTHER_MESSAGE, exception.getMessage());
view.onErrorCode(new BaseModel(exception.getMessage(), code));
break;
}
} else {
if (e != null) {
onError(e.toString());
} else {
onError("未知错误");
}
}
}
十、Retrofit配置及各情况处理(缓存拦截、日志打印、替换接口内容、参数添加等
相关参考跳转此链接
十一、后记
一、问:这样封装每个Activity对应一个Presenter,有些接口会多次用不想多次写
答:onCreate随便写
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainPresenter1 presenter1 = new MainPresenter1(this);
presenter.getTextApi();
MainPresenter2 presenter2 = new MainPresenter2(this);
presenter2.getTextApi();
MainPresenter3 presenter3 = new MainPresenter3(this);
presenter3getTextApi();
}
二、问:有人问dialog加载圈封装的不够好,这样每个接口都得显示加载圈,不想实现都不行
答:BaseActivity和BaseFragment中都有这俩个方法
//显示加载进度框回调
@Override
public void showLoading() {
showLoadingDialog();
}
//隐藏进度框回调
@Override
public void hideLoading() {
closeLoadingDialog();
}
如果说我本页面都不想显示Loading动画,那就在对应的Activity重写下父类的方法,比如
@Override
public void showLoading() {
// super.showLoading(); //将super去掉 就不会显示Loading动画了
}
如果我们需要显示就在对应的Fragment调用请求方法之后手动掉一下父类的显示Loading方法,如下:
mPresenter.collectApi("id");
showLoadingDialog();
三、问:假如接口返回1001,代表重写登录或者token失效,我想在对应activity拿到状态或者做统一操作
答:可以在BaseActivity判断跳页面
//BaseActivity代码
@Override
public void onErrorCode(BaseModel model) {
if (model.getErrcode() == 1001) {
startLogin();
}
}
private void startLogin() {
startActivity(LoginActivity.class);
}
如果想在对应Activity操作,那就在对应Activity重写此方法
//对应Activity代码
@Override
public void onErrorCode(BaseModel model) {
//super.onErrorCode(model);
if (model.getErrcode()==1001){
//............................................
}else if (model.getErrcode()==1002){
//............................................
}
}
github地址:https://github.com/LPTim/MVP-Retrofit2-okhttp3-Rxjava2