赞
踩
抓住人生中的一分一秒,胜过虚度中的一月一年!
小做个动图开篇引题
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 框架基本搭建及使用 |
二、 BaseAbilitySlice ,BaseFraction 封装,搭配框架使用 |
三、 Retrofit 运行时动态改变BaseUrl 解决方案,及动态改变retrofit.create(cls) 的接口cls ,来实现组件化思想如android 的Arouter ,和鸿蒙服务的理念可分可合可流转多entry 包思想 |
四、 Retrofit ,Gson 解析,请求返回的类型不统一,假如double 返回的是null |
五、 Retrofit 实现cookie 自动化管理 |
六、 接口成功失败路由判断,处理格式异常情况,如code=1成功,data={},code=100,data=null |
七、 Retrofit 配置及各情况处理(缓存拦截、日志打印、替换接口内容、参数添加等) |
八、 Retrofit 文件上传(封装中有,暂未实践) |
九、 Retrofit 文件下载(封装中有,暂未实践) |
十、 后记 |
十一、 本文譩在一篇文章搞定所有,上述描述文章都有讲解 |
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
为网络请求开始,我们可以将刚才创建的接口实现一下BaseView
中showLoading()
,用来代表网络开始的菊花框显示
2.onNext
为网络返回的内容,这时我们就可以将显示的菊花框关闭掉,BaseView
中hideLoading()
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; } @Override protected void onStart() { if (mView != null) mView.showLoading(isShowProgress); } @Override public 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(); } } @Override public 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); } @Override public void onComplete() { } public abstract void onSuccess(BaseModel<T> o); }
Rxjava
逻辑如上,下边开始讲解如何将Retrofit
,okhttp
,Rxjava
,RxHarmony
连贯起来使用
7、 BasePresenter封装
当我们使用Rxjava
的subscribe
订阅后,网络会立即触发,但是在请求中UI
层destroy
了怎么办,不及时取消订阅,可能会造成内存泄漏,这时候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; } @Override public 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; } @Override public 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; } @Override public 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; } @Override public void dispose() { disposed = true; handler.removeAllEvent(); } @Override public 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; } @Override public void run() { try { delegate.run(); } catch (Throwable t) { RxJavaPlugins.onError(t); } } @Override public void dispose() { disposed = true; handler.removeAllEvent(); } @Override public 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
,串联okhttp
,Retrofit
,Rxjava
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) { @Override public void onSuccess(BaseModel o) { baseView.onTextSuccess((BaseModel<TextBean>) o); } @Override public void onError(String msg) { if (baseView != null) { baseView.showError(msg); } } }); } }
11、 在AbilitySlice
中进行网络请求案例如下,当然,现在页面回调这么多东西,很不美观,就会想到j将无关方法放到基类,会引发Base
->AbilitySlice
,Fraction
写法,请看第二部分内容,BaseAbilitySlice
,BaseFraction
封装
public class TextSlice extends BaseAbilitySlice<MainPresenter>{ @Override public int getUIContent() { return ResourceTable.Layout_slice_collection; } @Override public void initComponent() { MainPresenter presenter = new MainPresenter(this); //网络请求 presenter.getTextApi(); } @Override public void showLoading(Boolean isShowProgress) { /**--------------------------------------------* * 接口开始情况 这时我们可以显示 菊花圈 或显示下载进度条 *-------------------------------------------*/ } @Override public void hideLoading() { /**--------------------------------------------* * 接口请求完毕 这时我们可以 将菊花圈隐藏掉 或下载进度条隐藏掉 *-------------------------------------------*/ } @Override public void onProgress(int progress) { /**--------------------------------------------* * 如果是下载文件时,或上传文件, 此回调是 文件下载进度监听回调 *-------------------------------------------*/ } @Override public void onErrorState(BaseModel model, int mType) { /**--------------------------------------------* * 返回 非定义的code状态码,和msg mType 区分异常时请求的接口是哪个 *-------------------------------------------*/ } }
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; @Override protected 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() { } @Override public void showLoading(Boolean isShowProgress) { } @Override public void hideLoading() { } @Override public void onProgress(int progress) { } @Override public 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; @Override protected Component onComponentAttached(LayoutScatter scatter, ComponentContainer container, Intent intent) { mComponentView = scatter.parse(getUIContent(), container, false); mContext = getFractionAbility(); mPresenter = createPresenter(); initComponent(); initData(); return mComponentView; } @Override protected void onStart(Intent intent) { super.onStart(intent); } @Override protected void onActive() { super.onActive(); } @Override protected 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; } @Override public void showLoading(Boolean isShowProgress) { } @Override public void hideLoading() { } @Override public void onErrorState(BaseModel model, int mType) { if (!BaseContent.getIsTrueCode(model.getCode())) { Toast.show(mContext, model.getMsg()); } } @Override public void onProgress(int progress) { } }
注:显示dialog方法可以直接放到base里显示,这样每个页面就不用每次重写了
3、 演示页面请求
public class TextSlice extends BaseAbilitySlice<MainPresenter> implements MainView { @Override protected MainPresenter createPresenter() { return new MainPresenter(this); } @Override public int getUIContent() { return ResourceTable.Layout_slice_collection; } @Override public void initComponent() { //网络请求 mPresenter.getTextApi(); } @Override public void onTextSuccess(BaseModel<TextBean> o) { //我是网络请求成功后的结果 } }
下面分为俩个部分来讲解对应实现原理,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
时,再去创建,用不到不创建,这样便会出现个问题,怎样知道我应该使用哪个Retrofit
和Retrofit
怎么去保存等问题,本人思路是创建成功便添加到集合缓存下载,使用的时候去比对集合中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; } } //新的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); } }
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; } } //新的baseUrl if (mIndex == -1) { synchronized (Object.class) { new ApiRetrofit(); } } else { //已经创建过的baseUrl retrofit = 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)
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> { @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<Integer>, JsonDeserializer<Integer> { @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<Long>, JsonDeserializer<Long> { @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<String>, JsonDeserializer<String> { @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<String> { @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",格式化为"" 空
对应文章解析
在现实开发中,我们可能会遇到这样的需求,需要保持长登陆状态,登陆失效为服务器判断,在我们不想往接口添加任何参数处理时,我们便想到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); } } @Override public 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()); } } } } @Override public 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 cookies Map<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缓存到内存中 如果缓存过期 就重置此cookie if (!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()中判断,如下
@Override public 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解析数据时,如果出现服务器下发非正常标识,此刻我们已判断服务器返回数据不是我们需要展示的,那我们解析到这一步已不用再向下解析,可以通过抛异常来释放当前任务代码如下
@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 (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);
如使用中遇到问题,后记中进行回答讲解
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。