1. 程式人生 > >Android:Okhttp+Retrofit+Rx+RxLificycler+Fragmenation框架搭建自己的技術堆疊(一)

Android:Okhttp+Retrofit+Rx+RxLificycler+Fragmenation框架搭建自己的技術堆疊(一)

            自己獨立開發一個新專案。從技術選型到框架構建,再到具體的程式碼編輯,單元測試,全部由一個人負責。(說白了,就是把你扔那裡,看你能弄出什麼么蛾子)。特此,在這裡記錄自己的開發過程。

        在沒看到產品需求和設計之前,自己先確定大概的專案框架和技術選型。

  •       MVP的設計模式,最大程度上解耦Activity和業務邏輯的關係;
  •       OkHttp+Retrofit 的網路訪問方案;
  •       RxJava+RxAndroid+RxLifeCycler實現執行緒控制的方案   
  •       Fragmenation 實現Fragment的管理;
  •       EventBus實現元件間通訊 

    因為是小型專案,所以以上的技術選型基本上可以滿足需求。現將各個部分的實現簡單記錄一下。今天主要記錄網路訪問這一塊。

        網路訪問這一塊基本上是對OkHttp+Retrofit+Rx的一些封裝以及對網路訪問錯誤的統一化處理。先看看我的專案分包和設計到的網路訪問的分包吧。


        專案分包分的比較隨便,基本上從命名上可以理解

  1.             base:    專案中的基類,,如BaseActivity,BaseFragment等
  2.             bean:    bean類,各種專案中bean物件的封裝;
  3.             configs:    專案中的常量和引數(全域性性);
  4.             http:    專案中的網路訪問部分;
  5.             ui:    介面部分。裡面是按照模組分的,每個模組又按照mvp模式繼續分包;
  6.             utils:    專案中涉及到的工具類;
  7.             widget    專案中涉及到的自定義View;

          其中http下的分包為:

  •     api:Retrofitde 需要的網路介面和api的幫助類(用於實現多個api的管理);
  •     exception:涉及到異常的統一化處理。
  •     function:自定義的實現Rx 中Function介面的類;
  •     intercepter:Okhttp的攔截器;
  •     listener:網路狀態的回撥;
  •     retrofit:Retrofit的封裝和幫助類;
  •     rx:實現對Rx網路訪問的封裝;

       1.OkHttp+Retrofit的簡易封裝:

        封裝OkHttp

        該方法返回一個okhttp的例項:該例項實現了日誌列印,快取處理,以及自定義訊息頭。這三者的實現都跟okhttp的攔截器有關;
    // 獲取http例項;
    private OkHttpClient okHttpClient(){

        // 快取目錄;
        File cacheFile = new File(BaseApplication.getContext().getCacheDir(), HTTP_CASH_FILE_DIR);

        //  logging 網路攔截
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        // 例項化client
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(NET_CONNECTED_TIME_OUT, TimeUnit.SECONDS)
                .readTimeout(NET_READ_TIME_OUT,TimeUnit.SECONDS)
                .writeTimeout(NET_WRITE_TIME_OUT,TimeUnit.SECONDS)
                .cache(new Cache(cacheFile,HTTP_CASH_SIZE))
                .addInterceptor(httpLoggingInterceptor)         // 日誌
                .addInterceptor(new HeadsInterceptor())         // 自定義的訊息頭
                .addInterceptor(new CacheInterceptor())         // 快取
                .build();

        return client;
    }

       日誌列印:HttpLogingInterceptor已經幫我們封裝的很好了,我們我們只要在我們的gradle檔案中宣告一下就可以很方便的使用了。

        cache快取的話,我們需要自定義一個快取目錄,(一般在應用快取目錄或者是系統內建儲存的快取目錄,當然你也可以自己定義),定義完後,在Okhttp中設定就可以了。當然做完這些是不完美的。因為這些僅僅是自己一廂情願,你還要在你的收到的響應裡面去改幾個引數。於是CacheInterceptor就應運而生了

public class CacheInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        //get the origin response;
        Response originResponse = chain.proceed(chain.request());
        String cacheSetting  = originResponse.cacheControl().toString();
        // if server can not support cache ,then cacheSetting is null or empty;
        if(TextUtils.isEmpty(cacheSetting)){
            cacheSetting = "public,max-age=60";
        }
        // return the rebuild response;
        return originResponse.newBuilder()
                .addHeader("Cache-Control",cacheSetting)
                .removeHeader("Pragma")
                .build();
    }
}

        最後,自定義訊息頭也是經常性用到的。在使用者註冊或者登陸後,為了辨別使用者身份,後端基本上都會要求我們在網路請求中帶上token。如果涉及到加密和解密的話,也會叫我們帶上一些公鑰。這些都是通過網路攔截器實現的。

public class HeadsInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        SPUtils sp = SPUtils.getInstance();

        String deviceId = DeviceUtils.getDeviceId();
        String token = sp.getString(CommonConfigs.KEY_TOKEN, null);
        String secretKey = sp.getString(CommonConfigs.KEY_SECRET_KEY, null);

        Request.Builder builder = chain.request().newBuilder();

        // add head;
      if(BaseApplication.getLoginState()){
          builder.addHeader(CommonConfigs.KEY_DEVICE_ID,deviceId)
                  .addHeader(CommonConfigs.KEY_TOKEN,token)
                  .addHeader(CommonConfigs.KEY_SECRET_KEY,secretKey);
      }else {
          builder.addHeader(CommonConfigs.KEY_DEVICE_ID,deviceId);
      }
        return chain.proceed(builder.build());
    }
}
        這裡,使用者登入後,後端希望我的每個訪問都能帶上使用者token secretKey和deviceId;

        封裝Retrofit

        Retrofit是個非常優秀的網路框架。這裡我加入了Json解析和Rx2的功能。
public Retrofit retrofit(){
        return  new Retrofit.Builder()
                .baseUrl(HTTP_BASE_URL)
                .client(okHttpClient())
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

    }

        封裝RetrofitHelper

        RetrofitHelper全域性的單利,通過它,可以獲得Retrofit的客戶端。
    private static RetrofitHelper mHelper;

    // 私有化構建函式;
    private RetrofitHelper(){

    }

    // 對外提供獲取該例項的方法;
    public static  RetrofitHelper getHelper(){
        if(mHelper == null){
            synchronized (RetrofitHelper.class){
                if(mHelper == null){
                    mHelper = new RetrofitHelper();
                }
            }
        }
        return mHelper;
    }

        ApiHelper管理Api分類

        ApiHelper主要是使用者管理Retrofit的api介面分類。因為很多情況,網路訪問介面我都會以模組來分類,一個模組對應一個api介面類。這樣做不僅可以便於我們做網路介面的單元測試,同時也方便我們管理和查詢我們的介面。
public class ApiHelper {
    private static LotteryApi mLotteryApi;

    public static LotteryApi getLotteryApi(){
        if(mLotteryApi == null){
            RetrofitHelper.getHelper().retrofit().create(LotteryApi.class);
        }
        return mLotteryApi;
    }



}
        當然,因為保密問題,只羅列出了一個介面。

    2.異常的統一化處理

        對於異常的話,這裡可以分為兩個大類:        客戶端異常:                客戶端異常就是指在非正常的網路訪問,或者在網路訪問過程中對資料的錯誤處理觸發的,可以在客戶端捕獲的異常或錯誤。具體的例子如,連線超時,讀寫超時,Json解析錯誤等。這些異常的發生都可以使得客戶端程式崩潰,或者異常中斷,以至於無法獲得我們需要的資料。但是這類資料通常我們可以在程式碼中捕獲。
        服務端異常:                服務端的異常,說白了就是後端與我們的約定的異常資訊。這類資訊往往不會觸發程式異常奔潰,網路互動功能可以正常進行。但是往往我們無法獲取我們期望的資料。資料往往為空。最常見例子就是:我們往往與後端約定返回碼200為網路訪問成功,非200則為某種異常,這種異常資訊往往由後端自己定義。如下圖            當這類異常發生的時候,我們的程式中是正常執行的,不丟擲異常則無法使我們的程式中斷,也就無法對此做相應的UI處理。當然,也許會有人說,跟以前一樣,在拿資料時候先判斷返回碼。這是一種方式。但是,這種方式有兩個弊端。一因為涉及的網路訪問太多,每次網路訪問都這樣寫,程式碼無法複用(如果自己封裝一套當然也是可以的),二則是無法實現服務端異常與客戶端異常的統一化處理,用這種方式處理服務端異常,往往是在我們從後端拿到資料後,而我們拿到資料後的操作往往是應該配合UI的賦值操作,如果這時候還要去做一系列的異常處理,至少在程式碼風格上顯得不太優雅。所以,將所有的異常進行統一的處理是顯得很有必要的。最後,我希望的結果是,在一次網路訪問中,只有兩種結果,成功與失敗。成功可以獲取資料,失敗知道失敗的原因。

        2.1定義網路回撥介面

public interface OnHttpCallback<T> {

    // 成功的回撥;
    void onSucceed();

    // 失敗的回撥;
    void onFailed(ApiException exception);
}
        該介面只有兩個方法,成功和失敗。這裡使用了泛型。成功的方法中返回成功後的資料。失敗的方法中返回失敗的異常物件。ApiException這個類是我自定義的異常類,是我們統一的異常類,它只有兩個屬性,異常碼,和異常宣告,具體如下;
public class ApiException extends Exception {
    private String msg;
    private int code;
    public ApiException(Throwable throwable, int code){
        super(throwable);
        this.code = code;
    }

    public ApiException(String msg, int code){
        this.msg = msg;
        this.code = code;
    }

    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 class ServerException extends RuntimeException {
    private String msg;
    private int code;
    public ServerException(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;
    }

}

        2.2 異常的統一轉化。

        這裡自定義了一個工具類,用於將所有的異常統一轉化為ApiException:
public class ExceptionEngine {
    public static final int UN_KNOW_ERROR = 10000;
    public static final int ANALYTIC_SERVER_DATA_ERROR = 10001;
    public static final int ANALYTIC_CLIENT_DATA_ERROR = 10002;
    public static final int NET_CONNECT_TIME_OUT = 10003;
    public static final int NET_CONNECT_ERROR = 10004;

    // 異常統一化處理;
    public static ApiException handleException(Throwable throwable){
        ApiException apiException = null;
        if(throwable instanceof HttpException){
            HttpException httpException = (HttpException) throwable;
            apiException = new ApiException(throwable,httpException.code());
            apiException.setMsg("網路錯誤");
        }else if (throwable instanceof ServerException){
            ServerException serverException = (ServerException) throwable;
            apiException = new ApiException(serverException.getMsg(),serverException.getCode());
        }else if(throwable instanceof JSONException || throwable instanceof JsonParseException
                || throwable instanceof ParseException || throwable instanceof MalformedJsonException){
            apiException = new ApiException(throwable,ANALYTIC_SERVER_DATA_ERROR);
            apiException.setMsg("解析錯誤");
        }else if(throwable instanceof ConnectException){
            apiException = new ApiException(throwable,NET_CONNECT_ERROR);
            apiException.setMsg("連線失敗");
        }else if(throwable instanceof SocketTimeoutException){
            apiException = new ApiException(throwable,NET_CONNECT_TIME_OUT);
            apiException.setMsg("連線超時");
        }else {
            apiException = new ApiException(throwable,UN_KNOW_ERROR);
            apiException.setMsg("未知錯誤");
        }
        return apiException;
    }

        2.3:配合Rx框架

        因為使用的RxAndroid+RxJava+RxLifeCycler框架。所以處理時機需要的選擇很重要。在這裡我的處理邏輯是如下:我們從服務端獲取的資料格式一般是包含狀態碼,狀態資訊,和返回資料。
{"code":0, "message":"登入成功",”data”:{“token”:3434…….,”secretKey”:ewadwadwad… ,”data”:{……}}}
        資料層級上而言,一般情況下,我們需要的資料是data物件。而code和message屬性則是我們判斷是否發生了服務端異常的標準。一般我們在網路訪問成功後,都需要剔除出有用的資料,這個操作一般在Rx 的map方法中進行操作。我們就在map的過程中判斷是否存在服務端異常,如果存在則丟擲異常。先看看對於服務端資料的封裝物件;
public class HttpResponse<T> {
    private int code;
    private String message;
    private T data;

    public HttpResponse(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    public T getData() {
        return data;
    }

    public boolean isSuccessful(){
        if(code == CommonConfigs.HTTP_RESPONSE_SUCCEED_CODE){
            return true;
        }
        return false;
    }

}
    在Rx的map中,我們如果沒有異常,則返回data中資料,如果發生異常,則丟擲一個ServerException,於是,我們可以自定義一個Function;
public class    MapFunction<T> implements Function<HttpResponse<T>,T> {
    @Override
    public T apply(HttpResponse<T> tHttpResponse) throws Exception {
        //  如果響應碼不是約定好的,則丟擲伺服器異常;
        if(!tHttpResponse.isSuccessful()){
           throw new ServerException(tHttpResponse.getCode(),tHttpResponse.getMessage());
        }
        // 過濾出有用資料;
        return tHttpResponse.getData();
    }
}
    在丟擲服務端異常後,接下來是對異常的統一轉化,這裡我是在Rx的onErrorResumeNext()方法中進行的,該方法的作用是Rx在丟擲錯誤後,停止原先Observerable的資料分發,重新訂閱onErrorResumeNext()方法中的被觀察者,說白了,就是原先的被觀察者丟擲異常後,會停止對其訂閱,轉而重新訂閱該方法中的被觀察者。該方法的內部也是一個function介面;
public class ErrorFunction<T> implements Function<Throwable,Observable<T>> {
    @Override
    public Observable<T> apply(Throwable throwable) throws Exception {
        return Observable.error(ExceptionEngine.handleException(throwable));
    }
}
    直接異常統一轉化。簡單粗暴。
        轉化後的異常在哪裡去處理呢?當然是在Oberver物件中了,這裡,我自定義了一個Oberver物件。主要是實現將網路訪問的結果傳遞。這裡的介面是指第一步定義的介面。
public class RxObserver<T> implements Observer<T> {
    OnHttpCallback<T> mCallback;
    Disposable mDisposable;

    public OnHttpCallback<T> getmCallback() {
        return mCallback;
    }

    public  RxObserver (OnHttpCallback callback){
        this.mCallback = callback;
    }
    @Override
    public void onSubscribe(Disposable d) {
        mDisposable = d;
    }

    @Override
    public void onNext(T t) {
        if(mCallback!=null){
            mCallback.onSucceed();
        }
    }

    @Override
    public void onError(Throwable e) {
        ApiException exception = null;
        if(!(e instanceof ApiException)){
            exception = ExceptionEngine.handleException(e);
        }
        if(mCallback!=null){
            mCallback.onFailed(exception);
        }
    }

    @Override
    public void onComplete() {

    }

    // dispose;
    public void cancle(){
        if(mDisposable != null){
            mDisposable.dispose();
            mDisposable = null;
        }
    }

}
        程式碼太簡單,沒什麼好講的。        同時,在Observerable中,封裝了異常的統一化處理方案,如下
public class RxObservable<T>{
    Observable<HttpResponse<T>> observable;
    public RxObservable(Observable<HttpResponse<T>> observable){
        this.observable = observable;
    }

    public Observable<T> init(){
        return observable.map(new MapFunction<T>())
                .onErrorResumeNext(new ErrorFunction<T>());
    }
}
        這裡原來是計劃採用裝飾者模式的。但是怕實現過程中有些東西遺漏掉,就放棄了。
        最後,自己封裝了一個網路訪問的工具類,該工具類將Rx生命週期的回收也加入了進去;
public class RxHelper {


    //  網路互動,並且繫結生命週期;
    public static void subscribe(@NonNull Observable observable, @NonNull ObservableTransformer
            transformer, @NonNull OnHttpCallback callback) {
        new RxObservable<>(observable).init()
                .compose(transformer)
                .subscribeOn(Schedulers.newThread())
                .unsubscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new RxObserver(callback));
    }

    // 網路互動,但是不繫結生命週期;
    public static void subscribe(@NonNull Observable observable, @NonNull OnHttpCallback callback) {
        new RxObservable<>(observable).init()
                .subscribeOn(Schedulers.newThread())
                .unsubscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new RxObserver(callback));
    }

    // 獲取被觀察者;
    public static Observable observable(@NonNull Observable observable){
        return new RxObservable<>(observable).init();
    }

    // 獲取觀察者;
    public static Observer observer(@NonNull OnHttpCallback callback){
        return new RxObserver(callback);
    }

        最後,當我們需要進行網路訪問的時候,只要呼叫RxHelper就可以了。        當然,這裡的內容有一部分設計到了MVP,這裡後面會將。
        總結,進行這樣封裝後,網路訪問一句搞定。


相關推薦

Android:Okhttp+Retrofit+Rx+RxLificycler+Fragmenation框架搭建自己技術堆疊

            自己獨立開發一個新專案。從技術選型到框架構建,再到具體的程式碼編輯,單元測試,全部由一個人負責。(說白了,就是把你扔那裡,看你能弄出什麼么蛾子)。特此,在這裡記錄自己的開發過程。        在沒看到產品需求和設計之前,自己先確定大概的專案框架和技術

Flask框架的學習與實戰:開發環境搭建

進行 read 模型 clas tar pychar html itl .html Flask是一個使用 Python 編寫的輕量級 Web 應用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎則使用 Jinja2。很多功能的實現都參考了django框架。由於項

vue從入門到女裝??:從零開始搭建後臺管理系統安裝框架

安裝及執行都是基於node的,不會node的可以自行百度,網上教程很多,也不難 專案效果預覽: demo1 demo2 原始碼下載 開始安裝框架: vue element-ui   注意如果報錯安裝失敗就重新安裝,不然雖然本地有element的依賴包但是可能會出一些奇怪的錯誤 另外element-ui

SSM框架開發web專案系列 環境搭建

前言   開發環境:Eclipse Mars + Maven + JDK 1.7 + Tomcat 7 + MySQL   主要框架:Spring + Spring MVC + Mybatis   目的:快速上手搭建SSM開發環境,熟悉客戶端請求到資料庫操作的流程。

NET使用ABP框架搭建部落格專案 使用者模組

首先我們先來設計下使用者模組,簡單使用者模組設計涵蓋兩個類,使用者表以及角色表。如下圖: 我們在Core專案中增加一個資料夾Domain,在Domain增加Customers資料夾,新增一個列舉PasswordFormat並且繼承Int。 /// <

詳細講解Android的圖片下載框架UniversialImageLoader之磁碟快取

    沉浸在Android的開發世界中有一些年頭的猴子們,估計都能夠深深的體會到Android中的圖片下載、展示、快取一直是心中抹不去的痛。鄙人亦是如此。Ok,閒話不說,為了督促自己的學習,下面就逐一的挖掘Android中還算是比較牛叉的圖片處理框架Universial

python框架之 Tornado 學習筆記

tornado pythontornado 一個簡單的服務器的例子:首先,我們需要安裝 tornado ,安裝比較簡單: pip install tornado 測試安裝是否成功,可以打開python 終端,輸入: import tornado.https

IDEA搭建maven項目

ext web項目 mage 技術 png images http 點擊 項目 在IntelliJ IDEA中配置maven 打開-File-Settings 新建maven WEB項目 打開-File-New-Project 點擊NEXT

如何搭建一個web網站

團隊合作 是的 轉換 們的 web服務 ons lang 用戶 域名 前言: 由於新生軍訓結束,作為學生會的一個技術部的老油條,這時候得幫幫他們了。 大多數新生都是奔著能做一些小東西,能夠被大家,被其他人用,為目的進入了技術部,部門主要負責做院系微信運營,順帶做開發。前兩任

從零開始利用vue-cli搭建簡單音樂網站

路徑 nod .com mman csdn desc blog -a where 最近在學習vue框架,練習了一些例子之後,想著搭建一個vue項目,了解到官方有提供一個vue-cli工具來搭建項目腳手架,嘗試了一下,寫下博客來記錄一下。 一、工具環境 1、node.js 6

【SSH框架】之Struts2系列

核心 mapping 調度 fault code 組件 -i -c params 微信公眾號:compassblog 歡迎關註、轉發,互相學習,共同進步! 有任何問題,請後臺留言聯系 1、Struts2框架概述 (1)、什麽是Struts2 Struts2是一種基於M

【SSH框架】之Spring系列

oca getc per 名稱 寫入 xmla java開發 無需 不能 微信公眾號:compassblog 歡迎關註、轉發,互相學習,共同進步! 有任何問題,請後臺留言聯系! 1、前言 前面更新過幾篇關於 Struts2 框架和 Hibernate 框架的文章,但鑒於

react搭建後臺管理系統

管理系 for menu port 文件中 segment 後臺管理 ans create 先準備工具:  yarn安裝:    npm install -g yarn #yarn也是包管理工具,只不過它構建效率更高    官方使用教程:https://yarnpkg.

作業流 oozie調度框架的配置與使用

大數據 hadoop oozie 調度框架 一: 常見的調度框架 一: oozie 概述與功能 二: oozie 安裝與配置 一: 常見的作用調度框架 1.1 linux 下面的計劃任務 在工作量比較下的情況下 使用linux 下的crond 使用定制計劃任務 * * *

集合框架相關接口概述

ren array 方法 klist link map trees nth 框架 List VS Set List 是有序的可重復的, Set 是無序的不可重復的。 ArrayList VS LinkList ArrayList : 底層實現是數組,所以易查詢難存儲,

windows環境下搭建Java開發環境:jdk安裝和配置

變量 win jns jdk安裝 分享 tool 直接 www. 技術 一、資源下載   官網:http://www.oracle.com/technetwork/java/javase/downloads/index.html   本人安裝的是jdk1.8,百度雲資源:鏈

環境的搭建--PWN入門系列

python2.7 com 是我 analysis strong nal 64bit amd ... 前言:作為一個偏前端開發的小白,入門信息安全也有兩年了。這兩年來一直被各種表哥血虐...... 最近暑假終於有時間專心水群了,這一個系列筆記是我這半個月來的pwn水題總結

函數計算搭建 Serverless Web 應用- HTTP 觸發器

選擇 文件 process 程序 例如 函數計算 -o 同時 時代 摘要: Web 應用(Serverless web backend) 是函數計算很重要的一個使用場景。相比於傳統的在服務器上搭建 web 應用,函數計算無需您管理服務器等基礎設施,只需編寫代碼並上傳,函數計

2018 - Python 3.7 爬蟲之 Scrapy 框架的安裝及配置

一,安裝 Python3.7 二,安裝 pip 三,安裝 pywin32 四,安裝 pyOpenSSL 五,安裝 lxml 六,安裝 zope.interface 七,安裝 twisted 八,安裝 Scrapy 九,一鍵升級所有庫,Python 3.7親測可用,建立

QT框架下的文字操作

簡介 檔案操作作為軟體開發中不可或缺的一環,將其獨立出來並形成一個模組就顯得十分必要。這樣不僅易於維護管理,而且易於在專案中整合。 文字型別 常用的文字型別包括: csv檔案 dbf 檔案 excel 檔案 ini 檔案 json 檔案