鴻蒙開發(fā)之網(wǎng)絡(luò)框架搭建,MVP+Retrofit2+okhttp3+Rxjava2+RxHarmony

抓住人生中的一分一秒,勝過虛度中的一月一年!

小做個動圖開篇引題


懶洋洋.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框架基本搭建及使用
二碘裕、 BaseAbilitySliceBaseFraction封裝,搭配框架使用
三攒钳、 Retrofit運行時動態(tài)改變BaseUrl解決方案帮孔,及動態(tài)改變retrofit.create(cls)的接口cls,來實現(xiàn)組件化思想如androidArouter夕玩,和鴻蒙服務(wù)的理念可分可合可流轉(zhuǎn)多entry包思想
四你弦、 RetrofitGson解析燎孟,請求返回的類型不統(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)一下BaseViewshowLoading()喉童,用來代表網(wǎng)絡(luò)開始的菊花框顯示
2.onNext為網(wǎng)絡(luò)返回的內(nèi)容撇寞,這時我們就可以將顯示的菊花框關(guān)閉掉,BaseViewhideLoading()
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牙甫,okhttpRxjava调违,RxHarmony連貫起來使用

7窟哺、 BasePresenter封裝

當(dāng)我們使用Rxjavasubscribe訂閱后,網(wǎng)絡(luò)會立即觸發(fā)技肩,但是在請求中UIdestroy了怎么辦且轨,不及時取消訂閱,可能會造成內(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摄咆,RetrofitRxjava
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->AbilitySliceFraction寫法蝉绷,請看第二部分內(nèi)容鸭廷,BaseAbilitySliceBaseFraction封裝
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)該使用哪個RetrofitRetrofit怎么去保存等問題彩届,本人思路是創(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í)行倆遍较坛,Stringnull都在此類處理,只處理一遍就可以了扒最, 處理所有情況為返回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ù)添加等

相關(guān)參考跳轉(zhuǎn)此鏈接

八、后記

如使用中遇到問題贸典,后記中進(jìn)行回答講解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末视卢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子廊驼,更是在濱河造成了極大的恐慌据过,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妒挎,死亡現(xiàn)場離奇詭異绳锅,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)酝掩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門鳞芙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人期虾,你說我怎么就攤上這事原朝。” “怎么了镶苞?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵喳坠,是天一觀的道長。 經(jīng)常有香客問我茂蚓,道長壕鹉,這世上最難降的妖魔是什么剃幌? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮御板,結(jié)果婚禮上锥忿,老公的妹妹穿的比我還像新娘。我一直安慰自己怠肋,他們只是感情好敬鬓,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著笙各,像睡著了一般钉答。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上杈抢,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天数尿,我揣著相機(jī)與錄音,去河邊找鬼惶楼。 笑死右蹦,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的歼捐。 我是一名探鬼主播何陆,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼豹储!你這毒婦竟也來了贷盲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤剥扣,失蹤者是張志新(化名)和其女友劉穎巩剖,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钠怯,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡佳魔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了晦炊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鞠鲜。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖刽锤,靈堂內(nèi)的尸體忽然破棺而出镊尺,到底是詐尸還是另有隱情朦佩,我是刑警寧澤并思,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站语稠,受9級特大地震影響宋彼,放射性物質(zhì)發(fā)生泄漏弄砍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一输涕、第九天 我趴在偏房一處隱蔽的房頂上張望音婶。 院中可真熱鬧,春花似錦莱坎、人聲如沸衣式。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碴卧。三九已至,卻和暖如春乃正,著一層夾襖步出監(jiān)牢的瞬間住册,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工瓮具, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留荧飞,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓名党,卻偏偏與公主長得像叹阔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子兑巾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容