抓住人生中的一分一秒,勝過虛度中的一月一年!
小做個動圖開篇引題
懶洋洋.gif
鴻蒙背景
2020年9月10號硼身,鴻蒙2.0(HarmonyOS 2.0)系統(tǒng)正式發(fā)布,鴻蒙2.0面向應(yīng)用開發(fā)者發(fā)布Beta版本覆享,在2020年9月10發(fā)布大屏佳遂,手表,車機(jī)版鴻蒙撒顿,2020年12月發(fā)布手機(jī)版鴻蒙丑罪。在2020年9月10日,鴻蒙開源路標(biāo)面向內(nèi)存128KB-128MB終端設(shè)備凤壁;2021年10月吩屹,將面向4GB以上所有設(shè)備。
前言
作為一個安卓開發(fā)者客扎,能夠看到屬于國產(chǎn)的操作系統(tǒng)確實很興奮祟峦,也許將來的某一天可能和
android
一戰(zhàn),但實際踩坑中發(fā)現(xiàn)徙鱼,鴻蒙需要走的路很長宅楞,系統(tǒng)優(yōu)化方面還有很多,和android
差距還是特別巨大的袱吆,入坑鴻蒙開發(fā)厌衙,可參考的東西少之有少,幾乎為0绞绒,所以需要大家一起行動起來婶希,互相分享,生態(tài)圈才能形成蓬衡,給大家分享點自己的踩坑之路和成果喻杈。
一個
APP
的必須品肯定是網(wǎng)絡(luò)訪問,所以第一篇文章先搭建個網(wǎng)絡(luò)框架供大家參考,可以更快的入手鴻蒙開發(fā)狰晚,鴻蒙支持java
開發(fā)筒饰,所以選擇了Retrofit+okhttp
組合,下面給大家演示下如何封裝使用壁晒,RxAndroid
不可用瓷们,需要改裝成RxHarmony
,有人肯定想為何不封裝攜程+mvvm
,這個需要問官方是否支持
- QQ群 : 鴻蒙開發(fā)技術(shù)討論QQ群:1084365075
相關(guān)業(yè)務(wù)需求及解決方案 |
---|
一谬晕、 MVP+Retrofit2+okhttp3+Rxjava2+RxHarmony 框架基本搭建及使用 |
二碘裕、 BaseAbilitySlice ,BaseFraction 封裝,搭配框架使用 |
三攒钳、 Retrofit 運行時動態(tài)改變BaseUrl 解決方案帮孔,及動態(tài)改變retrofit.create(cls) 的接口cls ,來實現(xiàn)組件化思想如android 的Arouter 夕玩,和鴻蒙服務(wù)的理念可分可合可流轉(zhuǎn)多entry 包思想 |
四你弦、 Retrofit ,Gson 解析燎孟,請求返回的類型不統(tǒng)一禽作,假如double 返回的是null
|
五、 Retrofit 實現(xiàn)cookie 自動化管理 |
六揩页、 接口成功失敗路由判斷旷偿,處理格式異常情況,如code=1成功爆侣,data={},code=100,data=null |
七萍程、 Retrofit 配置及各情況處理(緩存攔截、日志打印兔仰、替換接口內(nèi)容茫负、參數(shù)添加等) |
八、 Retrofit 文件上傳(封裝中有乎赴,暫未實踐) |
九忍法、 Retrofit 文件下載(封裝中有,暫未實踐) |
十榕吼、 后記 |
十一饿序、 本文譩在一篇文章搞定所有,上述描述文章都有講解 |
一羹蚣、MVP+Retrofit2+Okhttp3+Rxjava2+RxHarmony框架基本搭建
1原探、我們需要依賴相關(guān)第三方庫
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、創(chuàng)建接口類ApiServer顽素,定義接口方法
public interface ApiServer {
@FormUrlEncoded
@POST("/api/table_list/")
Observable<BaseModel<Object>> getCeShi(@FieldMap HashMap<String, String> params);
}
3咽弦、 上述1,2整理完畢胁出,開始創(chuàng)建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搭配使用如上述內(nèi)容所述离唬,下邊開始配合Rxjava使用
4、 先封裝個基本實體類BaseModle划鸽,下面會用到(準(zhǔn)備工作)
封裝理由:一個項目一般情況下json返回格式外層都是統(tǒng)一的
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、 定義幾個會用到的接口,來區(qū)分如網(wǎng)絡(luò)開始裸诽,結(jié)束嫂用,進(jìn)度條加載,錯誤碼等(準(zhǔn)備工作)
public interface BaseView {
/**--------------------------------------------*
* 接口開始情況 這時我們可以顯示 菊花圈 或顯示下載進(jìn)度條
*-------------------------------------------*/
void showLoading(Boolean isShowProgress);
/**--------------------------------------------*
* 接口請求完畢 這時我們可以 將菊花圈隱藏掉 或下載進(jìn)度條隱藏掉
*-------------------------------------------*/
void hideLoading();
/**--------------------------------------------*
* 返回 非定義的code狀態(tài)碼丈冬,和msg mType 區(qū)分異常時請求的接口是哪個
*-------------------------------------------*/
void onErrorState(BaseModel model, int mType);
/**--------------------------------------------*
* 如果是下載文件時嘱函,或上傳文件, 此回調(diào)是 文件下載進(jìn)度監(jiān)聽回調(diào)
*-------------------------------------------*/
void onProgress(int progress);
}
6埂蕊、 BaseObserver封裝往弓,開始結(jié)合Rxjava使用,以下為訂閱后回調(diào)代表含義蓄氧,封裝原因如下
1函似、onStart
為網(wǎng)絡(luò)請求開始,我們可以將剛才創(chuàng)建的接口實現(xiàn)一下BaseView
中showLoading()
喉童,用來代表網(wǎng)絡(luò)開始的菊花框顯示
2.onNext
為網(wǎng)絡(luò)返回的內(nèi)容撇寞,這時我們就可以將顯示的菊花框關(guān)閉掉,BaseView
中hideLoading()
3堂氯、onError
為網(wǎng)絡(luò)請求失敗的返回狀態(tài)蔑担,可以通過異常來區(qū)分網(wǎng)絡(luò)失敗原因,分析好的異常情況然后以接口形式回調(diào)出去咽白,所以實現(xiàn)BaseView
中的onErrorState
方法啤握,onErrorState(BaseModel model, int mType);
有人會問type
作用是什么,其實是用來區(qū)分請求的是哪個接口晶框,因為所有失敗異常我們統(tǒng)一回調(diào)一個方法排抬,這樣區(qū)分不出是哪個接口失敗的,所以傳入一個type值三妈,然后再回傳出去畜埋,可知哪個接口失敗
4、onComplete
代表請求完畢畴蒲,這里不做任何操作悠鞍,關(guān)閉菊花圈已經(jīng)在onNext
中回掉了,當(dāng)然模燥,你也可以在這里回調(diào)咖祭,但是存在一定體驗問題,可以自行測試下
說明:如下封裝包含其他邏輯判斷蔫骂,在下邊文章專題中進(jìn)行講解么翰,無關(guān)方法可以忽略
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;
//回傳標(biāo)識
private int mType = 0;
//true 展示進(jìn)度條
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, "網(wǎng)絡(luò)超時"));
} 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, "數(shù)據(jù)解析失敗"));
} else if (e instanceof ApiException) {
/***************************************************************
* 重點說一下此種情況:此類是接口返回內(nèi)容不規(guī)范,開發(fā)中肯定會存在這樣類似問題辽旋,雖不是前端問題浩嫌,但前端也可以很好處理此類問題
* 假如正常情況 返回data為集合
* code:1
* msg:獲取成功
* data[ 檐迟。。码耐。]
*
* 當(dāng)異常情況下追迟,返回data:{}或者data:""
* code:0
* msg:獲取失敗
* data:{}或者data:""
*
* 這樣我們定義好的類型Gson解析會失敗,由于類型不統(tǒng)一骚腥,并報異常敦间,發(fā)生此類情況,在不改動后臺代碼情況下束铭,
* 一般通常我們會定義成object類型再手動解析廓块,但這樣很是麻煩,所以契沫,可參考此種實現(xiàn)方式
*
* 實現(xiàn)原理:攔截gson解析带猴,解析前一步,先解析一遍code,如果是定義正常的埠褪,繼續(xù)向下解析浓利,如果非正常情況,拋異常處理钞速,
* 并且將接口返回的code,msg一并拋出贷掖,異常會在這里攔截!?视铩F煌!
**************************************************************/
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封裝
當(dāng)我們使用Rxjava
的subscribe
訂閱后,網(wǎng)絡(luò)會立即觸發(fā)技肩,但是在請求中UI
層destroy
了怎么辦且轨,不及時取消訂閱,可能會造成內(nèi)存泄漏虚婿,這時候CompositeDisposable
開始上場了旋奢,它可以對我們訂閱的請求進(jìn)行統(tǒng)一管理。
大致三步走:
1然痊、在UI
層創(chuàng)建的時候(比如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
是在子線程中執(zhí)行真仲,需要將返回結(jié)果回調(diào)到主線程,rxandroid
負(fù)責(zé)此任務(wù)初澎,然而鴻蒙無法使用rxandroid
,因為android
通知類方法handler
,鴻蒙的是EventHandler
虑凛,所以方法不一樣不可以使用碑宴,需要根據(jù)rxandroid
原理,重寫改裝成RxHarmony
桑谍,如下改裝三個類延柠,不由官方維護(hù),目前正常使用
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.");
}
}
相關(guān)邏輯已封裝完畢锣披,下面看下如何使用
9贞间、 接口請求三步驟,第一步驟寫個接口雹仿,用來回調(diào)數(shù)據(jù)增热,如定義MainView
,并繼承BaseView
public interface MainView extends BaseView {
void onTextSuccess(BaseModel<TextBean> o);
}
10胧辽、 接口請求三步驟峻仇,第二步驟p
層,繼承BasePresenter
邑商,串聯(lián)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ìn)行網(wǎng)絡(luò)請求案例如下,當(dāng)然吭从,現(xiàn)在頁面回調(diào)這么多東西,很不美觀恶迈,就會想到j(luò)將無關(guān)方法放到基類涩金,會引發(fā)Base
->AbilitySlice
,Fraction
寫法蝉绷,請看第二部分內(nèi)容鸭廷,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);
//網(wǎng)絡(luò)請求
presenter.getTextApi();
}
@Override
public void showLoading(Boolean isShowProgress) {
/**--------------------------------------------*
* 接口開始情況 這時我們可以顯示 菊花圈 或顯示下載進(jìn)度條
*-------------------------------------------*/
}
@Override
public void hideLoading() {
/**--------------------------------------------*
* 接口請求完畢 這時我們可以 將菊花圈隱藏掉 或下載進(jìn)度條隱藏掉
*-------------------------------------------*/
}
@Override
public void onProgress(int progress) {
/**--------------------------------------------*
* 如果是下載文件時熔吗,或上傳文件辆床, 此回調(diào)是 文件下載進(jìn)度監(jiān)聽回調(diào)
*-------------------------------------------*/
}
@Override
public void onErrorState(BaseModel model, int mType) {
/**--------------------------------------------*
* 返回 非定義的code狀態(tài)碼,和msg mType 區(qū)分異常時請求的接口是哪個
*-------------------------------------------*/
}
}
二桅狠、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;
@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() {
//網(wǎng)絡(luò)請求
mPresenter.getTextApi();
}
@Override
public void onTextSuccess(BaseModel<TextBean> o) {
//我是網(wǎng)絡(luò)請求成功后的結(jié)果
}
}
三、Retrofit運行時動態(tài)改變BaseUrl解決方案一喘,及動態(tài)改變retrofit.create(cls)的接口cls驱还,來實現(xiàn)組件化思想如android的Arouter,和鴻蒙服務(wù)的理念可分可合可流轉(zhuǎn)多entry包思想
下面分為倆個部分來講解對應(yīng)實現(xiàn)原理凸克,3.1:動態(tài)修改BaseUrl 3.2:動態(tài)修改retrofit.create(cls)的接口cls
3.1议蟆、Retrofit運行時動態(tài)改變BaseUrl解決方案
3.1.1、出現(xiàn)此類問題場景
在項目開發(fā)中涉及到多個BaseUrl
萎战,但在我們使用Retrofit開發(fā)時可能會遇到多BaseUrl
不是很好處理情況咐容,下面來講解下我的處理方案,原理很簡單
3.1.2蚂维、第一種解決方案
簡單粗暴解決方案戳粒,利用Retrofit
請求優(yōu)先級,因為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
默認(rèn)只能設(shè)置一個BaseUrl
,沒有提供其Api
去修改孝鹊,所以我們只能通過其他方案去實現(xiàn)炊琉,網(wǎng)上也有很多介紹的,但嘗試用了下感覺很不理想又活,于是自己稍加封裝了下苔咪,思路其實簡單。
思路:一個Retrofit
只能設(shè)置一個BaseUrl
柳骄,我們可以創(chuàng)建多個Retrofit
不就可以了嗎团赏?個接口創(chuàng)建一個,再通過用完再銷毀思想耐薯,這樣也可以舔清,但是不是很理想,我們可以再轉(zhuǎn)換思想曲初,有幾個BaseUrl
創(chuàng)建幾個体谒,問這樣不會造成內(nèi)存開銷?答案是不會的臼婆,項目中BaseUrl
不會出現(xiàn)N
多個抒痒,所以不必考慮這個問題
代碼實現(xiàn):在代碼設(shè)計時可以盡可能去優(yōu)化,所以當(dāng)我們用到此BaseUrl
時颁褂,再去創(chuàng)建故响,用不到不創(chuàng)建傀广,這樣便會出現(xiàn)個問題,怎樣知道我應(yīng)該使用哪個Retrofit
和Retrofit
怎么去保存等問題彩届,本人思路是創(chuàng)建成功便添加到集合緩存下載伪冰,使用的時候去比對集合中BaseUrl
和當(dāng)前是否匹配脆丁,如果一致從集合中獲取睁蕾,如果不一致去創(chuàng)建新的,如果使用沒有傳入BaseUrl
便用默認(rèn)的认臊,實現(xiàn)代碼如下
3.1.4寨辩、一般創(chuàng)建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);//錯誤重聯(lián)
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寂汇、對創(chuàng)建Retrofit稍加封裝
新建保存對象的集合
private static List<Retrofit> mRetrofitList = new ArrayList<>();
private static List<ApiRetrofit> mApiRetrofitList = new ArrayList<>();
修改創(chuàng)建時候的邏輯,如果請求接口時傳入BaseUrl
捣染,檢測BaseUrl
是否為空,如果為空使用默認(rèn)接口停巷,如果不為空耍攘,再從緩存的Retrofit
中查找是否已經(jīng)才創(chuàng)建過了,如果創(chuàng)建了用緩存的畔勤,如果沒有創(chuàng)建則創(chuàng)建
注:這塊可以用正則檢查下傳入的url
是否為正規(guī)的域名蕾各,再做下判斷
//創(chuàng)建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 {
//以前已經(jīng)創(chuàng)建過的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 {
//以前已經(jīng)創(chuàng)建過的baseUrl
return mApiRetrofitList.get(mIndex);
}
}
3.1.6、使用時寫法
地址可以寫成常量
ApiRetrofit.getInstance("http://www.baidu.com/").getApiService().getCeShi(params)
3.2庆揪、態(tài)改變retrofit.create(cls)的接口cls式曲,組件化思想很有必要
3.2.1、當(dāng)我們搭建組件化后缸榛,立馬會想到每個組件用一個接口類吝羞,或者搭建組件化時,每個模塊用一個接口類内颗,這種需求肯定會存在钧排,看如何來封裝(其中包含文件下載攔截器攔截邏輯,和添加請求頭等邏輯均澳,可參考恨溜,可忽略)
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) {
/**
* 處理文件下載進(jìn)度展示所需
*/
httpClientBuilder.addNetworkInterceptor(new ProgressInterceptor());
}
/**
* 默認(rèn)所需
*
* @param httpClientBuilder
*/
public void initDefaultClient(OkHttpClient.Builder httpClientBuilder) {
/**
* 處理一些識別識別不了 ipv6手機(jī),如小米 實現(xiàn)方案 將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()));
/**
* 添加日志攔截 實現(xiàn)方式1 上下倆種二者選其一即可
*/
// httpClientBuilder.addInterceptor(new JournalInterceptor());
/**
* 添加日志攔截 實現(xiàn)方式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);//錯誤重聯(lián)
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 {
//已經(jīng)創(chuàng)建過的baseUrl
retrofit = mRetrofitList.get(mIndex);
}
}
/**
* 默認(rèn)使用方式
*
* @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/");
}
/**
* 動態(tài)改變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解析项戴,請求返回的類型不統(tǒng)一,假如double返回的是null
現(xiàn)實開發(fā)中颗品,往往會遇到后臺返回數(shù)據(jù)格式不規(guī)范情況肯尺,比如前端字段原本定義為int
類型沃缘,而數(shù)據(jù)返回為空,如果用Gson
解析會導(dǎo)致解析失敗则吟,比如字段定義為double
類型槐臀,而返回的格式為字符串null
,導(dǎo)致解析失敗等等(只在后臺返回數(shù)據(jù)格式不規(guī)范情況下出現(xiàn)氓仲,如果后臺返回格式規(guī)范并不用考慮此問題)
1水慨、 實現(xiàn)目標(biāo)
1、格式化數(shù)據(jù)不規(guī)范【格式化int
類型數(shù)據(jù)】
2敬扛、格式化數(shù)據(jù)不規(guī)范【格式化Long
類型數(shù)據(jù)】
3晰洒、格式化數(shù)據(jù)不規(guī)范【格式化Double
類型數(shù)據(jù)】
4、格式化數(shù)據(jù)不規(guī)范【格式化String
類型數(shù)據(jù)】
5啥箭、格式化數(shù)據(jù)不規(guī)范【格式化Null
類型數(shù)據(jù)】
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);//錯誤重聯(lián)
retrofit = new Retrofit.Builder()
.baseUrl(BASE_SERVER_URL)
.addConverterFactory(GsonConverterFactory.create(buildGson()))//添加json轉(zhuǎn)換框架buildGson()根據(jù)需求添加
//支持RxJava2
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(httpClientBuilder.build())
.build();
apiServer = retrofit.create(ApiServer.class);
}
3、 對double類型處理急侥,返回“”砌滞,或“null”,動態(tài)更改為默認(rèn)值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”打掘,動態(tài)更改為默認(rèn)值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”侣夷,動態(tài)更改為默認(rèn)值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類型
根據(jù)上邊其他類型處理代碼可以看出惜纸,String
也就是把上述類中代碼改成String
就可以了叶撒,答案是可以的,如下耐版,處理的內(nèi)容為如果服務(wù)器返回字符串類型null
,我們將其格式化成“”祠够,空類型,但是我們?yōu)槭裁床恢苯訉懛嗌埻驴?/p>
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);
}
}
但是有種比較常見的不規(guī)范數(shù)據(jù)返回古瓤,為null
,不是字符串的"null",是這個null
落君,如果返回null
,會進(jìn)入到上邊這個類嗎穿香,經(jīng)過測試,返回null
的直接跳過绎速,所以出現(xiàn)了個問題皮获,null
到底是什么類型?
通過讀源碼可知纹冤,我們可以自定義TypeAdapter
洒宝,將其放入facotries
中,并且gson
在解析json
時使用對應(yīng)的TypeAdapter
來的萌京,而我們手動添加的TypeAdapter
會優(yōu)先于預(yù)設(shè)的TypeAdapter
被使用雁歌。
于是乎找到了一種其他方法來解決這個問題
新建個類來集成TypeAdapter,這樣就便優(yōu)先于預(yù)設(shè)的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
的情況會都?xì)w這個類來處理,但是String
的所有情況也會走里邊的方法求妹,所以為了同樣的類型不執(zhí)行倆遍较坛,String
和null
都在此類處理,只處理一遍就可以了扒最, 處理所有情況為返回null
,或字符串"null",格式化為"" 空
五华嘹、Retrofit實現(xiàn)cookie自動化管理
對應(yīng)文章解析
在現(xiàn)實開發(fā)中吧趣,我們可能會遇到這樣的需求,需要保持長登陸狀態(tài)耙厚,登陸失效為服務(wù)器判斷强挫,在我們不想往接口添加任何參數(shù)處理時,我們便想到cookie
最終實現(xiàn)效果為:登錄成功后將將服務(wù)器返回的cookie保存到本地(每次接口請求成功薛躬,更新本地保存Cookie值俯渤,目的讓本地的cookie值一直為最新的),下次請求接口時將本地最新cookie帶上型宝,用來告訴哪個用戶與服務(wù)器之間的交互
1八匠、 第一種實現(xiàn)方方法(第三方庫實現(xiàn)Cookie自動化管理)
(1)依賴第三方庫
implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
(2)創(chuàng)建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)// 設(shè)置封裝好的cookieJar
.build();
2、 第二種實現(xiàn)方方法(涉及到相關(guān)三個類)
(1)創(chuàng)建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)創(chuàng)建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)創(chuàng)建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緩存到內(nèi)存中 即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緩存到內(nèi)存中 如果緩存過期 就重置此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;
}
/**
* 二進(jìn)制數(shù)組轉(zhuǎn)十六進(jìn)制字符串
*
* @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);
}
/**
* 十六進(jìn)制字符串轉(zhuǎn)二進(jìn)制數(shù)組
*
* @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)創(chuàng)建OkHttpClient時添加cookieJar
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.addInterceptor(new LoginInterceptor())
.cookieJar(new CookieManger (context))// 設(shè)置封裝好的cookieJar
.build();
六趴酣、接口成功失敗路由判斷梨树,處理格式異常情況,如code=1成功岖寞,data={},code=100,data=null
* 重點說一下此種情況:此類是接口返回內(nèi)容不規(guī)范抡四,開發(fā)中肯定會存在這樣類似問題,雖不是前端問題,但前端也可以很好處理此類問題
* 假如正常情況 返回data為集合
* code:1
* msg:獲取成功
* data[ 指巡。淑履。。]
*
* 當(dāng)異常情況下藻雪,返回data:{}或者data:""
* code:0
* msg:獲取失敗
* data:{}或者data:""
*
* 這樣我們定義好的類型Gson解析會失敗秘噪,由于類型不統(tǒng)一,并報異常阔涉,發(fā)生此類情況缆娃,在不改動后臺代碼情況下,
* 一般通常我們會定義成object類型再手動解析瑰排,但這樣很是麻煩贯要,所以,可參考此種實現(xiàn)方式
*
* 實現(xiàn)原理:攔截gson解析椭住,解析前一步崇渗,先解析一遍code,如果是定義正常的,繼續(xù)向下解析京郑,如果非正常情況宅广,拋異常處理,
* 并且將接口返回的code,msg一并拋出些举,異常會在這里攔截8!;骸驶臊!
當(dāng)我們處理后臺返回數(shù)據(jù)時,我們會將成功需要的數(shù)據(jù)提取出來叼丑,失敗的只提示一下msg,所以通過判斷code來區(qū)分狀態(tài)关翎,一般情況下我們可以在onNext()中判斷,如下
@Override
public void onNext(BaseModel<T> o) {
T t = o.getData();
try {
/* if (t!=null){
L.e("返回數(shù)據(jù)="+o.toString());
}else {
L.e("返回數(shù)據(jù)=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從回調(diào)中onErrorResult拿,
返回的數(shù)據(jù)規(guī)范情況是沒有問題的星立,但是爽茴,如果數(shù)據(jù)不規(guī)范,data原本需要{}绰垂,但是返回了null闹啦,或者''",這樣GOSN解析立馬報異常,所以我們需要向辕坝,當(dāng)我們執(zhí)行到OnNext方法中窍奋,此時已經(jīng)執(zhí)行了Gson解析代碼,所以我們是否可以將判斷提前到Gson解析時候判斷呢? 請看第二種方法
2琳袄、 第二種判斷方法江场,Gson解析期間判斷
如果想通過Gson解析期間判斷,這樣必然會設(shè)計到Gson源碼如果走向窖逗,我們通過更改源碼來自定義操作址否,通過閱讀源碼我們會發(fā)現(xiàn)解析數(shù)據(jù)會涉及到三個類,GsonConverterFactory
,GsonRequestBodyConverter
,GsonResponseBodyConverter
這三個類碎紊,我們需要重寫這個三個類佑附,閱讀代碼會返現(xiàn)主要執(zhí)行解析代碼在GsonResponseBodyConverter
中,所以我們的目標(biāo)便是這里仗考。
思路:Gosn解析數(shù)據(jù)時音同,如果出現(xiàn)服務(wù)器下發(fā)非正常標(biāo)識,此刻我們已判斷服務(wù)器返回數(shù)據(jù)不是我們需要展示的秃嗜,那我們解析到這一步已不用再向下解析权均,可以通過拋異常來釋放當(dāng)前任務(wù)代碼如下
@Override
public T convert(ResponseBody value) throws IOException {
String response = value.string();
BaseResult re = gson.fromJson(response, BaseResult.class);
//關(guān)注的重點,自定義響應(yīng)碼中非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
塔橡,內(nèi)含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, "網(wǎng)絡(luò)超時"));
} 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, "數(shù)據(jù)解析失敗"));
} else if (e instanceof ApiException) {
/***************************************************************
* 重點說一下此種情況:此類是接口返回內(nèi)容不規(guī)范炮捧,開發(fā)中肯定會存在這樣類似問題庶诡,雖不是前端問題,但前端也可以很好處理此類問題
* 假如正常情況 返回data為集合
* code:1
* msg:獲取成功
* data[ 咆课。末誓。。]
*
* 當(dāng)異常情況下书蚪,返回data:{}或者data:""
* code:0
* msg:獲取失敗
* data:{}或者data:""
*
* 這樣我們定義好的類型Gson解析會失敗喇澡,由于類型不統(tǒng)一,并報異常殊校,發(fā)生此類情況晴玖,在不改動后臺代碼情況下,
* 一般通常我們會定義成object類型再手動解析,但這樣很是麻煩呕屎,所以让簿,可參考此種實現(xiàn)方式
*
* 實現(xiàn)原理:攔截gson解析,解析前一步秀睛,先解析一遍code,如果是定義正常的尔当,繼續(xù)向下解析,如果非正常情況蹂安,拋異常處理椭迎,
* 并且將接口返回的code,msg一并拋出,異常會在這里攔截L镉P蠛拧!缠黍!
**************************************************************/
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配置及各情況處理(緩存攔截、日志打印瓷式、替換接口內(nèi)容替饿、參數(shù)添加等
八、后記
如使用中遇到問題贸典,后記中進(jìn)行回答講解