OkHttp+Retrofit+Dagger2+RxJava+MVP架構 學習筆記

weixin_33913332發表於2019-02-24

一口吃不成一個大胖子,一步一步地講解各個框架特性及使用,再連線起來。

@[toc]

OkHttp

implementation 'com.squareup.okhttp3:okhttp:3.11.0'

Header的設定

  • 使用header(name,value)來設定HTTP頭的唯一值,如果請求中已經存在響應的資訊那麼直接替換掉。
  • 使用addHeader(name,value)來補充新值,如果請求頭中已經存在name的name-value,那麼還會繼續新增,請求頭中便會存在多個name相同而value不同的“鍵值對”。
  • 使用header(name)讀取唯一值或多個值的最後一個值
  • 使用headers(name)獲取所有值

GET & POST請求

private String post() throws IOException {
        MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
        String requestBody = "I am Renly.";
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(NetConfig.REQUEST_URL)
                .header("Token","TokenValue")
                .addHeader("HeaderName","HeaderValue")
                .post(RequestBody.create(mediaType, requestBody)) // 預設是GET請求,GET請求可以不寫
                .build();
//         同步請求
        Response response = client.newCall(request).execute();
//         非同步請求,此方法不適合該方法
//        Response response = client.newCall(request).enqueue(new Callback() {
//            @Override
//            public void onFailure(Call call, IOException e) {
//                Log.e(TAG,"getResponse fail " + e.getMessage());
//            }
//
//            @Override
//            public void onResponse(Call call, Response response) throws IOException {
//                Log.e(TAG,"getResponse success " + response.body().string());
//            }
//        });
        return response.body().string();
    }

攔截器-interceptor

OkHttp的攔截器鏈可謂是其整個框架的精髓,使用者可傳入的 interceptor 分為兩類:

  1. 一類是全域性的 interceptor,該類 interceptor 在整個攔截器鏈中最早被呼叫,通過 OkHttpClient.Builder#addInterceptor(Interceptor) 傳入;
  2. 另外一類是非網頁請求的 interceptor ,這類攔截器只會在非網頁請求中被呼叫,並且是在組裝完請求之後,真正發起網路請求前被呼叫,所有的 interceptor 被儲存在 List<Interceptor> interceptors 集合中,按照新增順序來逐個呼叫,具體可參考 RealCall#getResponseWithInterceptorChain() 方法。通過 OkHttpClient.Builder#addNetworkInterceptor(Interceptor) 傳入;

這裡舉一個簡單的例子,例如有這樣一個需求,我要監控App通過 OkHttp 發出的所有原始請求,以及整個請求所耗費的時間,針對這樣的需求就可以使用第一類全域性的 interceptor 在攔截器鏈頭去做。

OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .addInterceptor(new LoggingInterceptor())
        .build();
Request request = new Request.Builder()
        .url("http://www.publicobject.com/helloworld.txt")
        .header("User-Agent", "OkHttp Example")
        .build();
okHttpClient.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        Log.d(TAG, "onFailure: " + e.getMessage());
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        ResponseBody body = response.body();
        if (body != null) {
            Log.d(TAG, "onResponse: " + response.body().string());
            body.close();
        }
    }
});
public class LoggingInterceptor implements Interceptor {
    private static final String TAG = "LoggingInterceptor";
    
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        long startTime = System.nanoTime();
        Log.d(TAG, String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));

        Response response =  chain.proceed(request);

        long endTime = System.nanoTime();
        Log.d(TAG, String.format("Received response for %s in %.1fms%n%s",
                response.request().url(), (endTime - startTime) / 1e6d, response.headers()));

        return response;
    }
}
3631399-164b722ab35ae9bf.png
完整攔截器鏈

Retrofit

implementation 'com.squareup.retrofit2:retrofit:2.4.0'

Retrofit註解

  • 請求方法


    1724103-db95c51539b62c96.png
    請求方法
  • 請求引數


    1724103-073abf80aacf492e.png
    請求引數
  • 請求標記


    1724103-4d09b5595bfb3291.png
    請求標記

請求姿勢

1. 建立 接收伺服器返回資料 的類

public class Comment {
    private long id;
    private String body;
    private String user_id;
    ......
    }

2. 建立 用於描述網路請求 的介面

Retrofit將 Http請求 抽象成 Java介面:採用 註解 描述網路請求引數和配置網路請求引數

用 動態代理 動態 將該介面的註解“翻譯”成一個 Http 請求,最後再執行 Http 請求
注:介面中的每個方法的引數都需要使用註解標註,否則會報錯

public interface APi {
    // @GET註解的作用:採用Get方法傳送網路請求
    // "comments"用於在建立Retrofit例項時拼接網路請求的URL
    @GET("comments")
    Call<List<Comment>> getComments(@Header("Authorization") String authorization, @Query("page")int page);
    // getComments() = 接收網路請求資料的方法
    // 其中返回型別為Call<*>,*是接收資料的類(即上面定義的Comment類)

    @Headers("Authorization: authorization")
    @POST("login")
    Call<ResponseBody> doLogin(@Query("email") String email, @Query("password") String password);
    // 如果想直接獲得Responsebody中的內容,可以定義網路請求返回值為Call<ResponseBody>

    // Header:
    // 以上的效果是一致的。
    // 區別在於使用場景和使用方式
    // 1. 使用場景:@Header用於新增不固定的請求頭,@Headers用於新增固定的請求頭
    // 2. 使用方式:@Header作用於方法的引數;@Headers作用於方法
}

3. 建立 Retrofit的例項 並 發起請求

只演示post請求,get請求同理

private void TestRetrofit() {
        OkHttpClient client = new OkHttpClient.Builder().build();
        // 建立Retrofit例項
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(NetConfig.BASE_URL) //網路請求完整的URL = 通過例項化時.baseUrl()設定+網路請求介面的註解設定
                .addConverterFactory(GsonConverterFactory.create()) // 設定資料解析器
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支援RxJava平臺
                .client(client)
                .build();
        APi mApi = retrofit.create(APi.class);
        
        //傳送網路請求(非同步)
        retrofit2.Call<ResponseBody> call = mApi.doLogin("testUser@qq.com", "testPwd");
        call.enqueue(new retrofit2.Callback<ResponseBody>() {
            @Override
            public void onResponse(retrofit2.Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
                //請求成功時回撥
                Log.e("print","onResponse");
                try {
                    tv.setText(response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onFailure(retrofit2.Call<ResponseBody> call, Throwable t) {
                //請求失敗時候的回撥
                Log.e("print","onFailure " + t.getMessage());
            }
        });
        // 傳送網路請求(同步)
        Response<ResponseBody> response = call.execute();
    }

4. 關於資料解析器(Converter)

//設定資料解析器
.addConverterFactory(GsonConverterFactory.create())

這個有啥用?這句話的作用就是使得來自介面的json結果會自動解析成定義好了的欄位和型別都相符的json物件接受類。在Retrofit 2.0中,Package 中已經沒有Converter了,所以,你需要自己建立一個Converter, 不然的話Retrofit只能接收字串結果,你也只能拿到一串字元,剩下的json轉換的活還得你自己來幹。所以,如果你想接收json結果並自動轉換成解析好的接收類,必須自己建立Converter物件,然後使用addConverterFactory把它新增進來!
Retrofit支援多種資料解析方式,在使用時注意需要在Gradle新增依賴:

資料解析器 Gradle依賴
Gson com.squareup.retrofit2:converter-gson:2.0.2
Jackson com.squareup.retrofit2:converter-jackson:2.0.2
Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2
Moshi com.squareup.retrofit2:converter-moshi:2.0.2
Wire com.squareup.retrofit2:converter-wire:2.0.2
Scalars com.squareup.retrofit2:converter-scalars:2.0.2

5. 關於網路請求介面卡(CallAdapter)

//設定網路請求介面卡
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
網路請求介面卡 Gradle依賴
Guava com.squareup.retrofit2:adapter-guava:2.0.2
Java8 com.squareup.retrofit2:adapter-java8:2.0.2
RXJava com.squareup.retrofit2:adapter-rxjava:2.0.2

更多

注:addConverterFactory是有先後順序的,如果有多個ConverterFactory都支援(就是大神所謂的我可以處理)同一種型別,那麼就是隻有第一個才會被使用,而GsonConverterFactory是不判斷是否支援的(直接返回Converter)

附時序圖

6163786-03bc66bb37973344.png
時序圖

推薦閱讀:
這是一份很詳細的 Retrofit 2.0 使用教程(含例項講解)
Retrofit分析-漂亮的解耦套路

Retrofit Javadoc:Retrofit官方 API


RxJava

implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.3'

由於這部分知識過於龐大,自知實力沒法提煉出精髓就不班門弄斧了,這裡推薦幾篇文章,這些文章對我理解RxJava有很大的幫助,推薦結合看才能深入瞭解。
優美的非同步 --- RxAndroid
給 Android 開發者的 RxJava 詳解
RxJava原始碼分析與實戰 關於RxJava的專欄,適於深入瞭解(像是flatMap,retryWhen操作符)

接下來會開一個專案來寫OkHttp+Retrofit+RxJava


Dagger2

implementation 'com.google.dagger:dagger:2.19'
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'

關鍵的註解

@Inject
這個註解是用來說明該註解下方的屬性或方法需要依賴注入。(如果使用在類構造方法上,則該類也會被註冊在DI容器中作為注入物件。很重要,理解這個,就能理解Presenter注入到Activity的步驟!)

@Provider
在@Module註解的類中,使用@Provider註解,說明提供依賴注入的具體物件

@Component
簡單說就是,可以通過Component訪問到Module中提供的依賴注入物件。假設,如果有兩個Module,AModule、BModule,如果Component只註冊了AModule,而沒有註冊BModule,那麼BModule中提供的物件,無法進行依賴注入!

@SubComponent
該註解從名字上就能知道,它是子Component,會完全繼承父Component的所有依賴注入物件!

@Sigleton
被註解的物件,在App中是單例存在的!

@Scope
用來標註依賴注入物件的適用範圍。

@Named(@)
因為Dagger2 的以來注入是使用型別推斷的,所以同一型別的物件就無法區分,可以使用@Named註解區分同一型別物件,可以理解為物件的別名!


MVP架構

MVP是什麼

我們所說的mvp架構,是google開源的一個設計模式,目的是為了將程式碼更加優雅清晰的呈現出來,廢話也不多說,直接分析

M:M層,也就是我們在程式中經常出現的model層,他的功能就是處理資料,其他任務不由他來接手。
V:V層,我們的view層,也就是顯示資料的地方,我們在得到資料之後,把資料傳遞給view層,通過他來顯示資料。同時,view層的點選事件等處理會在這裡出現,但真正的資料處理不是在這裡,而是在model層中處理。
P:P層,也就是Presenter層,他是我們mvp架構中的中間人,通過p層的連線,讓我們可以是M層和V層進行通訊。M層在獲取到資料之後,把它交給P層,P層在交給View層,同樣,View層的點選事件等處理通過P層去通知M層,讓他去進行資料處理。

相關文章