1. 程式人生 > >從0開始搭建rxjava+retrofit+mvp+dagger2整合基礎框架(rxjava+retrofit網路層搭建)

從0開始搭建rxjava+retrofit+mvp+dagger2整合基礎框架(rxjava+retrofit網路層搭建)

古語有云,沒有規矩,就不成方圓。其實做什麼事都一樣,做什麼事都要有自己熟悉且大家都認同的一套規矩,這樣既能提高自己的做事效率,也方便他人的理解。

在移動開發中,mvp是新興的一種軟體開發模式,是經過時間的考驗並且大家都認同的解耦框架。它不僅能讓我們的程式碼邏輯更加清晰,不同層間分工不同又相互協作,服務於我們的專案。mode層負責本地資料和網路資料的處理,presenter層負責業務邏輯的處理,view層負責ui介面的展示。減少了view層與model層的直接互動,而是通過presenter實現中間代理的互動邏輯。

rxjava+retrofit是最近很火的網路層框架,兩者完美協作客戶端與伺服器的資料互動,並且rxjava是響應式程式設計代表,在retrofit提供網路服務的時候方便的切換處理執行緒,大大方便了客戶端網路層的開發。

dagger2是android端的依賴注入框架,做過ssh的同學可能不會陌生,有了依賴注入的思想,不用我們自己通過new的方式去建立物件了,而是通過注入,將物件託管權交出來由容器統一管理,而當我們需要的時候直接從容器中去取(如果用傳統的方式通過new的方式建立例項物件,當類建構函式或內部發生改變後,每個new的地方都需要去改變,工作量可見之大。而通過依賴注入的方式管理後,只需要很小的開銷就能實現)。

磨刀不誤砍柴工,在對rxjava+retrofit+mvp+dagger2進行了簡單的瞭解之後,開始我們的基層框架搭建吧。

既然這是一個大量資訊互動的時代,我們就從網路層框架開始搭建吧。實現rxjava+retrofit的網路層服務。

首先看一下最後的框架結構:

http層結構

在gradle中需要新增如下依賴:

   compile 'io.reactivex:rxjava:1.1.0'
    compile 'io.reactivex:rxandroid:1.1.0'
    compile 'com.squareup.retrofit2:retrofit:2.0.2'
    compile 'com.squareup.retrofit2:converter-gson:2.0.2'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
    compile 'com.squareup.okhttp3:logging-interceptor:3.3.0'
compile 'com.github.d-max:spots-dialog:0.7@aar'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

接下來我們一次介紹每層的功能和實現原理:api層是進行網路請求的介面封裝層,可以將網路請求的藉口和引數等放入commonapi中進行統一管理,當我們通過retrofit創建出commonapi例項後,就可以對所有網路請求進行訪問(當然也可以根據自己的需求和愛好進行分類)。看一下我的commonapi簡單實現:根據page路徑引數獲取當前頁的ip列表(get註解後為相對地址路徑,baseurl在建立retrofit時指定)

public interface CommonApi {

    @GET("allip/{page}")
    Observable<TableIp> userLogin(@Path("page")int page);
}
  • 1
  • 2
  • 3
  • 4
  • 5

接下來是exception包下面的內容,它主要處理網路請求過程中的異常。 
(1)ApiError類是處理資料請求異常(如請求地址,引數等錯誤)時,對錯誤資訊的封裝。它根據伺服器返回的不同的errorbody而異,本例中包含code和displayMessage兩個欄位,如下:

  /*錯誤碼*/
    private int code;
    /*顯示的資訊*/
    private String displayMessage;

    public ApiError(Throwable e) {
        super(e);
    }

    public ApiError(Throwable cause,@CodeException.CodeEp int code, String showMsg) {
        super(showMsg, cause);
        setCode(code);
        setDisplayMessage(showMsg);
    }

//    @CodeException.CodeEp
    public int getCode() {
        return code;
    }
//@CodeException.CodeEp
    public void setCode( int code) {
        this.code = code;
    }

    public String getDisplayMessage() {
        return displayMessage;
    }

    public void setDisplayMessage(String displayMessage) {
        this.displayMessage = displayMessage;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

(2)CodeException是請求失敗時不同的errorcode(錯誤碼)。

(3)HttpTimeException是對專案出現runtimeexception(執行時異常)時資料的封裝,方便向用戶展示錯誤資訊,如下所示:

 /*未知錯誤*/
    public static final int UNKOWN_ERROR = 0x1002;
    /*本地無快取錯誤*/
    public static final int NO_CHACHE_ERROR = 0x1003;
    /*快取過時錯誤*/
    public static final int CHACHE_TIMEOUT_ERROR = 0x1004;


    public HttpTimeException(int resultCode) {
        this(getApiExceptionMessage(resultCode));
    }

    public HttpTimeException(String detailMessage) {
        super(detailMessage);
    }

    /**
     * 轉換錯誤資料
     *
     * @param code
     * @return
     */
    private static String getApiExceptionMessage(int code) {
        switch (code) {
            case UNKOWN_ERROR:
                return "錯誤:網路錯誤";
            case NO_CHACHE_ERROR:
                return "錯誤:無快取資料";
            case CHACHE_TIMEOUT_ERROR:
                return "錯誤:快取資料過期";
            default:
                return "錯誤:未知錯誤";
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

(4)rertyWhenNetWorkError是網路異常時的重連機制,它主要負責在第一次網路訪問失敗後,間歇性的重新連線伺服器,保障服務的正常使用,定義如下:

  /* retry次數*/
    private int count = 3;
    /*延遲*/
    private long delay = 3000;
    /*疊加延遲*/
    private long increaseDelay = 3000;

    public rertyWhenNetWorkError() {

    }

    public rertyWhenNetWorkError(int count, long delay) {
        this.count = count;
        this.delay = delay;
    }

    public rertyWhenNetWorkError(int count, long delay, long increaseDelay) {
        this.count = count;
        this.delay = delay;
        this.increaseDelay = increaseDelay;
    }

    @Override
    public Observable<?> call(Observable<? extends Throwable> observable) {
        return observable
                .zipWith(Observable.range(1, count + 1), new Func2<Throwable, Integer, Wrapper>() {
                    @Override
                    public Wrapper call(Throwable throwable, Integer integer) {
                        return new Wrapper(throwable, integer);
                    }
                }).flatMap(new Func1<Wrapper, Observable<?>>() {
                    @Override
                    public Observable<?> call(Wrapper wrapper) {
                        if ((wrapper.throwable instanceof ConnectException
                                || wrapper.throwable instanceof SocketTimeoutException
                                || wrapper.throwable instanceof TimeoutException)
                                && wrapper.index < count + 1) { //如果超出重試次數也丟擲錯誤,否則預設是會進入onCompleted
                            return Observable.timer(delay + (wrapper.index - 1) * increaseDelay, TimeUnit.MILLISECONDS);

                        }
                        return Observable.error(wrapper.throwable);
                    }
                });
    }

    private class Wrapper {
        private int index;
        private Throwable throwable;

        public Wrapper(Throwable throwable, int index) {
            this.index = index;
            this.throwable = throwable;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

以上時對retrofit進行網路請求時可能出現的異常進行的總結與處理,主要是捕獲到異常,進行統一的封裝處理,方便後面統一的向用戶展示異常資訊。

介紹完一場處理機制後,接下來就該主角(工廠類)出場了,也就是factory包下的內容,包括retrofit工廠,異常處理工廠和json轉換工廠類,接下來我們一次介紹用途和實現。

(1)ApiErrorAwareConverterFactory是對json轉換錯誤時進行處理的工廠類,它的主要實現方法是responseBodyConverter方法。它通過EmptyJsonLenientConverterFactory從retrofit中獲取json資料轉換成指定Type型別,轉換失敗就反回null,成功我們就能拿到指定型別的資料了:

 @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        final Converter<ResponseBody, ?> delegateConverter =
                mEmptyJsonLenientConverterFactory.responseBodyConverter(type, annotations,
                        retrofit);
        return new Converter<ResponseBody, Object>() {
            @Override
            public Object convert(ResponseBody value) throws IOException {
                ResponseBody clone = RealResponseBody.create(value.contentType(), value.contentLength(),
                        value.source().buffer().clone());
                if (delegateConverter != null)
                    return delegateConverter.convert(value);

                return null;
            }
        };
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

(2)接下來是上面提到的EmptyJsonLenientConverterFactory工廠類,它通過GsonConverterFactory嘗試將獲取的資料進行型別轉換,主要是對空json字串進行過濾處理,與上面的工廠類協同對空json和異常json進行統一管理。

(3)FactoryException工廠是對所有異常統一轉換成apierror型別,方便對異常資訊的統一管理和展示。

(4)接下來是網路訪問最重要的RetrofitFactory工廠,它負責生成retrofit物件,通過retrofit建立comminapi,提供給我們訪問網路的介面,主要實現如下:

 private static volatile RetrofitFactory mInstanse=null;

    public CommonApi mApi;

    public RetrofitFactory() {
        mApi = provideHotApi();
    }

    private OkHttpClient provideOkHttpClient() {
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .readTimeout(Constants.HTTP_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
                .connectTimeout(Constants.HTTP_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
                .addInterceptor(loggingInterceptor)
                .addInterceptor(new AuthInterceptor())
                .addInterceptor(new ResponseInterceptor())
                .build();

        return okHttpClient;
    }

    public CommonApi provideHotApi() {
        Retrofit retrofit1 = new Retrofit.Builder()
                .baseUrl(Constants.Base_Url)
                .client(provideOkHttpClient())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(new ApiErrorAwareConverterFactory(new EmptyJsonLenientConverterFactory(GsonConverterFactory.create())))
                //.addConverterFactory(GsonConverterFactory.create())
                .build();
      return retrofit1.create(CommonApi.class);
    }

    public static RetrofitFactory getmInstanse() {
        if(mInstanse==null){
            synchronized (RetrofitFactory.class){
                if(mInstanse==null){
                    mInstanse = new RetrofitFactory();
                }
            }
        }
        return mInstanse;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

由於retrofit內部預設使用okhttp進行網路訪問,而且把okhttp的管理許可權交給了開發者,因此在使用它的時候,我們可以自己根據需求定製okhttp,然後交給retrofit進行網路請求。在栗子中我們使用到了HttpLoggingInterceptor

 HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
  • 1
  • 2

是為了方便網路請求的日誌的輸出,提高我們除錯和開發的效率。

同時我們給okhttp設定了連線超時引數,請求超時引數,請求攔截器(AuthInterceptor)和響應攔截器(ResponseInterceptor),它們的實現和用途會在接下來進一步說明。

接下來就是建立retrofit物件:

 Retrofit retrofit1 = new Retrofit.Builder()
                .baseUrl(Constants.Base_Url)
                .client(provideOkHttpClient())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(new ApiErrorAwareConverterFactory(new EmptyJsonLenientConverterFactory(GsonConverterFactory.create())))
                //.addConverterFactory(GsonConverterFactory.create())
                .build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在建立Retrofit時,指定訪問主機地址(baseurl)引數,同時將我們封裝好的okhttp和各種工廠類新增進去,就能獲取到Retrofit例項物件。最後就能由Retrofit創建出CommonApi物件,供我們進行網路訪問了。

由於上面用到了請求攔截器(AuthInterceptor)和響應攔截器(ResponseInterceptor),實際上有種spring中面向切面的意思,即對每次發器網路請求前和響應成功後的統一處理,一旦配置了它,每次請求都將經過它們的攔截處理。AuthInterceptor常用於在發器請求前向header頭部寫入引數或修改引數型別,ResponseInterceptor通常用於對請求成功後的header資料進行統一儲存或處理。如修改header中content-type:

 @Override
    public Response intercept(Chain chain) throws IOException {
        Request origin = chain.request();
        //原始請求header
        Headers originHeaders = origin.headers();

        //建立需要的請求header
        Headers.Builder newHeaders = new Headers.Builder();

        //向請求頭寫入Content-Type引數,設定引數擱置為application/json
        newHeaders.add("Content-Type", "application/json;charset=UTF-8");
        Request.Builder newRequest = origin.newBuilder()
                .headers(newHeaders.build());
        return chain.proceed(newRequest.build());
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

ResponseInterceptor儲存成功響應後header中的cookies值:

 /*
    此interceptor用於處理每次請求中header中資料問題
    本例用於獲取header中Set-Cookie引數值
    cookie未更新,導致登出登入時,再次登入還以原始使用者
     */

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        //判斷請求header中是否有page引數,且值為Login,可以通過在登入的介面上通過header註解新增page引數,用於判斷在登入成功後獲取cookie值
//        如@Headers({"Auth-Type:Basic", "Page:Login"})
//        @POST("api/login")
//        Observable<BaseDownBean> userLogin(@Body RequestBody requestBody);

//        if (request.header("Page") != null && TextUtils.equals(request.header("Page"), "Login")) {
//            if (!TextUtils.isEmpty(response.header("Set-Cookie"))) {
        //儲存登入成功後的cookie值
//                ShareFeranceUtil.getmInstance().putString(App.getAppContext(), Constants.Set_Cookie, response.header("Set-Cookie"));
//            }
//        }
        return response;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

到這裡retrofit相關知識就差不多結束了,可以直接從retrofit工廠類拿到CommonApi獲取資料,但是得將CommonApi方法的返回值換為Call的範型型別,如下:

    @GET("allip/{page}")
    Call<TableIp> getip(@Path("page")int page);
  • 1
  • 2

在需要的地方呼叫如下程式碼即可實現對響應成功和失敗的資料進行處理了:

 RetrofitFactory.getmInstanse().mApi.getip(1).enqueue(new Callback<TableIp>() {
            @Override
            public void onResponse(Call<TableIp> call, Response<TableIp> response) {

            }

            @Override
            public void onFailure(Call<TableIp> call, Throwable t) {

            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

但是這並不是我們的終極目標,我們需要的是幹練,簡單,清晰的處理邏輯。這時就該rxjava出場了,rxjava完美支援retrofit,大大簡化了我們對網路請求時的邏輯處理,並且可快速在網路訪問的子執行緒和ui執行緒間切換。要使用也很簡單:

將CommonApi方法的返回值換為Observable的範型型別,如下:

@GET("allip/{page}")
    Observable<TableIp> userLogin(@Path("page")int page);
  • 1
  • 2

這樣我們進行網路訪問時反回的型別就變成Observable型別了,這時我們需要在subscribe()方法中用一個Subscriber對資料進行處理。Subscriber用於對整個響應的生命週期進行管理,其中主要包含onstart,onNext,onCompleted,onError方法,對每一次開始響應到響應結束的整個週期進行管理。

onstart:在請求前執行的方法,由於是網路請求,經常需要向用戶展示載入對話方塊,表示請求正在進行。因此可以在onstart中統一顯示對話方塊。

onNext:在網路請求成功後對成功響應體的處理。

onError:在網路請求出現異常時統一在這個方法中對異常進行處理。

onCompleted:一次響應結束時執行的方法,經常在方法內關閉對話方塊,對前面使用的資源關閉等。

由於每個函式功能明確,我們經常也只需要對請求成功的資料進行處理,對異常可以進行統一處理,因此我們可以對Subscriber進行簡單封裝,對onstart,onError,onCompleted進行統一處理,只暴露出onNext方法每次進行不同的處理就ok了。因此就出現了下面的介面設計:

public interface onRequestListener<T> {

    void onSuccess(T t);

    void onHttpError(ApiError error);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

每次只需實現onSuccess方法對響應成功資料進行處理就好了,有時可能也需要拿到錯誤資訊,因此在這裡給出了onHttpError方法。介面主要還是根據自己的需求去設計。

最後就是我們封裝的Subscriber類了,它實現了onRequestListener介面,負責在每次請求前顯示載入礦,請求結束後讓載入框消失,並且對請求時的異常統一在errorDo方法中處理,實現如下:

public abstract class HttpObserver<T> extends Subscriber<T> implements onRequestListener<T> {
    //    弱引用防止記憶體洩露
    private WeakReference<Context> mActivity;
    //    載入框可自己定義
    private SpotsDialog mDialog = null;

    public HttpObserver(WeakReference<Context> mActivity) {
        this.mActivity = mActivity;
        initProgressDialog();
    }

    @Override
    public void onStart() {
        showProgressDialog();
    }

    @Override
    public void onCompleted() {
        dismissProgressDialog();
    }

    @Override
    public void onError(Throwable e) {
        /*需要快取並且本地有快取才返回*/
        errorDo(e);
        dismissProgressDialog();
    }

    @Override
    public void onHttpError(ApiError error) {
        //這裡可以做錯誤處理,也可以在呼叫的時候重寫error方法,對錯誤資訊進行處理
        ApiError error1 = FactoryException.analysisExcetpion(error);
        ToastUtil.showToastLong(mActivity.get(),error.getDisplayMessage());
    }

    /**
     * 錯誤統一處理
     *
     * @param e
     */
    private void errorDo(Throwable e) {
        Context context = mActivity.get();
        //if (context == null) return;
        if (e instanceof ApiError) {
            onError((ApiError) e);
        } else if (e instanceof HttpTimeException) {
            HttpTimeException exception=(HttpTimeException)e;
            onError(new ApiError(exception, CodeException.RUNTIME_ERROR,exception.getMessage()));
        } else {
            onError(new ApiError(e, CodeException.UNKNOWN_ERROR,e.getMessage()));
        }
        /*可以在這裡統一處理錯誤處理-可自由擴充套件*/

    }


    @Override
    public void onNext(T t) {
        onSuccess(t);
    }

    private void initProgressDialog() {
        Context context = mActivity.get();
        if (mDialog == null && context != null) {
            mDialog = new SpotsDialog(context,"正在載入中...");
        }
    }

    private void showProgressDialog() {
        Context context = mActivity.get();
        if (mDialog == null || context == null) return;
        if (!mDialog.isShowing()) {
            mDialog.show();
        }
    }

    private void dismissProgressDialog() {
        if (mDialog != null && mDialog.isShowing()) {
            mDialog.dismiss();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

最後SchedulersCompat類主要是網路請求時的執行緒管理,讓請求時在子執行緒,處理結果是回到ui執行緒。實現如下:

 private final static Observable.Transformer ioTransformer =new Observable.Transformer() {
        @Override
        public Object call(Object o) {
          return  ((Observable)o).subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread());
        }
    };

    public static <T> Observable.Transformer<T, T> applyIoSchedulers() {
        return (Observable.Transformer<T, T>) ioTransformer;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

至此,我們retrofit+rxjava進行網路請求的框架就完成了。在進行網路訪問時的程式碼就更加簡單清晰了,只需要實現onSuccess方法對結果處理即可:

RetrofitFactory.getmInstanse().mApi.userLogin(1).subscribe(new HttpObserver<TableIp>() {
            @Override
            public void onSuccess(TableIp tableIp) {

            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

以後每次要訪問網路時,都只需要這麼簡單的一