Retrofit2 完全解析 探索與okhttp之間的關係

yangxi_001發表於2017-05-15

一、概述

之前寫了個okhttputils的工具類,然後有很多同學詢問這個工具類和retrofit什麼區別,於是上了下官網,發現其底層對網路的訪問預設也是基於okhttp,不過retrofit非常適合於restful url格式的請求,更多使用註解的方式提供功能。

既然這樣,我們本篇博文首先研究其所提供的常用的用法:

  • 一般的get、post請求
  • 動態url,動態引數設定,各種註解的使用
  • 上傳檔案(單檔案,多檔案上傳等)
  • 下載檔案等(這個不推薦retrofit去做,具體看下文)

此外,由於其內部提供了ConverterFactory用於對返回的requestBody進行轉化和特殊的requestBody的構造,所以本文也包含:

  • 如何自定義ConverterFactory

最後呢,因為其原始碼並不複雜,本文將對原始碼進行整體的介紹,即

  • retrofit 原始碼分析

ok,說這麼多,既然需要restful url,我只能撿起我那個半桶水的spring mvc 搭建一個服務端的小例子~~

最後本文使用版本:

compile 'com.squareup.retrofit2:retrofit:2.0.2'
  • 1
  • 1

主要是原始碼解析,自定義Converter.Factory等一些細節的探索。

恩,寫完後,發現本文很長,中途請沒事站起來走兩步。

retrofit2官網地址:https://github.com/square/retrofit/

二、retrofit 用法示例

(1)一般的get請求

retrofit在使用的過程中,需要定義一個介面物件,我們首先演示一個最簡單的get請求,介面如下所示:

public interface IUserBiz
{
    @GET("users")
    Call<List<User>> getUsers();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

可以看到有一個getUsers()方法,通過@GET註解標識為get請求,@GET中所填寫的value和baseUrl組成完整的路徑,baseUrl在構造retrofit物件時給出。

下面看如何通過retrofit完成上述的請求:

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://192.168.31.242:8080/springmvc_users/user/")
        .addConverterFactory(GsonConverterFactory.create())
        .build();
IUserBiz userBiz = retrofit.create(IUserBiz.class);
Call<List<User>> call = userBiz.getUsers();
        call.enqueue(new Callback<List<User>>()
        {
            @Override
            public void onResponse(Call<List<User>> call, Response<List<User>> response)
            {
                Log.e(TAG, "normalGet:" + response.body() + "");
            }

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

            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

依然是構造者模式,指定了baseUrlConverter.Factory,該物件通過名稱可以看出是用於物件轉化的,本例因為伺服器返回的是json格式的陣列,所以這裡設定了GsonConverterFactory完成物件的轉化。

ok,這裡可以看到很神奇,我們通過Retrofit.create就可以拿到我們定義的IUserBiz的例項,呼叫其方法即可拿到一個Call物件,通過call.enqueue即可完成非同步的請求。

具體retrofit怎麼得到我們介面的例項的,以及物件的返回結果是如何轉化的,我們後面具體分析。

這裡需要指出的是:

  1. 介面中的方法必須有返回值,且比如是Call<T>型別
  2. .addConverterFactory(GsonConverterFactory.create())這裡如果使用gson,需要額外匯入:

    compile 'com.squareup.retrofit2:converter-gson:2.0.2'
    

    當然除了gson以外,還提供了以下的選擇:

    Gson: com.squareup.retrofit2:converter-gson
    Jackson: com.squareup.retrofit2:converter-jackson
    Moshi: com.squareup.retrofit2:converter-moshi
    Protobuf: com.squareup.retrofit2:converter-protobuf
    Wire: com.squareup.retrofit2:converter-wire
    Simple XML: com.squareup.retrofit2:converter-simplexml
    Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
    

    當然也支援自定義,你可以選擇自己寫轉化器完成資料的轉化,這個後面將具體介紹。

  3. 既然call.enqueue是非同步的訪問資料,那麼同步的訪問方式為call.execute,這一點非常類似okhttp的API,實際上預設情況下內部也是通過okhttp3.Call實現。

那麼,通過這麼一個簡單的例子,應該對retrofit已經有了一個直觀的認識,下面看更多其支援的特性。

(2)動態的url訪問@PATH

文章開頭提過,retrofit非常適用於restful url的格式,那麼例如下面這樣的url:

//用於訪問zhy的資訊
http://192.168.1.102:8080/springmvc_users/user/zhy
//用於訪問lmj的資訊
http://192.168.1.102:8080/springmvc_users/user/lmj
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

即通過不同的username訪問不同使用者的資訊,返回資料為json字串。

那麼可以通過retrofit提供的@PATH註解非常方便的完成上述需求。

我們再定義一個方法:

public interface IUserBiz
{
    @GET("{username}")
    Call<User> getUser(@Path("username") String username);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

可以看到我們定義了一個getUser方法,方法接收一個username引數,並且我們的@GET註解中使用{username}宣告瞭訪問路徑,這裡你可以把{username}當做佔位符,而實際執行中會通過@PATH("username")所標註的引數進行替換。

那麼訪問的程式碼很類似:

//省略了retrofit的構建程式碼
Call<User> call = userBiz.getUser("zhy");
//Call<User> call = userBiz.getUser("lmj");
call.enqueue(new Callback<User>()
{

    @Override
    public void onResponse(Call<User> call, Response<User> response)
    {
        Log.e(TAG, "getUsePath:" + response.body());
    }

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

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

(3)查詢引數的設定@Query

看下面的url

http://baseurl/users?sortby=username
http://baseurl/users?sortby=id
  • 1
  • 2
  • 1
  • 2

即一般的傳參,我們可以通過@Query註解方便的完成,我們再次在介面中新增一個方法:

public interface IUserBiz
{
    @GET("users")
    Call<List<User>> getUsersBySort(@Query("sortby") String sort);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

訪問的程式碼,其實沒什麼寫的:

//省略retrofit的構建程式碼
Call<List<User>> call = userBiz.getUsersBySort("username");
//Call<List<User>> call = userBiz.getUsersBySort("id");
//省略call執行相關程式碼
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

ok,這樣我們就完成了引數的指定,當然相同的方式也適用於POST,只需要把註解修改為@POST即可。

對了,我能剛才學了@PATH,那麼會不會有這樣嘗試的衝動,對於剛才的需求,我們這麼寫:

 @GET("users?sortby={sortby}")
 Call<List<User>> getUsersBySort(@Path("sortby") String sort);
  • 1
  • 2
  • 1
  • 2

乍一看別說好像有點感覺,哈,實際上執行是不支援的~估計是@ Path的定位就是用於url的路徑而不是引數,對於引數還是選擇通過@Query來設定。

(4)POST請求體的方式向伺服器傳入json字串@Body

大家都清楚,我們app很多時候跟伺服器通訊,會選擇直接使用POST方式將json字串作為請求體傳送到伺服器,那麼我們看看這個需求使用retrofit該如何實現。

再次新增一個方法:

public interface IUserBiz
{
 @POST("add")
 Call<List<User>> addUser(@Body User user);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

提交的程式碼其實基本都是一致的:

//省略retrofit的構建程式碼
 Call<List<User>> call = userBiz.addUser(new User(1001, "jj", "123,", "jj123", "jj@qq.com"));
//省略call執行相關程式碼
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

ok,可以看到其實就是使用@Body這個註解標識我們的引數物件即可,那麼這裡需要考慮一個問題,retrofit是如何將user物件轉化為字串呢?下文將詳細解釋~

下面對應okhttp,還有兩種requestBody,一個是FormBody,一個是MultipartBody,前者以表單的方式傳遞簡單的鍵值對,後者以POST表單的方式上傳檔案可以攜帶引數,retrofit也二者也有對應的註解,下面繼續~

(5)表單的方式傳遞鍵值對@FormUrlEncoded

這裡我們模擬一個登入的方法,新增一個方法:

public interface IUserBiz
{
    @POST("login")
    @FormUrlEncoded
    Call<User> login(@Field("username") String username, @Field("password") String password);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

訪問的程式碼:

//省略retrofit的構建程式碼
Call<User> call = userBiz.login("zhy", "123");
//省略call執行相關程式碼
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

ok,看起來也很簡單,通過@POST指明url,新增FormUrlEncoded,然後通過@Field新增引數即可。

(6)單檔案上傳@Multipart

下面看一下單檔案上傳,依然是再次新增個方法:

public interface IUserBiz
{
    @Multipart
    @POST("register")
    Call<User> registerUser(@Part MultipartBody.Part photo, @Part("username") RequestBody username, @Part("password") RequestBody password);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

這裡@MultiPart的意思就是允許多個@Part了,我們這裡使用了3個@Part,第一個我們準備上傳個檔案,使用了MultipartBody.Part型別,其餘兩個均為簡單的鍵值對。

使用:

File file = new File(Environment.getExternalStorageDirectory(), "icon.png");
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file);
MultipartBody.Part photo = MultipartBody.Part.createFormData("photos", "icon.png", photoRequestBody);

Call<User> call = userBiz.registerUser(photo, RequestBody.create(null, "abc"), RequestBody.create(null, "123"));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

ok,這裡感覺略為麻煩。不過還是蠻好理解~~多個@Part,每個Part對應一個RequestBody。

這裡插個實驗過程,其實我最初對於檔案,也是嘗試的@Part RequestBody,因為@Part("key"),然後傳入一個代表檔案的RequestBody,我覺得更加容易理解,後來發現試驗無法成功,而且查了下issue,給出了一個很奇怪的解決方案,這裡可以參考:retrofit#1063

給出了一個類似如下的方案:

public interface ApiInterface {
        @Multipart
        @POST ("/api/Accounts/editaccount")
        Call<User> editUser (@Header("Authorization") String authorization, @Part("file\"; filename=\"pp.png") RequestBody file , @Part("FirstName") RequestBody fname, @Part("Id") RequestBody id);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

可以看到對於檔案的那個@Partvalue竟然寫了這麼多奇怪的東西,而且filename竟然硬編碼了~~這個不好吧,我上傳的檔名竟然不能動態指定。

為了檔名不會被寫死,所以給出了最上面的上傳單檔案的方法,ps:上面這個方案經測試也是可以上傳成功的。

恩,這個奇怪方案,為什麼這麼做可行,下文會給出非常詳細的解釋。

最後看下多檔案上傳~

(7)多檔案上傳@PartMap

再新增一個方法~~~

 public interface IUserBiz
 {
     @Multipart
     @POST("register")
      Call<User> registerUser(@PartMap Map<String, RequestBody> params,  @Part("password") RequestBody password);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

這裡使用了一個新的註解@PartMap,這個註解用於標識一個Map,Map的key為String型別,代表上傳的鍵值對的key(與伺服器接受的key對應),value即為RequestBody,有點類似@Part的封裝版本。

執行的程式碼:

File file = new File(Environment.getExternalStorageDirectory(), "messenger_01.png");
        RequestBody photo = RequestBody.create(MediaType.parse("image/png", file);
Map<String,RequestBody> photos = new HashMap<>();
photos.put("photos\"; filename=\"icon.png", photo);
photos.put("username",  RequestBody.create(null, "abc"));

Call<User> call = userBiz.registerUser(photos, RequestBody.create(null, "123"));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

可以看到,可以在Map中put進一個或多個檔案,鍵值對等,當然你也可以分開,單獨的鍵值對也可以使用@Part,這裡又看到設定檔案的時候,相對應的key很奇怪,例如上例"photos\"; filename=\"icon.png",前面的photos就是與伺服器對應的key,後面filename是伺服器得到的檔名,ok,引數雖然奇怪,但是也可以動態的設定檔名,不太影響使用~~

(8)下載檔案

這個其實我覺得直接使用okhttp就好了,使用retrofit去做這個事情真的有點瞎用的感覺~~

增加一個方法:

@GET("download")
Call<ResponseBody> downloadTest();
  • 1
  • 2
  • 1
  • 2

呼叫:

Call<ResponseBody> call = userBiz.downloadTest();
call.enqueue(new Callback<ResponseBody>()
{
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response)
    {
        InputStream is = response.body().byteStream();
        //save file
    }

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

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

可以看到可以返回ResponseBody,那麼很多事都能幹了~~

but,也看出這種方式下載感覺非常雞肋,並且onReponse回撥雖然在UI執行緒,但是你還是要處理io操作,也就是說你在這裡還要另外開執行緒操作,或者你可以考慮同步的方式下載。

最後還是建議使用okhttp去下載,例如使用okhttputils.

有人可能會問,使用okhttp,和使用retrofit會不會造成新建多個OkHttpClient物件呢,其實是可設定的,參考下文。

ok,上面就是一些常用的方法,當然還涉及到一些沒有介紹的註解,但是通過上面這麼多方法的介紹,再多一二個註解的使用方式,相信大家能夠解決。

三、配置OkHttpClient

這個需要簡單提一下,很多時候,比如你使用retrofit需要統一的log管理,給每個請求新增統一的header等,這些都應該通過okhttpclient去操作,比如addInterceptor

例如:

OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor()//log,統一的header等
{
    @Override
    public okhttp3.Response intercept(Chain chain) throws IOException
    {
        return null;
    }
}).build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

或許你需要更多的配置,你可以單獨寫一個OkhttpClient的單例生成類,在這個裡面完成你所需的所有的配置,然後將OkhttpClient例項通過方法公佈出來,設定給retrofit。

設定方式:

Retrofit retrofit = new Retrofit.Builder()
    .callFactory(OkHttpUtils.getClient())
    .build();
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

callFactory方法接受一個okhttp3.Call.Factory物件,OkHttpClient即為一個實現類。

四、retrofit 原始碼解析

ok,接下來我們隊retrofit的原始碼做簡單的分析,首先我們看retrofit如何為我們的介面實現例項;然後看整體的執行流程;最後再看詳細的細節;

(1)retrofit如何為我們的介面實現例項

通過上文的學習,我們發現使用retrofit需要去定義一個介面,然後可以通過呼叫retrofit.create(IUserBiz.class);方法,得到一個介面的例項,最後通過該例項執行我們的操作,那麼retrofit如何實現我們指定介面的例項呢?

其實原理是:動態代理。但是不要被動態代理這幾個詞嚇唬到,Java中已經提供了非常簡單的API幫助我們來實現動態代理。

看原始碼前先看一個例子:

public interface ITest
{
    @GET("/heiheihei")
    public void add(int a, int b);

}
public static void main(String[] args)
{
    ITest iTest = (ITest) Proxy.newProxyInstance(ITest.class.getClassLoader(), new Class<?>[]{ITest.class}, new InvocationHandler()
    {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            Integer a = (Integer) args[0];
            Integer b = (Integer) args[1];
            System.out.println("方法名:" + method.getName());
            System.out.println("引數:" + a + " , " + b);

            GET get = method.getAnnotation(GET.class);
            System.out.println("註解:" + get.value());
            return null;
        }
    });
    iTest.add(3, 5);
}
  • 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
  • 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

輸出結果為:

方法名:add
引數:3 , 5
註解:/heiheihei
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

可以看到我們通過Proxy.newProxyInstance產生的代理類,當呼叫介面的任何方法時,都會呼叫InvocationHandler#invoke方法,在這個方法中可以拿到傳入的引數,註解等。

試想,retrofit也可以通過同樣的方式,在invoke方法裡面,拿到所有的引數,註解資訊然後就可以去構造RequestBody,再去構建Request,得到Call物件封裝後返回。

ok,下面看retrofit#create的原始碼:

public <T> T create(final Class<T> service) {
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
            @Override 
            public Object invoke(Object proxy, Method method, Object... args) throws Throwable {
       });
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

哈,和上面對應。到這裡,你應該明白retrofit為我們介面生成例項物件並不神奇,僅僅是使用了Proxy這個類的API而已,然後在invoke方法裡面拿到足夠的資訊去構建最終返回的Call而已。

哈,其實真正的動態代理一般是有具體的實現類的,只是在這個類呼叫某個方法的前後去執行一些別的操作,比如開事務,打log等等。當然,本博文並不需要涉及這些詳細的內容,如果你希望詳細去了解,可以搜尋關鍵字:Proxy InvocationHandler

(2)retrofit整體實現流程

4.2.1 Retrofit的構建

這裡依然是通過構造者模式進行構建retrofit物件,好在其內部的成員變數比較少,我們直接看build()方法。

public Builder() {
    this(Platform.get());
}

public Retrofit build() {
  if (baseUrl == null) {
    throw new IllegalStateException("Base URL required.");
  }

  okhttp3.Call.Factory callFactory = this.callFactory;
  if (callFactory == null) {
    callFactory = new OkHttpClient();
  }

  Executor callbackExecutor = this.callbackExecutor;
  if (callbackExecutor == null) {
    callbackExecutor = platform.defaultCallbackExecutor();
  }

  // Make a defensive copy of the adapters and add the default Call adapter.
  List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
  adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

  // Make a defensive copy of the converters.
  List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

  return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
      callbackExecutor, validateEagerly);
}
  • 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
  • 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
  • baseUrl必須指定,這個是理所當然的;
  • 然後可以看到如果不著急設定callFactory,則預設直接new OkHttpClient(),可見如果你需要對okhttpclient進行詳細的設定,需要構建OkHttpClient物件,然後傳入;
  • 接下來是callbackExecutor,這個想一想大概是用來將回撥傳遞到UI執行緒了,當然這裡設計的比較巧妙,利用platform物件,對平臺進行判斷,判斷主要是利用Class.forName("")進行查詢,具體程式碼已經被放到文末,如果是Android平臺,會自定義一個Executor物件,並且利用Looper.getMainLooper()例項化一個handler物件,在Executor內部通過handler.post(runnable),ok,整理憑大腦應該能構思出來,暫不貼程式碼了。
  • 接下來是adapterFactories,這個物件主要用於對Call進行轉化,基本上不需要我們自己去自定義。
  • 最後是converterFactories,該物件用於轉化資料,例如將返回的responseBody轉化為物件等;當然不僅僅是針對返回的資料,還能用於一般備註解的引數的轉化例如@Body標識的物件做一些操作,後面遇到原始碼詳細再描述。

ok,總體就這幾個物件去構造retrofit,還算比較少的~~

4.2.2 具體Call構建流程

我們構造完成retrofit,就可以利用retrofit.create方法去構建介面的例項了,上面我們已經分析了這個環節利用了動態代理,而且我們也分析了具體的Call的構建流程在invoke方法中,下面看程式碼:

public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    //...
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
           @Override 
          public Object invoke(Object proxy, Method method, Object... args){
            //...
            ServiceMethod serviceMethod = loadServiceMethod(method);
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

主要也就三行程式碼,第一行是根據我們的method將其包裝成ServiceMethod,第二行是通過ServiceMethod和方法的引數構造retrofit2.OkHttpCall物件,第三行是通過serviceMethod.callAdapter.adapt()方法,將OkHttpCall進行代理包裝;

下面一個一個介紹:

  • ServiceMethod應該是最複雜的一個類了,包含了將一個method轉化為Call的所有的資訊。
#Retrofit class
ServiceMethod loadServiceMethod(Method method) {
    ServiceMethod result;
    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = new ServiceMethod.Builder(this, method).build();
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

#ServiceMethod
public ServiceMethod build() {
      callAdapter = createCallAdapter();
      responseType = callAdapter.responseType();
      if (responseType == Response.class || responseType == okhttp3.Response.class) {
        throw methodError("'"
            + Utils.getRawType(responseType).getName()
            + "' is not a valid response body type. Did you mean ResponseBody?");
      }
      responseConverter = createResponseConverter();

      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }


      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        Type parameterType = parameterTypes[p];
        if (Utils.hasUnresolvableType(parameterType)) {
          throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
              parameterType);
        }

        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
        if (parameterAnnotations == null) {
          throw parameterError(p, "No Retrofit annotation found.");
        }

        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }

      return new ServiceMethod<>(this);
    }
  • 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
  • 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

直接看build方法,首先拿到這個callAdapter最終拿到的是我們在構建retrofit裡面時adapterFactories時新增的,即為:new ExecutorCallbackCall<>(callbackExecutor, call),該ExecutorCallbackCall唯一做的事情就是將原本call的回撥轉發至UI執行緒。

接下來通過callAdapter.responseType()返回的是我們方法的實際型別,例如:Call<User>,則返回User型別,然後對該型別進行判斷。

接下來是createResponseConverter拿到responseConverter物件,其當然也是根據我們構建retrofit時,addConverterFactory新增的ConverterFactory物件來尋找一個合適的返回,尋找的依據主要看該converter能否處理你編寫方法的返回值型別,預設實現為BuiltInConverters,僅僅支援返回值的實際型別為ResponseBodyVoid,也就說明了預設情況下,是不支援Call<User>這類型別的。

接下來就是對註解進行解析了,主要是對方法上的註解進行解析,那麼可以拿到httpMethod以及初步的url(包含佔位符)。

後面是對方法中引數中的註解進行解析,這一步會拿到很多的ParameterHandler物件,該物件在toRequest()構造Request的時候呼叫其apply方法。

ok,這裡我們並沒有去一行一行檢視程式碼,其實意義也不太大,只要知道ServiceMethod主要用於將我們介面中的方法轉化為一個Request物件,於是根據我們的介面返回值確定了responseConverter,解析我們方法上的註解拿到初步的url,解析我們引數上的註解拿到構建RequestBody所需的各種資訊,最終呼叫toRequest的方法完成Request的構建。

  • 接下來看OkHttpCall的構建,建構函式僅僅是簡單的賦值
OkHttpCall(ServiceMethod<T> serviceMethod, Object[] args) {
    this.serviceMethod = serviceMethod;
    this.args = args;
  }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
  • 最後一步是serviceMethod.callAdapter.adapt(okHttpCall)

我們已經確定這個callAdapter是ExecutorCallAdapterFactory.get()對應程式碼為:

final class ExecutorCallAdapterFactory extends CallAdapter.Factory {
  final Executor callbackExecutor;

  ExecutorCallAdapterFactory(Executor callbackExecutor) {
    this.callbackExecutor = callbackExecutor;
  }

  @Override
  public CallAdapter<Call<?>> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }
    final Type responseType = Utils.getCallResponseType(returnType);
    return new CallAdapter<Call<?>>() {
      @Override public Type responseType() {
        return responseType;
      }

      @Override public <R> Call<R> adapt(Call<R> call) {
        return new ExecutorCallbackCall<>(callbackExecutor, call);
      }
    };
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

可以看到adapt返回的是ExecutorCallbackCall物件,繼續往下看:

static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate;

    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
      this.callbackExecutor = callbackExecutor;
      this.delegate = delegate;
    }

    @Override public void enqueue(final Callback<T> callback) {
      if (callback == null) throw new NullPointerException("callback == null");

      delegate.enqueue(new Callback<T>() {
        @Override public void onResponse(Call<T> call, final Response<T> response) {
          callbackExecutor.execute(new Runnable() {
            @Override public void run() {
              if (delegate.isCanceled()) {
                // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
                callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
              } else {
                callback.onResponse(ExecutorCallbackCall.this, response);
              }
            }
          });
        }

        @Override public void onFailure(Call<T> call, final Throwable t) {
          callbackExecutor.execute(new Runnable() {
            @Override public void run() {
              callback.onFailure(ExecutorCallbackCall.this, t);
            }
          });
        }
      });
    }
    @Override public Response<T> execute() throws IOException {
      return delegate.execute();
    }
  }
  • 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
  • 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

可以看出ExecutorCallbackCall僅僅是對Call物件進行封裝,類似裝飾者模式,只不過將其執行時的回撥通過callbackExecutor進行回撥到UI執行緒中去了。

4.2.3 執行Call

在4.2.2我們已經拿到了經過封裝的ExecutorCallbackCall型別的call物件,實際上就是我們實際在寫程式碼時拿到的call物件,那麼我們一般會執行enqueue方法,看看原始碼是怎麼做的

首先是ExecutorCallbackCall.enqueue方法,程式碼在4.2.2,可以看到除了將onResponse和onFailure回撥到UI執行緒,主要的操作還是delegate完成的,這個delegate實際上就是OkHttpCall物件,我們看它的enqueue方法

 @Override
public void enqueue(final Callback<T> callback)
{
    okhttp3.Call call;
    Throwable failure;

    synchronized (this)
    {
        if (executed) throw new IllegalStateException("Already executed.");
        executed = true;

        try
        {
            call = rawCall = createRawCall();
        } catch (Throwable t)
        {
            failure = creationFailure = t;
        }
    }

    if (failure != null)
    {
        callback.onFailure(this, failure);
        return;
    }

    if (canceled)
    {
        call.cancel();
    }

    call.enqueue(new okhttp3.Callback()
    {
        @Override
        public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
                throws IOException
        {
            Response<T> response;
            try
            {
                response = parseResponse(rawResponse);
            } catch (Throwable e)
            {
                callFailure(e);
                return;
            }
            callSuccess(response);
        }

        @Override
        public void onFailure(okhttp3.Call call, IOException e)
        {
            try
            {
                callback.onFailure(OkHttpCall.this, e);
            } catch (Throwable t)
            {
                t.printStackTrace();
            }
        }

        private void callFailure(Throwable e)
        {
            try
            {
                callback.onFailure(OkHttpCall.this, e);
            } catch (Throwable t)
            {
                t.printStackTrace();
            }
        }

        private void callSuccess(Response<T> response)
        {
            try
            {
                callback.onResponse(OkHttpCall.this, response);
            } catch (Throwable t)
            {
                t.printStackTrace();
            }
        }
    });
}
  • 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
  • 83
  • 84
  • 85
  • 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
  • 83
  • 84
  • 85

沒有任何神奇的地方,內部實際上就是okhttp的Call物件,也是呼叫okhttp3.Call.enqueue方法。

中間對於okhttp3.Call的建立程式碼為:

private okhttp3.Call createRawCall() throws IOException
{
    Request request = serviceMethod.toRequest(args);
    okhttp3.Call call = serviceMethod.callFactory.newCall(request);
    if (call == null)
    {
        throw new NullPointerException("Call.Factory returned null.");
    }
    return call;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

可以看到,通過serviceMethod.toRequest完成對request的構建,通過request去構造call物件,然後返回.

中間還涉及一個parseResponse方法,如果順利的話,執行的程式碼如下:

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException
{
    ResponseBody rawBody = rawResponse.body();
    ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);

    T body = serviceMethod.toResponse(catchingBody);
    return Response.success(body, rawResponse);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

通過serviceMethod對ResponseBody進行轉化,然後返回,轉化實際上就是通過responseConverter的convert方法。

#ServiceMethod
 T toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

ok,關於responseConverter後面還會細說,不用擔心。

到這裡,我們整個原始碼的流程分析就差不多了,目的就掌握一個大體的原理和執行流程,瞭解下幾個核心的類。

那麼總結一下:

  • 首先構造retrofit,幾個核心的引數呢,主要就是baseurl,callFactory(預設okhttpclient),converterFactories,adapterFactories,excallbackExecutor。
  • 然後通過create方法拿到介面的實現類,這裡利用Java的Proxy類完成動態代理的相關代理
  • 在invoke方法內部,拿到我們所宣告的註解以及實參等,構造ServiceMethod,ServiceMethod中解析了大量的資訊,最痛可以通過toRequest構造出okhttp3.Request物件。有了okhttp3.Request物件就可以很自然的構建出okhttp3.call,最後calladapter對Call進行裝飾返回。
  • 拿到Call就可以執行enqueue或者execute方法了

ok,瞭解這麼多足以。

下面呢,有幾個地方需要注意,一方面是一些特殊的細節;另一方面就是Converter

五、retrofit中的各類細節

(1)上傳檔案中使用的奇怪value值

第一個問題涉及到檔案上傳,還記得我們在單檔案上傳那裡所說的嗎?有種類似於hack的寫法,上傳檔案是這麼做的?

public interface ApiInterface {
        @Multipart
        @POST ("/api/Accounts/editaccount")
        Call<User> editUser (@Part("file_key\"; filename=\"pp.png"),@Part("username") String username);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

首先我們一點明確,因為這裡使用了@ Multipart,那麼我們認為@Part應當支援普通的key-value,以及檔案。

對於普通的key-value是沒問題的,只需要這樣@Part("username") String username

那麼對於檔案,為什麼需要這樣呢?@Part("file_key\"; filename=\"pp.png")

這個value設定的值不用看就會覺得特別奇怪,然而卻可以正常執行,原因是什麼呢?

原因是這樣的:

當上傳key-value的時候,實際上對應這樣的程式碼:

builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""),
                        RequestBody.create(null, params.get(key)));
  • 1
  • 2
  • 1
  • 2

也就是說,我們的@Part轉化為了

Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"")
  • 1
  • 1

這麼一看,很隨意,只要把key放進去就可以了。

但是,retrofit2並沒有對檔案做特殊處理,檔案的對應的字串應該是這樣的

 Headers.of("Content-Disposition", "form-data; name="filekey";filename="filename.png");
  • 1
  • 1

與鍵值對對應的字串相比,多了個;filename="filename.png,就因為retrofit沒有做特殊處理,所以你現在看這些hack的做法

@Part("file_key\"; filename=\"pp.png")
拼接:==>
Content-Disposition", "form-data; name=\"" + key + "\"
結果:==>
Content-Disposition", "form-data; name=file_key\"; filename=\"pp.png\"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

ok,到這裡我相信你已經理解了,為什麼要這麼做,而且為什麼這麼做可以成功!

恩,值得一提的事,因為這種方式檔名寫死了,我們上文使用的的是@Part MultipartBody.Part file,可以滿足檔名動態設定,這個方式貌似也是2.0.1的時候支援的。

上述相關的原始碼:

#ServiceMethod
if (annotation instanceof Part) {
    if (!isMultipart) {
      throw parameterError(p, "@Part parameters can only be used with multipart encoding.");
    }
    Part part = (Part) annotation;
    gotPart = true;

    String partName = part.value();

    Headers headers =
          Headers.of("Content-Disposition", "form-data; name=\"" + partName + "\"",
              "Content-Transfer-Encoding", part.encoding());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

可以看到呢,並沒有對檔案做特殊處理,估計下個版本說不定@Part會多個isFile=true|false屬性,甚至修改對應形參,然後在這裡做簡單的處理。

ok,最後來到關鍵的ConverterFactory了~

六、自定義Converter.Factory

(1)responseBodyConverter

關於Converter.Factory,肯定是通過addConverterFactory設定的

Retrofit retrofit = new Retrofit.Builder()
    .addConverterFactory(GsonConverterFactory.create())
        .build();
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

該方法接受的是一個Converter.Factory factory物件

該物件呢,是一個抽象類,內部包含3個方法:

abstract class Factory {

    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }


    public Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }


    public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

可以看到呢,3個方法都是空方法而不是抽象的方法,也就表明了我們可以選擇去實現其中的1個或多個方法,一般只需要關注requestBodyConverterresponseBodyConverter就可以了。

ok,我們先看如何自定義,最後再看GsonConverterFactory.create的原始碼。

先來個簡單的,實現responseBodyConverter方法,看這個名字很好理解,就是將responseBody進行轉化就可以了。

ok,這裡呢,我們先看一下上述中我們使用的介面:

package com.zhy.retrofittest.userBiz;

public interface IUserBiz
{
    @GET("users")
    Call<List<User>> getUsers();

    @POST("users")
    Call<List<User>> getUsersBySort(@Query("sort") String sort);

    @GET("{username}")
    Call<User> getUser(@Path("username") String username);

    @POST("add")
    Call<List<User>> addUser(@Body User user);

    @POST("login")
    @FormUrlEncoded
    Call<User> login(@Field("username") String username, @Field("password") String password);

    @Multipart
    @POST("register")
    Call<User> registerUser(@Part("photos") RequestBody photos, @Part("username") RequestBody username, @Part("password") RequestBody password);

    @Multipart
    @POST("register")
    Call<User> registerUser(@PartMap Map<String, RequestBody> params,  @Part("password") RequestBody password);

    @GET("download")
    Call<ResponseBody> downloadTest();

}
  • 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
  • 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

不知不覺,方法還蠻多的,假設哈,我們這裡去掉retrofit構造時的GsonConverterFactory.create,自己實現一個Converter.Factory來做資料的轉化工作。

首先我們解決responseBodyConverter,那麼程式碼很簡單,我們可以這麼寫:

public class UserConverterFactory extends Converter.Factory
{
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)
    {
        //根據type判斷是否是自己能處理的型別,不能的話,return null ,交給後面的Converter.Factory
        return new UserConverter(type);
    }

}

public class UserResponseConverter<T> implements Converter<ResponseBody, T>
{
    private Type type;
    Gson gson = new Gson();

    public UserResponseConverter(Type type)
    {
        this.type = type;
    }

    @Override
    public T convert(ResponseBody responseBody) throws IOException
    {
        String result = responseBody.string();
        T users = gson.fromJson(result, type);
        return users;
    }
}
  • 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
  • 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

使用的時候呢,可以

 Retrofit retrofit = new Retrofit.Builder()
.callFactory(new OkHttpClient())               .baseUrl("http://example/springmvc_users/user/")
//.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(new UserConverterFactory())
            .build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

ok,這樣的話,就可以完成我們的ReponseBodyList<User>或者User的轉化了。

可以看出,我們這裡用的依然是Gson,那麼有些同學肯定不希望使用Gson就能實現,如果不使用Gson的話,一般需要針對具體的返回型別,比如我們針對返回List<User>或者User

你可以這麼寫:

package com.zhy.retrofittest.converter;
/**
 * Created by zhy on 16/4/30.
 */
public class UserResponseConverter<T> implements Converter<ResponseBody, T>
{
    private Type type;
    Gson gson = new Gson();

    public UserResponseConverter(Type type)
    {
        this.type = type;
    }

    @Override
    public T convert(ResponseBody responseBody) throws IOException
    {
        String result = responseBody.string();

        if (result.startsWith("["))
        {
            return (T) parseUsers(result);
        } else
        {
            return (T) parseUser(result);
        }
    }

    private User parseUser(String result)
    {
        JSONObject jsonObject = null;
        try
        {
            jsonObject = new JSONObject(result);
            User u = new User();
            u.setUsername(jsonObject.getString("username"));
            return u;
        } catch (JSONException e)
        {
            e.printStackTrace();
        }
        return null;
    }

    private List<User> parseUsers(String result)
    {
        List<User> users = new ArrayList<>();
        try
        {
            JSONArray jsonArray = new JSONArray(result);
            User u = null;
            for (int i = 0; i < jsonArray.length(); i++)
            {
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                u = new User();
                u.setUsername(jsonObject.getString("username"));
                users.add(u);
            }
        } catch (JSONException e)
        {
            e.printStackTrace();
        }
        return users;
    }
}
  • 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
  • 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

這裡簡單讀取了一個屬性,大家肯定能看懂,這樣就能滿足返回值是Call<List<User>>或者Call<User>.

這裡鄭重提醒:如果你針對特定的型別去寫Converter,一定要在UserConverterFactory#responseBodyConverter中對型別進行檢查,發現不能處理的型別return null,這樣的話,可以交給後面的Converter.Factory處理,比如本例我們可以按照下列方式檢查:

public class UserConverterFactory extends Converter.Factory
{
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)
    {
        //根據type判斷是否是自己能處理的型別,不能的話,return null ,交給後面的Converter.Factory
        if (type == User.class)//支援返回值是User
        {
            return new UserResponseConverter(type);
        }

        if (type instanceof ParameterizedType)//支援返回值是List<User>
        {
            Type rawType = ((ParameterizedType) type).getRawType();
            Type actualType = ((ParameterizedType) type).getActualTypeArguments()[0];
            if (rawType == List.class && actualType == User.class)
            {
                return new UserResponseConverter(type);
            }
        }
        return null;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

好了,到這呢responseBodyConverter方法告一段落了,謹記就是將reponseBody->返回值返回中的實際型別,例如Call<User>中的User;還有對於該converter不能處理的型別一定要返回null。

(2)requestBodyConverter

ok,上面介面一大串方法呢,使用了我們的Converter之後,有個方法我們現在還是不支援的。

@POST("add")
Call<List<User>> addUser(@Body User user);
  • 1
  • 2
  • 1
  • 2

ok,這個@Body需要用到這個方法,叫做requestBodyConverter,根據引數轉化為RequestBody,下面看下我們如何提供支援。

public class UserRequestBodyConverter<T> implements Converter<T, RequestBody>
{
    private Gson mGson = new Gson();
    @Override
    public RequestBody convert(T value) throws IOException
    {
        String string = mGson.toJson(value);
        return RequestBody.create(MediaType.parse("application/json; charset=UTF-8"),string);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

然後在UserConverterFactory中複寫requestBodyConverter方法,返回即可:

public class UserConverterFactory extends Converter.Factory
{

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit)
    {
        return new UserRequestBodyConverter<>();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

這裡偷了個懶,使用Gson將物件轉化為json字串了,如果你不喜歡使用框架,你可以選擇拼接字串,或者反射寫一個支援任何物件的,反正就是物件->json字串的轉化。最後構造一個RequestBody返回即可。

ok,到這裡,我相信如果你看的細緻,自定義Converter.Factory是幹嘛的,但是我還是要總結下:

  • responseBodyConverter 主要是對應@Body註解,完成ResponseBody到實際的返回型別的轉化,這個型別對應Call<XXX>裡面的泛型XXX,其實@Part等註解也會需要responseBodyConverter,只不過我們的引數型別都是RequestBody,由預設的converter處理了。
  • requestBodyConverter 完成物件到RequestBody的構造。
  • 一定要注意,檢查type如果不是自己能處理的型別,記得return null (因為可以新增多個,你不能處理return null ,還會去遍歷後面的converter).

七、值得學習的API

其實一般情況下看原始碼呢,可以讓我們更好的去使用這個庫,當然在看的過程中如果發現了一些比較好的處理方式呢,是非常值得記錄的。如果每次看別人的原始碼都能吸取一定的精華,比你單純的去理解會好很多,因為你的記憶力再好,原始碼解析你也是會忘的,而你記錄下來並能夠使用的優越的程式碼,可能用久了就成為你的程式碼了。

我舉個例子:比如retrofit2中判斷當前執行的環境程式碼如下,如果下次你有這樣的需求,你也可以這麼寫,甚至原始碼中根據不同的執行環境還提供了不同的Executor都很值得記錄:

class Platform {
  private static final Platform PLATFORM = findPlatform();

  static Platform get() {
    return PLATFORM;
  }

  private static Platform findPlatform() {
    try {
      Class.forName("android.os.Build");
      if (Build.VERSION.SDK_INT != 0) {
        return new Android();
      }
    } catch (ClassNotFoundException ignored) {
    }
    try {
      Class.forName("java.util.Optional");
      return new Java8();
    } catch (ClassNotFoundException ignored) {
    }
    try {
      Class.forName("org.robovm.apple.foundation.NSObject");
      return new IOS();
    } catch (ClassNotFoundException ignored) {
    }
    return new Platform();
  }
  • 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
  • 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

相關文章