Retrofit 使用及源碼解析

使用 Retrofit 引入

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

retrofit 使用示例

retrofit 在使用時吞鸭,需要定義一個接口對象

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

1. 一般的 get 請求

上述接口中酌呆,可以看到一個 getUsers() 方法,通過 @GET 注解標識為 get 請求吊圾,@GET 中所填寫的 value 和 baseUrl 組成完整的路徑蝶念,baseUrl 在構(gòu)造 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)
            {

            }
        });

依然是構(gòu)造者模式堤尾,指定了 baseUrl 和 Converter.Factory肝劲,該對象通過名稱可以看出是用于對象轉(zhuǎn)化的,本例因為服務(wù)器返回的是json格式的數(shù)組郭宝,所以這里設(shè)置了 GsonConverterFactory 完成對象的轉(zhuǎn)化辞槐。

ok,這里可以看到很神奇粘室,我們通過 Retrofit.create 就可以拿到我們定義的 IUserBiz 的實例榄檬,調(diào)用其方法即可拿到一個 Call 對象,通過 call.enqueue 即可完成異步的請求衔统。

具體 retrofit 怎么得到我們接口的實例的丙号,以及對象的返回結(jié)果是如何轉(zhuǎn)化的,我們后面具體分析缰冤。

這里需要指出的是:

接口中的方法必須有返回值犬缨,且比如是 Call<T> 類型
.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

既然 call.enqueue 是異步的訪問數(shù)據(jù)怀薛,那么同步的訪問方式為 call.execute,這一點非常類似 okhttp 的 API迷郑,實際上默認情況下內(nèi)部也是通過 okhttp3.Call 實現(xiàn)枝恋。

2. 動態(tài)的 url 訪問 @PATH

如下的 url

//用于訪問zhy的信息
http://192.168.1.102:8080/springmvc_users/user/zhy
//用于訪問lmj的信息
http://192.168.1.102:8080/springmvc_users/user/lmj

即通過不同的 username 訪問不同用戶的信息创倔,返回數(shù)據(jù)為 json 字符串。

那么可以通過 retrofit 提供的 @PATH 注解非常方便的完成上述需求焚碌。

我們再定義一個方法:

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

可以看到我們定義了一個 getUser 方法畦攘,方法接收一個 username 參數(shù),并且我們的 @GET 注解中使用 {username} 聲明了訪問路徑十电,這里你可以把 {username} 當做占位符知押,而實際運行中會通過 @PATH("username") 所標注的參數(shù)進行替換。

那么訪問的代碼很類似:

//省略了retrofit的構(gòu)建代碼
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)
    {

    }
});

3. 查詢參數(shù)的設(shè)置 @Query

看下面的 url

http://baseurl/users?sortby=username
http://baseurl/users?sortby=id

即一般的傳參鹃骂,我們可以通過@Query注解方便的完成台盯,我們再次在接口中添加一個方法:

public interface IUserBiz
{
    @GET("users")
    Call<List<User>> getUsersBySort(@Query("sortby") String sort);
}

訪問的代碼

//省略retrofit的構(gòu)建代碼
Call<List<User>> call = userBiz.getUsersBySort("username");
//Call<List<User>> call = userBiz.getUsersBySort("id");
//省略call執(zhí)行相關(guān)代碼

ok,這樣我們就完成了參數(shù)的指定畏线,當然相同的方式也適用于 POST静盅,只需要把注解修改為 @POST 即可。

4. POST 請求體的方式向服務(wù)器傳入 json 字符串 @Body

大家都清楚寝殴,我們 app 很多時候跟服務(wù)器通信蒿叠,會選擇直接使用 POST 方式將 json 字符串作為請求體發(fā)送到服務(wù)器,那么我們看看這個需求使用 retrofit 該如何實現(xiàn)蚣常。

public interface IUserBiz
{
 @POST("add")
 Call<List<User>> addUser(@Body User user);
}

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

//省略retrofit的構(gòu)建代碼
 Call<List<User>> call = userBiz.addUser(new User(1001, "jj", "123,", "jj123", "jj@qq.com"));
//省略call執(zhí)行相關(guān)代碼

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

模擬一下登錄的方法

public interface IUserBiz
{
    @POST("login")
    @FormUrlEncoded
    Call<User> login(@Field("username") String username, @Field("password") String password);
}

訪問的代碼

//省略retrofit的構(gòu)建代碼
Call<User> call = userBiz.login("zhy", "123");
//省略call執(zhí)行相關(guān)代碼

看起來也很簡單市咽,通過 @POST 指明 url,添加 FormUrlEncoded史隆,然后通過 @Field 添加參數(shù)即可。

6. 單文件上傳 @Multipart

下面看下單文件上傳

public interface IUserBiz
{
    @Multipart
    @POST("register")
    Call<User> registerUser(@Part MultipartBody.Part photo, @Part("username") RequestBody username, @Part("password") RequestBody password);
}

這里 @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"));

這里感覺略為麻煩。不過還是蠻好理解~~多個 @Part豺裆,每個 Part 對應(yīng)一個 RequestBody拒秘。

7. 多文件上傳 @PartMap

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

這里使用了一個新的注解 @PartMap,這個注解用于標識一個 Map臭猜,Map 的 key 為 String 類型躺酒,代表上傳的鍵值對的 key (與服務(wù)器接受的 key 對應(yīng)),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"));

可以看到羹应,可以在 Map 中 put 進一個或多個文件,鍵值對等次屠,當然你也可以分開园匹,單獨的鍵值對也可以使用 @Part雳刺,這里又看到設(shè)置文件的時候,相對應(yīng)的 key 很奇怪裸违,例如上例 "photos"; filename="icon.png", 前面的 photos 就是與服務(wù)器對應(yīng)的 key掖桦,后面 filename 是服務(wù)器得到的文件名,ok供汛,參數(shù)雖然奇怪枪汪,但是也可以動態(tài)的設(shè)置文件名,不太影響使用~~

8. 下載文件

下載直接使用okhttp就好了紊馏,這邊簡單介紹下

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

調(diào)用:

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)
    {

    }
});

可以看到可以返回 ResponseBody料饥,那么很多事都能干了~~

but,也看出這種方式下載感覺非常雞肋朱监,并且 onReponse 回調(diào)雖然在UI線程岸啡,但是你還是要處理 io 操作,也就是說你在這里還要另外開線程操作赫编,或者你可以考慮同步的方式下載巡蘸。

配置 OkHttpClient

這個需要簡單提一下,很多時候擂送,比如你使用 retrofit 需要統(tǒng)一的 log 管理悦荒,給每個請求添加統(tǒng)一的 header 等,這些都應(yīng)該通過 okhttpclient 去操作嘹吨,比如 addInterceptor

例:

OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor()//log搬味,統(tǒng)一的header等
{
    @Override
    public okhttp3.Response intercept(Chain chain) throws IOException
    {
        return null;
    }
}).build();

或許你需要更多的配置,你可以單獨寫一個 OkhttpClient 的單例生成類蟀拷,在這個里面完成你所需的所有的配置碰纬,然后將 OkhttpClient 實例通過方法公布出來,設(shè)置給 retrofit问芬。

設(shè)置方式:

Retrofit retrofit = new Retrofit.Builder()
    .callFactory(OkHttpUtils.getClient())
    .build();

callFactory方法接受一個okhttp3.Call.Factory對象悦析,OkHttpClient即為一個實現(xiàn)類。

retrofit 源碼解析

1. retrofit 如何為我們的接口實現(xiàn)實例

通過上文的學習此衅,我們發(fā)現(xiàn)使用 retrofit 需要去定義一個接口强戴,然后可以通過調(diào)用 retrofit.create(IUserBiz.class); 方法,得到一個接口的實例挡鞍,最后通過該實例執(zhí)行我們的操作骑歹,那么 retrofit 如何實現(xiàn)我們指定接口的實例呢?

其實原理是:動態(tài)代理墨微。但是不要被動態(tài)代理這幾個詞嚇唬到陵刹,Java 中已經(jīng)提供了非常簡單的 API 幫助我們來實現(xiàn)動態(tài)代理。

看源碼前先看一個例子:

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("參數(shù):" + a + " , " + b);

            GET get = method.getAnnotation(GET.class);
            System.out.println("注解:" + get.value());
            return null;
        }
    });
    iTest.add(3, 5);
}

輸出結(jié)果為:

方法名:add
參數(shù):3 , 5
注解:/heiheihei

可以看到我們通過 Proxy.newProxyInstance 產(chǎn)生的代理類,當調(diào)用接口的任何方法時衰琐,都會調(diào)用 InvocationHandler#invoke 方法也糊,在這個方法中可以拿到傳入的參數(shù),注解等羡宙。

試想狸剃,retrofit 也可以通過同樣的方式,在 invoke 方法里面狗热,拿到所有的參數(shù)钞馁,注解信息然后就可以去構(gòu)造 RequestBody,再去構(gòu)建 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 {
       });
  }

哈熟丸,和上面對應(yīng)训措。到這里,你應(yīng)該明白 retrofit 為我們接口生成實例對象并不神奇光羞,僅僅是使用了 Proxy 這個類的 API 而已绩鸣,然后在 invoke 方法里面拿到足夠的信息去構(gòu)建最終返回的 Call 而已。

哈纱兑,其實真正的動態(tài)代理一般是有具體的實現(xiàn)類的呀闻,只是在這個類調(diào)用某個方法的前后去執(zhí)行一些別的操作,比如開事務(wù)潜慎,打 log 等等捡多。當然,本博文并不需要涉及這些詳細的內(nèi)容铐炫,如果你希望詳細去了解垒手,可以搜索關(guān)鍵字: Proxy InvocationHandler。

2. retrofit 整體實現(xiàn)流程

4.2.1 Retrofit 的構(gòu)建

這里依然是通過構(gòu)造者模式進行構(gòu)建 retrofit 對象驳遵,好在其內(nèi)部的成員變量比較少淫奔,我們直接看 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);
}
  • baseUrl 必須指定堤结,這個是理所當然的;
  • 然后可以看到如果不著急設(shè)置 callFactory鸭丛,則默認直接 new OkHttpClient()竞穷,可見如果你需要對 okhttpclient 進行詳細的設(shè)置,需要構(gòu)建 OkHttpClient 對象鳞溉,然后傳入瘾带;
  • 接下來是 callbackExecutor,這個想一想大概是用來將回調(diào)傳遞到 UI 線程了熟菲,當然這里設(shè)計的比較巧妙看政,利用 platform 對象朴恳,對平臺進行判斷,判斷主要是利用 Class.forName("") 進行查找允蚣,具體代碼已經(jīng)被放到文末于颖,如果是 Android 平臺,會自定義一個 Executor 對象嚷兔,并且利用 Looper.getMainLooper() 實例化一個 handler 對象森渐,在 Executor 內(nèi)部通過 handler.post(runnable),ok冒晰,整理憑大腦應(yīng)該能構(gòu)思出來同衣,暫不貼代碼了。
  • 接下來是 adapterFactories壶运,這個對象主要用于對 Call 進行轉(zhuǎn)化耐齐,基本上不需要我們自己去自定義。
  • 最后是 converterFactories前弯,該對象用于轉(zhuǎn)化數(shù)據(jù)蚪缀,例如將返回的 responseBody 轉(zhuǎn)化為對象等;當然不僅僅是針對返回的數(shù)據(jù)恕出,還能用于一般備注解的參數(shù)的轉(zhuǎn)化例如 @Body 標識的對象做一些操作询枚,后面遇到源碼詳細再描述。

4.2.2 具體Call構(gòu)建流程

我們構(gòu)造完成 retrofit浙巫,就可以利用 retrofit.create 方法去構(gòu)建接口的實例了金蜀,上面我們已經(jīng)分析了這個環(huán)節(jié)利用了動態(tài)代理,而且我們也分析了具體的 Call 的構(gòu)建流程在 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);
          }
        });
}

主要也就三行代碼渊抄,第一行是根據(jù)我們的 method 將其包裝成 ServiceMethod,第二行是通過 ServiceMethod 和方法的參數(shù)構(gòu)造 retrofit2.OkHttpCall 對象丧裁,第三行是通過 serviceMethod.callAdapter.adapt() 方法护桦,將 OkHttpCall 進行代理包裝;

下面一個一個介紹:

  • ServiceMethod應(yīng)該是最復(fù)雜的一個類了煎娇,包含了將一個method轉(zhuǎn)化為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);
    }

直接看 build 方法,首先拿到這個 callAdapter 最終拿到的是我們在構(gòu)建 retrofit 里面時 adapterFactories 時添加的缓呛,即為:new ExecutorCallbackCall<>(callbackExecutor, call)催享,該 ExecutorCallbackCall 唯一做的事情就是將原本 call 的回調(diào)轉(zhuǎn)發(fā)至 UI 線程。

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

接下來是 createResponseConverter 拿到 responseConverter 對象,其當然也是根據(jù)我們構(gòu)建 retrofit 時,addConverterFactory 添加的 ConverterFactory 對象來尋找一個合適的返回攀涵,尋找的依據(jù)主要看該 converter 能否處理你編寫方法的返回值類型铣耘,默認實現(xiàn)為 BuiltInConverters,僅僅支持返回值的實際類型為 ResponseBody 和 Void以故,也就說明了默認情況下涡拘,是不支持 Call<User> 這類類型的。

接下來就是對注解進行解析了据德,主要是對方法上的注解進行解析鳄乏,那么可以拿到 httpMethod 以及初步的 url(包含占位符)。

后面是對方法中參數(shù)中的注解進行解析棘利,這一步會拿到很多的 ParameterHandler 對象橱野,該對象在 toRequest() 構(gòu)造 Request 的時候調(diào)用其 apply 方法。

ok善玫,這里我們并沒有去一行一行查看代碼水援,其實意義也不太大,只要知道 ServiceMethod 主要用于將我們接口中的方法轉(zhuǎn)化為一個 Request 對象茅郎,于是根據(jù)我們的接口返回值確定了 responseConverter, 解析我們方法上的注解拿到初步的 url,解析我們參數(shù)上的注解拿到構(gòu)建 RequestBody 所需的各種信息蜗元,最終調(diào)用 toRequest 的方法完成 Request 的構(gòu)建。

  • 接下來看 OkHttpCall 的構(gòu)建系冗,構(gòu)造函數(shù)僅僅是簡單的賦值
OkHttpCall(ServiceMethod<T> serviceMethod, Object[] args) {
    this.serviceMethod = serviceMethod;
    this.args = args;
  }
  • 最后一步是 serviceMethod.callAdapter.adapt(okHttpCall)

我們已經(jīng)確定這個 callAdapter 是 ExecutorCallAdapterFactory.get() 對應(yīng)代碼為:

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);
      }
    };
  }

可以看到 adapt 返回的是 ExecutorCallbackCall 對象奕扣,繼續(xù)往下看:

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();
    }
  }

可以看出 ExecutorCallbackCall 僅僅是對 Call 對象進行封裝,類似裝飾者模式掌敬,只不過將其執(zhí)行時的回調(diào)通過 callbackExecutor 進行回調(diào)到UI線程中去了惯豆。

4.2.3 執(zhí)行Call

在 4.2.2 我們已經(jīng)拿到了經(jīng)過封裝的 ExecutorCallbackCall 類型的 call 對象,實際上就是我們實際在寫代碼時拿到的 call 對象奔害,那么我們一般會執(zhí)行 enqueue 方法楷兽,看看源碼是怎么做的

首先是 ExecutorCallbackCall.enqueue 方法,代碼在 4.2.2华临,可以看到除了將 onResponse 和 onFailure 回調(diào)到 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();
            }
        }
    });
}

沒有任何神奇的地方揭厚,內(nèi)部實際上就是 okhttp 的 Call 對象吃谣,也是調(diào)用 okhttp3.Call.enqueue 方法。

中間對于 okhttp3.Call 的創(chuàng)建代碼為:

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

可以看到桐款,通過 serviceMethod.toRequest 完成對 request 的構(gòu)建域仇,通過 request 去構(gòu)造 call 對象,然后返回.

中間還涉及一個 parseResponse 方法使碾,如果順利的話慰安,執(zhí)行的代碼如下:

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);

通過 serviceMethod 對 ResponseBody 進行轉(zhuǎn)化狸臣,然后返回轰绵,轉(zhuǎn)化實際上就是通過 responseConverter 的 convert 方法粉寞。

#ServiceMethod
 T toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }

ok,關(guān)于 responseConverter 后面還會細說左腔,不用擔心唧垦。

到這里,我們整個源碼的流程分析就差不多了液样,目的就掌握一個大體的原理和執(zhí)行流程振亮,了解下幾個核心的類。

那么總結(jié)一下:

  • 首先構(gòu)造retrofit鞭莽,幾個核心的參數(shù)呢坊秸,主要就是 baseurl,callFactory(默認okhttpclient),converterFactories,adapterFactories,excallbackExecutor。
  • 然后通過 create 方法拿到接口的實現(xiàn)類澎怒,這里利用 Java 的 Proxy 類完成動態(tài)代理的相關(guān)代理
  • 在 invoke 方法內(nèi)部褒搔,拿到我們所聲明的注解以及實參等,構(gòu)造 ServiceMethod喷面,ServiceMethod 中解析了大量的信息星瘾,最痛可以通過 toRequest 構(gòu)造出 okhttp3.Request 對象。有了 okhttp3.Request 對象就可以很自然的構(gòu)建出 okhttp3.call惧辈,最后 calladapter 對 Call 進行裝飾返回琳状。
  • 拿到 Call 就可以執(zhí)行 enqueue 或者 execute 方法了

ok,了解這么多足以。

下面呢盒齿,有幾個地方需要注意算撮,一方面是一些特殊的細節(jié);另一方面就是 Converter县昂。

Retrofit 中的各類細節(jié)

1. 上傳文件中使用的奇怪 value 值

第一個問題涉及到文件上傳肮柜,還記得我們在單文件上傳那里所說的嗎?有種類似于hack的寫法倒彰,上傳文件是這么做的审洞?

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

首先我們一點明確,因為這里使用了 @ Multipart待讳,那么我們認為 @Part 應(yīng)當支持普通的 key-value芒澜,以及文件。

對于普通的 key-value 是沒問題的创淡,只需要這樣 @Part("username") String username痴晦。

那么對于文件,為什么需要這樣呢琳彩? @Part("file_key"; filename="pp.png")

這個 value 設(shè)置的值不用看就會覺得特別奇怪誊酌,然而卻可以正常執(zhí)行部凑,原因是什么呢?

原因是這樣的:

當上傳 key-value 的時候碧浊,實際上對應(yīng)這樣的代碼:

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

也就是說涂邀,我們的 @Part 轉(zhuǎn)化為了

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

這么一看,很隨意箱锐,只要把key放進去就可以了比勉。

但是,retrofit2 并沒有對文件做特殊處理驹止,文件的對應(yīng)的字符串應(yīng)該是這樣的

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

與鍵值對對應(yīng)的字符串相比浩聋,多了個 ;filename="filename.png,就因為 retrofit 沒有做特殊處理臊恋,所以你現(xiàn)在看這些 hack 的做法

@Part("file_key\"; filename=\"pp.png")
拼接:==>
Content-Disposition", "form-data; name=\"" + key + "\"
結(jié)果:==>
Content-Disposition", "form-data; name=file_key\"; filename=\"pp.png\"

ok赡勘,到這里我相信你已經(jīng)理解了,為什么要這么做捞镰,而且為什么這么做可以成功闸与!

恩,值得一提的事岸售,因為這種方式文件名寫死了践樱,我們上文使用的的是 @Part MultipartBody.Part file,可以滿足文件名動態(tài)設(shè)置,這個方式貌似也是 2.0.1 的時候支持的凸丸。

上述相關(guān)的源碼:

#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());
}

可以看到呢拷邢,并沒有對文件做特殊處理,估計下個版本說不定 @Part 會多個 isFile=true|false 屬性屎慢,甚至修改對應(yīng)形參瞭稼,然后在這里做簡單的處理。

ok腻惠,最后來到關(guān)鍵的 ConverterFactory 了~

自定義 Converter.Factory

1. responseBodyConverter

關(guān)于 Converter.Factory环肘,肯定是通過 addConverterFactory 設(shè)置的

Retrofit retrofit = new Retrofit.Builder()
    .addConverterFactory(GsonConverterFactory.create())
        .build();

該方法接受的是一個 Converter.Factory factory 對象

該對象呢,是一個抽象類集灌,內(nèi)部包含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;
    }
  }

可以看到呢悔雹,3 個方法都是空方法而不是抽象的方法,也就表明了我們可以選擇去實現(xiàn)其中的 1 個或多個方法欣喧,一般只需要關(guān)注 requestBodyConverter 和 responseBodyConverter 就可以了腌零。

ok,我們先看如何自定義唆阿,最后再看 GsonConverterFactory.create 的源碼益涧。

先來個簡單的,實現(xiàn) responseBodyConverter 方法驯鳖,看這個名字很好理解闲询,就是將 responseBody 進行轉(zhuǎn)化就可以了久免。

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();

}

不知不覺,方法還蠻多的摔握,假設(shè)哈寄狼,我們這里去掉 retrofit 構(gòu)造時的 GsonConverterFactory.create,自己實現(xiàn)一個 Converter.Factory 來做數(shù)據(jù)的轉(zhuǎn)化工作氨淌。

首先我們解決 responseBodyConverter泊愧,那么代碼很簡單,我們可以這么寫:

public class UserConverterFactory extends Converter.Factory
{
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)
    {
        //根據(jù)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;
    }
}

使用的時候呢,可以

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

ok豪筝,這樣的話痰滋,就可以完成我們的 ReponseBody 到 List<User> 或者 User 的轉(zhuǎn)化了。

可以看出续崖,我們這里用的依然是 Gson敲街,那么有些同學肯定不希望使用 Gson 就能實現(xiàn),如果不使用 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;
    }
}

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

這里鄭重提醒:如果你針對特定的類型去寫 Converter峻黍,一定要在 UserConverterFactory#responseBodyConverter 中對類型進行檢查,發(fā)現(xiàn)不能處理的類型 return null拨匆,這樣的話姆涩,可以交給后面的 Converter.Factory處理,比如本例我們可以按照下列方式檢查:

public class UserConverterFactory extends Converter.Factory
{
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)
    {
        //根據(jù)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;
    }

}

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

2. requestBodyConverter

ok览爵,上面接口一大串方法呢置鼻,使用了我們的 Converter 之后,有個方法我們現(xiàn)在還是不支持的蜓竹。

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

ok箕母,這個 @Body 需要用到這個方法储藐,叫做 requestBodyConverter,根據(jù)參數(shù)轉(zhuǎn)化為 RequestBody嘶是,下面看下我們?nèi)绾翁峁┲С帧?/p>

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);
    }
}

然后在 UserConverterFactory 中復(fù)寫 requestBodyConverter 方法钙勃,返回即可:

public class UserConverterFactory extends Converter.Factory
{

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

這里偷了個懶,使用 Gson 將對象轉(zhuǎn)化為 json 字符串了聂喇,如果你不喜歡使用框架辖源,你可以選擇拼接字符串,或者反射寫一個支持任何對象的希太,反正就是對象 ->json 字符串的轉(zhuǎn)化克饶。最后構(gòu)造一個 RequestBody 返回即可。

ok,到這里誊辉,我相信如果你看的細致矾湃,自定義 Converter.Factory 是干嘛的,但是我還是要總結(jié)下:

  • responseBodyConverter 主要是對應(yīng)@Body注解堕澄,完成 ResponseBody 到實際的返回類型的轉(zhuǎn)化邀跃,這個類型對應(yīng) Call<XXX> 里面的泛型 XXX,其實 @Part 等注解也會需要 responseBodyConverter蛙紫,只不過我們的參數(shù)類型都是 RequestBody坞嘀,由默認的 converter 處理了。
  • requestBodyConverter 完成對象到 RequestBody 的構(gòu)造惊来。
  • 一定要注意丽涩,檢查type如果不是自己能處理的類型,記得 return null (因為可以添加多個裁蚁,你不能處理 return null ,還會去遍歷后面的 converter).

值得學習的API

其實一般情況下看源碼呢矢渊,可以讓我們更好的去使用這個庫,當然在看的過程中如果發(fā)現(xiàn)了一些比較好的處理方式呢枉证,是非常值得記錄的矮男。如果每次看別人的源碼都能吸取一定的精華,比你單純的去理解會好很多室谚,因為你的記憶力再好毡鉴,源碼解析你也是會忘的,而你記錄下來并能夠使用的優(yōu)越的代碼秒赤,可能用久了就成為你的代碼了猪瞬。

我舉個例子:比如 retrofit2 中判斷當前運行的環(huán)境代碼如下,如果下次你有這樣的需求入篮,你也可以這么寫陈瘦,甚至源碼中根據(jù)不同的運行環(huán)境還提供了不同的 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();
  }

原文鏈接 :http://blog.csdn.net/lmj623565791/article/details/51304204

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市潮售,隨后出現(xiàn)的幾起案子痊项,更是在濱河造成了極大的恐慌锅风,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鞍泉,死亡現(xiàn)場離奇詭異皱埠,居然都是意外死亡,警方通過查閱死者的電腦和手機咖驮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門边器,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人游沿,你說我怎么就攤上這事饰抒“估” “怎么了诀黍?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長仗处。 經(jīng)常有香客問我眯勾,道長,這世上最難降的妖魔是什么婆誓? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任吃环,我火速辦了婚禮,結(jié)果婚禮上洋幻,老公的妹妹穿的比我還像新娘郁轻。我一直安慰自己,他們只是感情好文留,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布好唯。 她就那樣靜靜地躺著,像睡著了一般燥翅。 火紅的嫁衣襯著肌膚如雪骑篙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天森书,我揣著相機與錄音靶端,去河邊找鬼。 笑死凛膏,一個胖子當著我的面吹牛杨名,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播猖毫,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼镣煮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鄙麦?” 一聲冷哼從身側(cè)響起典唇,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤镊折,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后介衔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體恨胚,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年炎咖,在試婚紗的時候發(fā)現(xiàn)自己被綠了赃泡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡乘盼,死狀恐怖升熊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绸栅,我是刑警寧澤级野,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站粹胯,受9級特大地震影響蓖柔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜风纠,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一况鸣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧竹观,春花似錦镐捧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至速址,卻和暖如春玩焰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芍锚。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工昔园, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人并炮。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓默刚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親逃魄。 傳聞我的和親對象是個殘疾皇子荤西,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348

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