抓住人生中的一分一秒,胜过虚度中的一月一年!

小做个动图开篇引题

鸿蒙背景

2020年9月10号,鸿蒙2.0(HarmonyOS 2.0)系统正式发布,鸿蒙2.0面向应用开发者发布Beta版本,在2020年9月10发布大屏,手表,车机版鸿蒙,2020年12月发布手机版鸿蒙。在2020年9月10日,鸿蒙开源路标面向内存128KB-128MB终端设备;2021年10月,将面向4GB以上所有设备。

前言

作为一个安卓开发者,能够看到属于国产的操作系统确实很兴奋,也许将来的某一天可能和android一战,但实际踩坑中发现,鸿蒙需要走的路很长,系统优化方面还有很多,和android差距还是特别巨大的,入坑鸿蒙开发,可参考的东西少之有少,几乎为0,所以需要大家一起行动起来,互相分享,生态圈才能形成,给大家分享点自己的踩坑之路和成果。

一个APP的必须品肯定是网络访问,所以第一篇文章先搭建个网络框架供大家参考,可以更快的入手鸿蒙开发,鸿蒙支持java开发,所以选择了Retrofit+okhttp组合,下面给大家演示下如何封装使用,RxAndroid不可用,需要改装成RxHarmony,有人肯定想为何不封装携程+mvvm,这个需要问官方是否支持



相关业务需求及解决方案
一、 MVP+Retrofit2+okhttp3+Rxjava2+RxHarmony框架基本搭建及使用
二、 BaseAbilitySliceBaseFraction封装,搭配框架使用
三、 Retrofit运行时动态改变BaseUrl解决方案,及动态改变retrofit.create(cls)的接口cls,来实现组件化思想如androidArouter,和鸿蒙服务的理念可分可合可流转多entry包思想
四、 RetrofitGson解析,请求返回的类型不统一,假如double返回的是null
五、 Retrofit实现cookie自动化管理
六、 接口成功失败路由判断,处理格式异常情况,如code=1成功,data={},code=100,data=null
七、 Retrofit配置及各情况处理(缓存拦截、日志打印、替换接口内容、参数添加等)
八、 Retrofit文件上传(封装中有,暂未实践)
九、 Retrofit文件下载(封装中有,暂未实践)
十、 后记
十一、 本文譩在一篇文章搞定所有,上述描述文章都有讲解

一、MVP+Retrofit2+Okhttp3+Rxjava2+RxHarmony框架基本搭建

1、我们需要依赖相关第三方库

networkDeps = ["okhttp"             : 'com.squareup.okhttp3:okhttp:4.2.2',"retrofit"           : 'com.squareup.retrofit2:retrofit:2.6.2',"converter-gson"     : 'com.squareup.retrofit2:converter-gson:2.6.2',"adapter-rxjava2"    : 'com.squareup.retrofit2:adapter-rxjava2:2.6.2',"logging-interceptor": 'com.squareup.okhttp3:logging-interceptor:3.12.0']networkLibs = networkDeps.values()

######2、创建接口类ApiServer,定义接口方法

public interface ApiServer {@FormUrlEncoded@POST("/api/table_list/")Observable<BaseModel<Object>> getCeShi(@FieldMap HashMap<String, String> params);
}

3、 上述1,2整理完毕,开始创建okhttp和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;}
}

Retrofit和Okhttp搭配使用如上述内容所述,下边开始配合Rxjava使用
######4、 先封装个基本实体类BaseModle,下面会用到(准备工作)
封装理由:一个项目一般情况下json返回格式外层都是统一的

public class BaseModel<T> implements Serializable {private String msg;private int code;private T data;public BaseModel(int code, String msg) {this.code = code;this.msg = msg;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}public T getData() {return data;}public void setData(T data) {this.data = data;}
}

5、 定义几个会用到的接口,来区分如网络开始,结束,进度条加载,错误码等(准备工作)

public interface BaseView {/**--------------------------------------------** 接口开始情况  这时我们可以显示 菊花圈 或显示下载进度条*-------------------------------------------*/void showLoading(Boolean isShowProgress);/**--------------------------------------------** 接口请求完毕  这时我们可以  将菊花圈隐藏掉 或下载进度条隐藏掉*-------------------------------------------*/void hideLoading();/**--------------------------------------------** 返回 非定义的code状态码,和msg  mType 区分异常时请求的接口是哪个*-------------------------------------------*/void onErrorState(BaseModel model, int mType);/**--------------------------------------------** 如果是下载文件时,或上传文件, 此回调是  文件下载进度监听回调*-------------------------------------------*/void onProgress(int progress);
}

6、 BaseObserver封装,开始结合Rxjava使用,以下为订阅后回调代表含义,封装原因如下
1、onStart为网络请求开始,我们可以将刚才创建的接口实现一下BaseViewshowLoading(),用来代表网络开始的菊花框显示
2.onNext为网络返回的内容,这时我们就可以将显示的菊花框关闭掉,BaseViewhideLoading()
3、onError为网络请求失败的返回状态,可以通过异常来区分网络失败原因,分析好的异常情况然后以接口形式回调出去,所以实现BaseView中的onErrorState方法,onErrorState(BaseModel model, int mType);有人会问type作用是什么,其实是用来区分请求的是哪个接口,因为所有失败异常我们统一回调一个方法,这样区分不出是哪个接口失败的,所以传入一个type值,然后再回传出去,可知哪个接口失败
4、onComplete代表请求完毕,这里不做任何操作,关闭菊花圈已经在onNext中回掉了,当然,你也可以在这里回调,但是存在一定体验问题,可以自行测试下
######说明:如下封装包含其他逻辑判断,在下边文章专题中进行讲解,无关方法可以忽略

public abstract class BaseObserver<T> extends DisposableObserver<BaseModel<T>> {protected BaseView mView;public static final int PARSE_ERROR = 10008;public static final int BAD_NETWORK = 10007;public static final int CONNECT_ERROR = 10006;public static final int CONNECT_TIMEOUT = 10005;public static final int CONNECT_N = 10004;//回传标识private int mType = 0;//true 展示进度条private Boolean isShowProgress = false;public BaseObserver(BaseView view) {this.mView = view;}public BaseObserver(BaseView view, int mType) {this.mView = view;this.mType = mType;}public BaseObserver(BaseView view, Boolean isShowProgress) {this.mView = view;this.isShowProgress = isShowProgress;}@Overrideprotected void onStart() {if (mView != null) mView.showLoading(isShowProgress);}@Overridepublic void onNext(BaseModel<T> o) {try {if (mView != null) mView.hideLoading();if (BaseContent.getIsTrueCode(o.getCode())) {onSuccessResult(o);} else {onErrorResult(o);}} catch (Exception e) {e.printStackTrace();}}@Overridepublic void onError(Throwable e) {if (mView != null) mView.hideLoading();if (e instanceof HttpException) {onErrorResult(new BaseModel<>(BAD_NETWORK, "网络超时"));} else if (e instanceof ConnectException ||e instanceof UnknownHostException) {onErrorResult(new BaseModel<>(CONNECT_ERROR, "连接错误"));} else if (e instanceof InterruptedIOException) {        //  连接超时onErrorResult(new BaseModel<>(CONNECT_TIMEOUT, "连接超时"));} else if (e instanceof JsonParseException|| e instanceof ParseException) {onErrorResult(new BaseModel<>(PARSE_ERROR, "数据解析失败"));} else if (e instanceof ApiException) {/**************************************************************** 重点说一下此种情况:此类是接口返回内容不规范,开发中肯定会存在这样类似问题,虽不是前端问题,但前端也可以很好处理此类问题* 假如正常情况 返回data为集合* code:1* msg:获取成功* data[ 。。。]** 当异常情况下,返回data:{}或者data:""* code:0* msg:获取失败* data:{}或者data:""** 这样我们定义好的类型Gson解析会失败,由于类型不统一,并报异常,发生此类情况,在不改动后台代码情况下,* 一般通常我们会定义成object类型再手动解析,但这样很是麻烦,所以,可参考此种实现方式** 实现原理:拦截gson解析,解析前一步,先解析一遍code,如果是定义正常的,继续向下解析,如果非正常情况,抛异常处理,* 并且将接口返回的code,msg一并抛出,异常会在这里拦截!!!!**************************************************************/ApiException apiException = (ApiException) e;onErrorResult(new BaseModel<>(apiException.getErrorCode(), apiException.getMessage()));} else {if (e != null) {onErrorResult(new BaseModel<>(CONNECT_N, e.toString()));} else {onErrorResult(new BaseModel<>(CONNECT_N, "未知错误"));}}}private void onSuccessResult(BaseModel<T> o) {onSuccess(o);}private void onErrorResult(BaseModel<T> o) {if (mView != null) mView.onErrorState(o, mType);}@Overridepublic void onComplete() {}public abstract void onSuccess(BaseModel<T> o);
}

Rxjava逻辑如上,下边开始讲解如何将RetrofitokhttpRxjavaRxHarmony连贯起来使用

7、 BasePresenter封装

当我们使用Rxjavasubscribe订阅后,网络会立即触发,但是在请求中UIdestroy了怎么办,不及时取消订阅,可能会造成内存泄漏,这时候CompositeDisposable开始上场了,它可以对我们订阅的请求进行统一管理。
大致三步走:
1、在UI层创建的时候(比如onCreate之类的),实例化CompositeDisposable
2、把subscribe订阅返回的Disposable对象加入管理器;
3、UI销毁时清空订阅的对象。
我们将其封装到P层

public class BasePresenter<V extends BaseView> {private CompositeDisposable compositeDisposable;public V baseView;public BasePresenter(V baseView) {this.baseView = baseView;}/*** 解除绑定*/public void detachView() {baseView = null;removeDisposable();}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(HmOSSchedulers.mainThread()).subscribeWith(observer));}public void addDisposable(Observable<?> observable, DisposableObserver observer) {if (compositeDisposable == null) {compositeDisposable = new CompositeDisposable();}compositeDisposable.add(observable.subscribeOn(Schedulers.io()).observeOn(HmOSSchedulers.mainThread()).subscribeWith(observer));}public void addFileDisposable(Observable<?> observable, FileObserver observer) {if (compositeDisposable == null) {compositeDisposable = new CompositeDisposable();}compositeDisposable.add(observable.subscribeOn(Schedulers.io()).observeOn(HmOSSchedulers.mainThread()).subscribeWith(observer));}public void removeDisposable() {if (compositeDisposable != null) {compositeDisposable.dispose();}}
}

8、 这时候Rxjava是在子线程中执行,需要将返回结果回调到主线程,rxandroid负责此任务,然而鸿蒙无法使用rxandroid,因为android通知类方法handler,鸿蒙的是EventHandler,所以方法不一样不可以使用,需要根据rxandroid原理,重写改装成RxHarmony,如下改装三个类,不由官方维护,目前正常使用

HmOSSchedulers类

public final class HmOSSchedulers {private static final class MainHolder {static final Scheduler DEFAULT = new HandlerScheduler(new EventHandler(EventRunner.getMainEventRunner() ));}private static final Scheduler MAIN_THREAD = RxHmOSPlugins.initMainThreadScheduler(new Callable<Scheduler>() {@Override public Scheduler call() throws Exception {return MainHolder.DEFAULT;}});public static Scheduler mainThread() {return RxHmOSPlugins.onMainThreadScheduler(MAIN_THREAD);}public static Scheduler from(EventRunner eventRunner) {if (eventRunner == null) throw new NullPointerException("eventRunner == null");return new HandlerScheduler(new EventHandler(eventRunner));}private HmOSSchedulers() {throw new AssertionError("No instances.");}
}

HandlerScheduler类

final class HandlerScheduler extends Scheduler {private final EventHandler handler;HandlerScheduler(EventHandler handler) {this.handler = handler;}@Overridepublic Disposable scheduleDirect(Runnable run, long delay, TimeUnit unit) {if (run == null) throw new NullPointerException("run == null");if (unit == null) throw new NullPointerException("unit == null");run = RxJavaPlugins.onSchedule(run);ScheduledRunnable scheduled = new ScheduledRunnable(handler, run);handler.postTask(scheduled, unit.toMillis(delay));return scheduled;}@Overridepublic Worker createWorker() {return new HandlerWorker(handler);}private static final class HandlerWorker extends Worker {private final EventHandler handler;private volatile boolean disposed;HandlerWorker(EventHandler handler) {this.handler = handler;}@Overridepublic Disposable schedule(Runnable run, long delay, TimeUnit unit) {if (run == null) throw new NullPointerException("run == null");if (unit == null) throw new NullPointerException("unit == null");if (disposed) {return Disposables.disposed();}run = RxJavaPlugins.onSchedule(run);ScheduledRunnable scheduled = new ScheduledRunnable(handler, run);handler.postTask(scheduled, unit.toMillis(delay));if (disposed) {handler.removeAllEvent();return Disposables.disposed();}return scheduled;}@Overridepublic void dispose() {disposed = true;handler.removeAllEvent();}@Overridepublic boolean isDisposed() {return disposed;}}private static final class ScheduledRunnable implements Runnable, Disposable {private final EventHandler handler;private final Runnable delegate;private volatile boolean disposed;ScheduledRunnable(EventHandler handler, Runnable delegate) {this.handler = handler;this.delegate = delegate;}@Overridepublic void run() {try {delegate.run();} catch (Throwable t) {RxJavaPlugins.onError(t);}}@Overridepublic void dispose() {disposed = true;handler.removeAllEvent();}@Overridepublic boolean isDisposed() {return disposed;}}
}

RxHmOSPlugins类

public final class RxHmOSPlugins {private static volatile Function<Callable<Scheduler>, Scheduler> onInitMainThreadHandler;private static volatile Function<Scheduler, Scheduler> onMainThreadHandler;public static void setInitMainThreadSchedulerHandler(Function<Callable<Scheduler>, Scheduler> handler) {onInitMainThreadHandler = handler;}public static Scheduler initMainThreadScheduler(Callable<Scheduler> scheduler) {if (scheduler == null) {throw new NullPointerException("scheduler == null");}Function<Callable<Scheduler>, Scheduler> f = onInitMainThreadHandler;if (f == null) {return callRequireNonNull(scheduler);}return applyRequireNonNull(f, scheduler);}public static void setMainThreadSchedulerHandler(Function<Scheduler, Scheduler> handler) {onMainThreadHandler = handler;}public static Scheduler onMainThreadScheduler(Scheduler scheduler) {if (scheduler == null) {throw new NullPointerException("scheduler == null");}Function<Scheduler, Scheduler> f = onMainThreadHandler;if (f == null) {return scheduler;}return apply(f, scheduler);}public static Function<Callable<Scheduler>, Scheduler> getInitMainThreadSchedulerHandler() {return onInitMainThreadHandler;}public static Function<Scheduler, Scheduler> getOnMainThreadSchedulerHandler() {return onMainThreadHandler;}public static void reset() {setInitMainThreadSchedulerHandler(null);setMainThreadSchedulerHandler(null);}static Scheduler callRequireNonNull(Callable<Scheduler> s) {try {Scheduler scheduler = s.call();if (scheduler == null) {throw new NullPointerException("Scheduler Callable returned null");}return scheduler;} catch (Throwable ex) {throw Exceptions.propagate(ex);}}static Scheduler applyRequireNonNull(Function<Callable<Scheduler>, Scheduler> f, Callable<Scheduler> s) {Scheduler scheduler = apply(f,s);if (scheduler == null) {throw new NullPointerException("Scheduler Callable returned null");}return scheduler;}static <T, R> R apply(Function<T, R> f, T t) {try {return f.apply(t);} catch (Throwable ex) {throw Exceptions.propagate(ex);}}private RxHmOSPlugins() {throw new AssertionError("No instances.");}
}

相关逻辑已封装完毕,下面看下如何使用

9、 接口请求三步骤,第一步骤写个接口,用来回调数据,如定义MainView,并继承BaseView

public interface MainView extends BaseView {void onTextSuccess(BaseModel<TextBean> o);
}

10、 接口请求三步骤,第二步骤p层,继承BasePresenter,串联okhttpRetrofitRxjava

public class MainPresenter extends BasePresenter<MainView> {public MainPresenter(MainView baseView) {super(baseView);}public void getTextApi() {HashMap<String, String> params = new HashMap<>();params.put("type", "junshi");params.put("key", "2c1cb93f8c7430a754bc3ad62e0fac06");addDisposable(apiServer.getText(params), new BaseObserver(baseView) {@Overridepublic void onSuccess(BaseModel o) {baseView.onTextSuccess((BaseModel<TextBean>) o);}@Overridepublic void onError(String msg) {if (baseView != null) {baseView.showError(msg);}}});}
}

11、 在AbilitySlice中进行网络请求案例如下,当然,现在页面回调这么多东西,很不美观,就会想到j将无关方法放到基类,会引发Base->AbilitySliceFraction写法,请看第二部分内容,BaseAbilitySliceBaseFraction封装

public class TextSlice extends BaseAbilitySlice<MainPresenter>{@Overridepublic int getUIContent() {return ResourceTable.Layout_slice_collection;}@Overridepublic void initComponent() {MainPresenter presenter = new MainPresenter(this);//网络请求presenter.getTextApi();}@Overridepublic void showLoading(Boolean isShowProgress) {/**--------------------------------------------** 接口开始情况  这时我们可以显示 菊花圈 或显示下载进度条*-------------------------------------------*/}@Overridepublic void hideLoading() {/**--------------------------------------------** 接口请求完毕  这时我们可以  将菊花圈隐藏掉 或下载进度条隐藏掉*-------------------------------------------*/}@Overridepublic void onProgress(int progress) {/**--------------------------------------------** 如果是下载文件时,或上传文件, 此回调是  文件下载进度监听回调*-------------------------------------------*/}@Overridepublic void onErrorState(BaseModel model, int mType) {/**--------------------------------------------** 返回 非定义的code状态码,和msg  mType 区分异常时请求的接口是哪个*-------------------------------------------*/}
}



二、BaseAbilitySlice,BaseFraction封装,搭配框架使用

1、 BaseAbilitySlice封装

public abstract class BaseAbilitySlice<P extends BasePresenter> extends AbilitySlice implements BaseView {protected P mPresenter;public abstract int getUIContent();public abstract void initComponent();protected abstract P createPresenter();public Context mContext;public Intent intent;@Overrideprotected void onStart(Intent intent) {super.onStart(intent);this.intent = intent;mContext = this;mPresenter = createPresenter();beforsetUIContent();super.setUIContent(getUIContent());this.initComponent();}public String getString(int resId) {try {return getResourceManager().getElement(resId).getString();} catch (Exception e) {e.printStackTrace();}return "";}public int getColor(int colorId) {try {return getResourceManager().getElement(colorId).getColor();} catch (Exception e) {e.printStackTrace();}return 0;}public FractionManager getFractionManager() {Ability ability = getAbility();if (ability instanceof FractionAbility) {FractionAbility fractionAbility = (FractionAbility) ability;return fractionAbility.getFractionManager();}return null;}public P getPresenter() {return mPresenter;}public void beforsetUIContent() {}@Overridepublic void showLoading(Boolean isShowProgress) {}@Overridepublic void hideLoading() {}@Overridepublic void onProgress(int progress) {}@Overridepublic void onErrorState(BaseModel model, int mType) {if (!BaseContent.getIsTrueCode(model.getCode())) {Toast.show(mContext, model.getMsg());}}
}

2、 BaseFraction封装

public abstract class BaseFraction<P extends BasePresenter> extends Fraction implements BaseView {protected P mPresenter;protected Component mComponentView;public abstract int getUIContent();protected abstract P createPresenter();public abstract void initComponent();public abstract void initData();public Context mContext;@Overrideprotected Component onComponentAttached(LayoutScatter scatter, ComponentContainer container, Intent intent) {mComponentView = scatter.parse(getUIContent(), container, false);mContext = getFractionAbility();mPresenter = createPresenter();initComponent();initData();return mComponentView;}@Overrideprotected void onStart(Intent intent) {super.onStart(intent);}@Overrideprotected void onActive() {super.onActive();}@Overrideprotected void onForeground(Intent intent) {super.onForeground(intent);}public String getString(int resId) {try {return getFractionAbility().getResourceManager().getElement(resId).getString();} catch (Exception e) {e.printStackTrace();}return "";}public int getColor(int colorId) {try {return getFractionAbility().getResourceManager().getElement(colorId).getColor();} catch (Exception e) {e.printStackTrace();}return 0;}@Overridepublic void showLoading(Boolean isShowProgress) {}@Overridepublic void hideLoading() {}@Overridepublic void onErrorState(BaseModel model, int mType) {if (!BaseContent.getIsTrueCode(model.getCode())) {Toast.show(mContext, model.getMsg());}}@Overridepublic void onProgress(int progress) {}
}

注:显示dialog方法可以直接放到base里显示,这样每个页面就不用每次重写了

3、 演示页面请求

public class TextSlice extends BaseAbilitySlice<MainPresenter> implements MainView {@Overrideprotected MainPresenter createPresenter() {return new MainPresenter(this);}@Overridepublic int getUIContent() {return ResourceTable.Layout_slice_collection;}@Overridepublic void initComponent() {//网络请求mPresenter.getTextApi();}@Overridepublic void onTextSuccess(BaseModel<TextBean> o) {//我是网络请求成功后的结果}
}

三、Retrofit运行时动态改变BaseUrl解决方案,及动态改变retrofit.create(cls)的接口cls,来实现组件化思想如android的Arouter,和鸿蒙服务的理念可分可合可流转多entry包思想

下面分为俩个部分来讲解对应实现原理,3.1:动态修改BaseUrl 3.2:动态修改retrofit.create(cls)的接口cls
3.1、Retrofit运行时动态改变BaseUrl解决方案
3.1.1、出现此类问题场景
在项目开发中涉及到多个BaseUrl,但在我们使用Retrofit开发时可能会遇到多BaseUrl不是很好处理情况,下面来讲解下我的处理方案,原理很简单
3.1.2、第一种解决方案
简单粗暴解决方案,利用Retrofit请求优先级,因为Retrofit支持全路径,比如

 @GET("http://www.baidu.com")Observable<Object> getApi(@Path("param") String param);

再比如

@GET
Observable<Object> getApi(@Url String fileUrl, @Query("param")String param);

3.1.3、第二种解决方案
Retrofit默认只能设置一个BaseUrl,没有提供其Api去修改,所以我们只能通过其他方案去实现,网上也有很多介绍的,但尝试用了下感觉很不理想,于是自己稍加封装了下,思路其实简单。

思路:一个Retrofit只能设置一个BaseUrl,我们可以创建多个Retrofit不就可以了吗?个接口创建一个,再通过用完再销毁思想,这样也可以,但是不是很理想,我们可以再转换思想,有几个BaseUrl创建几个,问这样不会造成内存开销?答案是不会的,项目中BaseUrl不会出现N多个,所以不必考虑这个问题

代码实现:在代码设计时可以尽可能去优化,所以当我们用到此BaseUrl时,再去创建,用不到不创建,这样便会出现个问题,怎样知道我应该使用哪个RetrofitRetrofit怎么去保存等问题,本人思路是创建成功便添加到集合缓存下载,使用的时候去比对集合中BaseUrl和当前是否匹配,如果一致从集合中获取,如果不一致去创建新的,如果使用没有传入BaseUrl便用默认的,实现代码如下
3.1.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;}
}

3.1.5、对创建Retrofit稍加封装
新建保存对象的集合

private static List<Retrofit> mRetrofitList = new ArrayList<>();
private static List<ApiRetrofit> 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;}}//新的baseUrlif (mIndex == -1) {synchronized (Object.class) {mApiRetrofit = new ApiRetrofit();mApiRetrofitList.add(mApiRetrofit);return mApiRetrofit;}} else {//以前已经创建过的baseUrlreturn 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;}}//新的baseUrlif (mIndex == -1) {synchronized (Object.class) {mApiRetrofit = new ApiRetrofit();mApiRetrofitList.add(mApiRetrofit);return mApiRetrofit;}} else {//以前已经创建过的baseUrlreturn mApiRetrofitList.get(mIndex);}}

3.1.6、使用时写法
地址可以写成常量

ApiRetrofit.getInstance("http://www.baidu.com/").getApiService().getCeShi(params)

3.2、态改变retrofit.create(cls)的接口cls,组件化思想很有必要
3.2.1、当我们搭建组件化后,立马会想到每个组件用一个接口类,或者搭建组件化时,每个模块用一个接口类,这种需求肯定会存在,看如何来封装(其中包含文件下载拦截器拦截逻辑,和添加请求头等逻辑,可参考,可忽略)

public class ApiRetrofit {private static Retrofit retrofit;private Gson gson;private static final int DEFAULT_TIMEOUT = 135;private static List<Retrofit> mRetrofitList = new ArrayList<>();public static String mBaseUrl = BaseContent.getBaseUrl();private static BaseView mBaseView = null;private static volatile Type mType = Type.BASE;public enum Type {FILE,BASE,BASE_URL,}public Type getType() {return mType;}public static void setType(Type type) {mType = type;}/*** 文件处理** @param httpClientBuilder*/public void initFileClient(OkHttpClient.Builder httpClientBuilder) {/*** 处理文件下载进度展示所需*/httpClientBuilder.addNetworkInterceptor(new ProgressInterceptor());}/*** 默认所需** @param httpClientBuilder*/public void initDefaultClient(OkHttpClient.Builder httpClientBuilder) {/*** 处理一些识别识别不了 ipv6手机,如小米  实现方案  将ipv6与ipv4置换位置,首先用ipv4解析*/
//        httpClientBuilder.dns(new ApiDns());/*** 添加cookie管理* 方法1:第三方框架*/PersistentCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(),new SharedPrefsCookiePersistor(app));httpClientBuilder.cookieJar(cookieJar);/*** 添加cookie管理* 方法2:手动封装cookie管理*/
//        httpClientBuilder.cookieJar(new CookieManger(BaseApp.getContent()));/*** 添加日志拦截 实现方式1     上下俩种二者选其一即可*/
//        httpClientBuilder.addInterceptor(new JournalInterceptor());/*** 添加日志拦截 实现方式2     上下俩种二者选其一即可*/HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor(new HttpLogger());logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);httpClientBuilder.addInterceptor(logInterceptor);/*** 添加请求头*/
//        httpClientBuilder.addInterceptor(new HeadUrlInterceptor());/*** 忽略证书*/
//        httpClientBuilder.hostnameVerifier(new AllowAllHostnameVerifier());}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);//错误重联switch (getType()) {case FILE:initFileClient(httpClientBuilder);break;case BASE:case BASE_URL:initDefaultClient(httpClientBuilder);break;}retrofit = new Retrofit.Builder().baseUrl(mBaseUrl).addConverterFactory(GsonConverterFactory.create(buildGson())).addCallAdapterFactory(RxJava2CallAdapterFactory.create()).client(httpClientBuilder.build()).build();mRetrofitList.add(retrofit);}/*** 增加后台返回""和"null"的处理,如果后台返回格式正常* 1.int=>0* 2.double=>0.00* 3.long=>0L* 4.String=>""** @return*/public Gson buildGson() {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;}private static <T> T create(Class<T> cls, String baseUrl) {mBaseUrl = baseUrl;if (retrofit == null) {new ApiRetrofit();} else {initRetrofit();}T t = retrofit.create(cls);return t;}private static void initRetrofit() {int mIndex = -1;for (int i = 0; i < mRetrofitList.size(); i++) {if (mBaseUrl.equals(mRetrofitList.get(i).baseUrl().toString())) {mIndex = i;break;}}//新的baseUrlif (mIndex == -1) {synchronized (Object.class) {new ApiRetrofit();}} else {//已经创建过的baseUrlretrofit = mRetrofitList.get(mIndex);}}/*** 默认使用方式** @return*/public static <T> T getInstance(Class<T> cls) {setType(Type.BASE);mBaseView = null;return create(cls, BaseContent.getBaseUrl());}/*** 文件下载使用方式** @param baseView* @return*/public static <T> T getFileInstance(Class<T> cls, BaseView baseView) {setType(Type.FILE);mBaseView = baseView;return create(cls, BaseContent.getBaseUrl() + "file/");}/*** 动态改变baseUrl使用方式** @param baseUrl* @return*/public static <T> T getBaseUrlInstance(Class<T> cls, String baseUrl) {setType(Type.BASE_URL);mBaseView = null;return create(cls, baseUrl);}}

######3.2.2、使用时写法

ApiRetrofit.getBaseUrlInstance(LiveApiServer.class, "http://www.baidu.com/").getCeShi(params)
ApiRetrofit.getInstance(LiveApiServer.class).getCeShi(params)

四、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<Double>, JsonDeserializer<Double> {@Overridepublic Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {try {if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定义为double类型,如果后台返回""或者null,则返回0.00return 0.00;}} catch (Exception ignore) {}try {return json.getAsDouble();} catch (NumberFormatException e) {throw new JsonSyntaxException(e);}}@Overridepublic JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {return new JsonPrimitive(src);}
}

4、 对int类型处理,返回“”,或“null”,动态更改为默认值0,新建DoubleDefaultAdapter类

public class IntegerDefaultAdapter implements JsonSerializer<Integer>, JsonDeserializer<Integer> {@Overridepublic Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)throws JsonParseException {try {if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定义为int类型,如果后台返回""或者null,则返回0return 0;}} catch (Exception ignore) {}try {return json.getAsInt();} catch (NumberFormatException e) {throw new JsonSyntaxException(e);}}@Overridepublic JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {return new JsonPrimitive(src);}
}

5、 对Long类型处理,返回“”,或“null”,动态更改为默认值0,新建DoubleDefaultAdapter类

public class LongDefault0Adapter implements JsonSerializer<Long>, JsonDeserializer<Long> {@Overridepublic Long deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)throws JsonParseException {try {if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定义为long类型,如果后台返回""或者null,则返回0return 0l;}} catch (Exception ignore) {}try {return json.getAsLong();} catch (NumberFormatException e) {throw new JsonSyntaxException(e);}}@Overridepublic JsonElement serialize(Long src, Type typeOfSrc, JsonSerializationContext context) {return new JsonPrimitive(src);}
}

5、 重点说一下String类型
根据上边其他类型处理代码可以看出,String也就是把上述类中代码改成String就可以了,答案是可以的,如下,处理的内容为如果服务器返回字符串类型null,我们将其格式化成“”,空类型,但是我们为什么不直接写,请往下看

public class StringDefaultConverter implements JsonSerializer<String>, JsonDeserializer<String> {@Overridepublic 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);}}@Overridepublic 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<String> {@Overridepublic 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;}}@Overridepublic void write(JsonWriter writer, String value) throws IOException {if (value == null) {writer.nullValue();return;}writer.value(value);}
}

定义的类型为String,这样为null的情况会都归这个类来处理,但是String的所有情况也会走里边的方法,所以为了同样的类型不执行俩遍,Stringnull都在此类处理,只处理一遍就可以了, 处理所有情况为返回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、 第二种实现方方法(涉及到相关三个类)
(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);}}@Overridepublic void saveFromResponse(HttpUrl url, List<Cookie> 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());}}}}@Overridepublic List<Cookie> loadForRequest(HttpUrl url) {List<Cookie> 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<String, ConcurrentHashMap<String, Cookie>> cookies;private final SharedPreferences cookiePrefs;public PersistentCookieStore(Context context) {cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);cookies = new HashMap<>();//将持久化的cookies缓存到内存中 即map cookiesMap<String, ?> prefsMap = cookiePrefs.getAll();for (Map.Entry<String, ?> 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<String, Cookie>());}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缓存到内存中 如果缓存过期 就重置此cookieif (!cookie.persistent()) {if (!cookies.containsKey(url.host())) {cookies.put(url.host(), new ConcurrentHashMap<String, Cookie>());}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<Cookie> get(HttpUrl url) {ArrayList<Cookie> 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<Cookie> getCookies() {ArrayList<Cookie> 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=1成功,data={},code=100,data=null

         * 重点说一下此种情况:此类是接口返回内容不规范,开发中肯定会存在这样类似问题,虽不是前端问题,但前端也可以很好处理此类问题* 假如正常情况 返回data为集合* code:1* msg:获取成功* data[ 。。。]** 当异常情况下,返回data:{}或者data:""* code:0* msg:获取失败* data:{}或者data:""** 这样我们定义好的类型Gson解析会失败,由于类型不统一,并报异常,发生此类情况,在不改动后台代码情况下,* 一般通常我们会定义成object类型再手动解析,但这样很是麻烦,所以,可参考此种实现方式** 实现原理:拦截gson解析,解析前一步,先解析一遍code,如果是定义正常的,继续向下解析,如果非正常情况,抛异常处理,* 并且将接口返回的code,msg一并抛出,异常会在这里拦截!!!!

当我们处理后台返回数据时,我们会将成功需要的数据提取出来,失败的只提示一下msg,所以通过判断code来区分状态,一般情况下我们可以在onNext()中判断,如下

@Overridepublic void onNext(BaseModel<T> 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) {onSuccessResult(t, o.getMsg(), o.getErrcode());} else {view.onErrorResult(o);}} catch (Exception e) {e.printStackTrace();onError(e.toString());}}

假如code=1是成功,获取成功值从onSuccessResult中拿,失败值只要code,msg从回调中onErrorResult拿,
返回的数据规范情况是没有问题的,但是,如果数据不规范,data原本需要{},但是返回了null,或者’’",这样GOSN解析立马报异常,所以我们需要向,当我们执行到OnNext方法中,此时已经执行了Gson解析代码,所以我们是否可以将判断提前到Gson解析时候判断呢? 请看第二种方法
2、 第二种判断方法,Gson解析期间判断
如果想通过Gson解析期间判断,这样必然会设计到Gson源码如果走向,我们通过更改源码来自定义操作,通过阅读源码我们会发现解析数据会涉及到三个类,GsonConverterFactory,GsonRequestBodyConverter,GsonResponseBodyConverter这三个类,我们需要重写这个三个类,阅读代码会返现主要执行解析代码在GsonResponseBodyConverter中,所以我们的目标便是这里。

思路:Gosn解析数据时,如果出现服务器下发非正常标识,此刻我们已判断服务器返回数据不是我们需要展示的,那我们解析到这一步已不用再向下解析,可以通过抛异常来释放当前任务代码如下

@Overridepublic 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通过分析异常是否为我们自定义实体类来判断下一步如何操作,此方法为路由的第二种判断,示例如下

@Overridepublic void onError(Throwable e) {if (mView != null) mView.hideLoading();if (e instanceof HttpException) {onErrorResult(new BaseModel<>(BAD_NETWORK, "网络超时"));} else if (e instanceof ConnectException ||e instanceof UnknownHostException) {onErrorResult(new BaseModel<>(CONNECT_ERROR, "连接错误"));} else if (e instanceof InterruptedIOException) {        //  连接超时onErrorResult(new BaseModel<>(CONNECT_TIMEOUT, "连接超时"));} else if (e instanceof JsonParseException|| e instanceof ParseException) {onErrorResult(new BaseModel<>(PARSE_ERROR, "数据解析失败"));} else if (e instanceof ApiException) {/**************************************************************** 重点说一下此种情况:此类是接口返回内容不规范,开发中肯定会存在这样类似问题,虽不是前端问题,但前端也可以很好处理此类问题* 假如正常情况 返回data为集合* code:1* msg:获取成功* data[ 。。。]** 当异常情况下,返回data:{}或者data:""* code:0* msg:获取失败* data:{}或者data:""** 这样我们定义好的类型Gson解析会失败,由于类型不统一,并报异常,发生此类情况,在不改动后台代码情况下,* 一般通常我们会定义成object类型再手动解析,但这样很是麻烦,所以,可参考此种实现方式** 实现原理:拦截gson解析,解析前一步,先解析一遍code,如果是定义正常的,继续向下解析,如果非正常情况,抛异常处理,* 并且将接口返回的code,msg一并抛出,异常会在这里拦截!!!!**************************************************************/ApiException apiException = (ApiException) e;onErrorResult(new BaseModel<>(apiException.getErrorCode(), apiException.getMessage()));} else {if (e != null) {onErrorResult(new BaseModel<>(CONNECT_N, e.toString()));} else {onErrorResult(new BaseModel<>(CONNECT_N, "未知错误"));}}}private void onSuccessResult(BaseModel<T> o) {onSuccess(o);}private void onErrorResult(BaseModel<T> o) {if (mView != null) mView.onErrorState(o, mType);}public abstract void onSuccess(BaseModel<T> o);

七、Retrofit配置及各情况处理(缓存拦截、日志打印、替换接口内容、参数添加等

相关参考跳转此链接

八、后记

如使用中遇到问题,后记中进行回答讲解

鸿蒙开发之网络框架搭建,MVP+Retrofit2+okhttp3+Rxjava2+RxHarmony相关推荐

  1. Android Compose 新闻App(一)网络框架搭建

    Compose 新闻App(一)网络框架搭建 前言 正文 一.项目创建 二.依赖配置 三.数据API 四.网络框架构建 五.项目配置 六.网络请求 七.源码 前言   要去学习新的知识,光是简单的使用 ...

  2. HTML5 APP开发环境的框架搭建

    HTML5 APP开发环境的框架搭建 转载:橘子博客 HTML5 开发APP需要一个开发框架和编译环境,参阅了相关资料后选择开源的PhoneGap.PhoneGap是一个开源的开发框架,使用HTML5 ...

  3. 上门洗车APP --- Android客户端开发 之 网络框架封装介绍(一)

    上门洗车APP --- Android客户端开发 之 网络框架封装介绍(一) 上篇文章中给大家简单介绍了一些业务,上门洗车APP --- Android客户端开发 前言及业务简介,本篇文章给大家介绍下 ...

  4. WEB开发之前端框架搭建-庞永旺-专题视频课程

    WEB开发之前端框架搭建-34人已学习 课程介绍         本课程主要教大家如何整理出常用的代码,编写前端常用的功能,课程的最大亮点就是数据模板的使用,此数据模板均为本人原创,此数据模板也经过几 ...

  5. Android之---项目开发中网络框架的选择

    项目开发中网络框架的选择 Android常用的网路框架: 大多数应用程序基本都需要连接网络,发送一些数据给服务端,或者从服务端获取一些数据.通常在 Android 中进行网络连接一般使用 Scoket ...

  6. 鸿蒙OS开发sdk,鸿蒙开发之基础环境搭建

    鸿蒙开发之基础环境搭建 一.下载和安装DevEco Studio HUAWEI DevEco Studio(以下简称DevEco Studio)是基于IntelliJ IDEA Community开源 ...

  7. 鸿蒙开发起步系列 | 环境搭建、HarmonyOS应用开发及智能硬件开发

    本文从鸿蒙环境搭建开始说起,并汇总了HarmonyOS智能硬件开发和应用开发的一手资料,全场景剖析HarmonyOS系统究竟是什么.如何助力开发者更便捷高效地开发应用. 1.我的鸿蒙起步--开发环境搭 ...

  8. 主机、虚拟机、开发板网络环境搭建小结备忘

    首先得声明,本文章属转载,个人觉得写得灰常好,图文并茂,详细,深刻,不仅让本来网络没学好的本人对网络这一块有了更深刻的理解,而且解决了我纠结了几天的 开发板 虚拟机  win7 之间的通信问题得到了解 ...

  9. 用gin进行web开发的基本框架搭建

    作者很菜,欢迎交流,不对的请指正! 使用gin构建了一个平常开发易用脚手架,代码简洁易读,可快速进行高效web开发. 主要功能有: mysql/redis的配置 使用viper读取配置文件(yaml) ...

最新文章

  1. R语言unlist函数将复杂数据(list列表、dataframe、字符串String)对象处理成简单向量vector形式:将包含dataframe和字符串的向量列表转换为单个向量(删除数据名称)
  2. Opencv-python 3.3版本安装
  3. java二维码生成技术
  4. SEO中HTML标签权重
  5. [BZOJ3791]作业
  6. 无法对齐已对齐的分区linux,使用parted命令对齐分区,以获得最佳性能
  7. Delphi 中的DLL 封装和调用对象技术
  8. L2-010 排座位 (25 分)
  9. Mysql入门实战上
  10. 将数组分成两部分,使得这两部分的和的差最小
  11. java rfc接口_java调用sap的RFC接口
  12. WOS(一)——文献高级检索
  13. 生活中有哪些行为是高情商的表现?
  14. DataFrame.to_excel多次写入不同Sheet
  15. 致远SPM解决方案之安全管理
  16. IE浏览器兼容性模式
  17. ThinkPad E420升级改造过程
  18. 网络编辑要学些什么技能?
  19. 基于QT实现的商品销售管理系统
  20. error 1962:no operating system found.boot sequence will automatically repeat.

热门文章

  1. 吃得不对,骨骺提前闭合类似“拔苗助长”,反而长不高
  2. HIT-ICS2023大作业傅一川2021113146
  3. Hirbernate[一]
  4. Docker版 Linux百度网盘备份工具
  5. 计算机本科生去实验室,为了完成毕业设计差点“累死”在实验室,如何高效完成?有秘诀!...
  6. 星界边境机器人升级_2天 科技全满。 星界边境 最简单升级上手生存攻略 有贴图...
  7. toPlainString() 、 toString()
  8. 人类活动识别的深度学习模型
  9. 使用Crypto实现RSA算法的数字签名和检验
  10. ANDROID PAD模拟器设置