Retrofit2使用方式和原始碼解析

robert_chao發表於2016-06-04

簡單介紹

Retrofit是一套RESTful架構的Android(Java)客戶端實現,基於註解,提供JSON to POJO(Plain Ordinary Java Object,簡單Java物件),POJO to JSON,網路請求(POST,GET,PUT,DELETE等)封裝。

軟體首頁http://square.github.io/retrofit/

github地址https://github.com/square/retrofit

具體使用

官方文件非常清晰,Retrofit2對Retrofit做了很大改進,針對改進做一下分析和使用說明

底層okhttp不同

Retrofit 預設使用okhttp
Retrofit2 預設使用okhttp3

Service介面定義方式不同

在Retrofit2之前如果定義一個同步的函式,應該這樣定義:
public interface GitHubService {
  
    @POST("/list")
    Repo loadRepo();
  
}
非同步函式定義
public interface GitHubService {
  
    @POST("/list")
    void loadRepo(Callback<Repo> cb);
  
}
Retrofit2 同步非同步方法定義一個介面就可以了
mport retrofit.Call;
public interface GitHubService {
  
    @POST("/list")
    Call<Repo> loadRepo();
  
}

如果要呼叫同步請求,只需呼叫execute;而發起一個非同步請求則是呼叫enqueue。

Retrofit2 可以取消請求方法

Retrofit2請求使用Call,Call有cancel方法可以取消請求
只需呼叫call.cancel()就可以取消請求。

Converter現在從Retrofit2中刪除,需要根據自己的需要引入Converter

這裡是Square提供的官方Converter modules列表。選擇一個最滿足你需求的。
Gson: com.squareup.retrofit:converter-gson
Jackson: com.squareup.retrofit:converter-jackson
Moshi: com.squareup.retrofit:converter-moshi
Protobuf: com.squareup.retrofit:converter-protobuf
Wire: com.squareup.retrofit:converter-wire
Simple XML: com.squareup.retrofit:converter-simplexml

Retrofit2 新的URL定義方式

Retrofit介面請求URL要求用/開頭,必須設定baseUrl,介面請求URL不能是完整路徑。可以使用Endpoint切換伺服器路徑
於 Retrofit2中新的URL定義方式,建議
- Base URL: 總是以 /結尾
- @Url: 不要以 / 開頭
Retrofit2中沒有Endpoint這個類,@Url中可以包含完整的路徑,包含完整路徑baseUrl會被忽略
public interface APIService {
    @POST("http://api.test.com/test)
    Call<Users> loadUsers();
}

Retrofit2 需要OkHttp的支援

OkHttp在Retrofit裡是可選的。如果你想讓Retrofit 使用OkHttp 作為HTTP 連線介面,需要手動包含okhttp 依賴。
但是在Retrofit2中,OkHttp 是必須的,並且自動設定為了依賴。
Retrofit2缺少INTERNET許可權會導致SecurityException異常
在Retrofit 中,如果忘記在AndroidManifest.xml檔案中新增INTERNET許可權。非同步請求會直接進入failure回撥方法,得到PERMISSION DENIED 錯誤訊息。沒有任何異常被丟擲。但是在Retrofit2中,當呼叫call.enqueue或者call.execute,將立即丟擲SecurityException,如果不使用try-catch會導致崩潰。

即使response存在問題onResponse依然被呼叫

在Retrofit中,如果獲取的 response 不能背解析成定義好的物件,則會呼叫failure。但是在Retrofit2中,不管 response 是否能被解析。onResponse總是會被呼叫。但是在結果不能被解析的情況下,response.body()會返回null。只有丟擲異常才會呼叫onFailure

攔截器不同

在Retrofit中,可以使用RequestInterceptor來攔截一個請求,但是它已經從Retrofit2 移除了,因為HTTP連線層已經完全轉為OkHttp。
Retrofit2使用okhttp的攔截器


原始碼分析

Retrofit的建立

Retrofit是個final類,不能再定義子類,Retrofit沒有public構造方法,只能使用構建的者方式Retrofit.Builder構建。
etrofit.Builder可以指定url根地址、採用的網路客戶端、回撥執行緒池、請求攔截器、返回資料格式器和錯誤處理。
通常必須呼叫baseUrl,addConverterFactory這兩個方法。
構建器預設使用OkHttpClient作為網路請求工具,addCallAdapterFactory用來構建請求例項,callbackExecutor指定回撥方法執行緒池,根據不同平臺構建不同,Android構建的是defaultCallbackExecutor,預設在主執行緒中。validateEagerly預設為false,表示執行的時候再去解析註解。如果設定為true,那麼create例項之後,就會先解析介面所有方法。

if (baseUrl == null) {
       throw new IllegalStateException("Base URL required.");
     }<pre name="code" class="java"> public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
......


請求例項的建立

呼叫Retrofit的create方法,建立了執行請求物件的代理,代理方法的執行中,對於Object方法和預設方法,直接執行,其它方法解析後執行。使用代理加註解,是Retrofit的核心思想。
  return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod serviceMethod = loadServiceMethod(method);
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });

介面的解析過程

loadServiceMethod中首先從快取中找解析的方法,找到直接返回,否則解析方法,然後放入快取中。解析過的介面儲存在了serviceMethodCache中,防止重複解析,提高效率。解析方法使用ServiceMethod.Builder構建解析器。
build過程中,首先建立callAdapter,
解析中有一系列規則,發現異常會丟擲異常
方法返回的引數不能是泛型,不能是基本資料型別,不能是void。
註解不能為空
建立完callAdapter繼續構建其他ServiceMethod屬性,需要注意其他丟擲異常的情況
返回型別不能是一個Response
httpMethod註解不能為空
如果請求中沒有body,不能使用Multipart和FormEncoded
引數型別不能是基本型別和泛型
引數不能沒有註解
URL不能為空
Form-encoded方法至少包含一個@Field註解
Multipart方法至少包含一個@Part註解
滿足以上要求,才能正確構建ServiceMethod。
  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;
  }

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

網路請求處理

方法解析完成之後,呼叫CallAdapter的adapt方法返回Call
內部構建如下
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);
      }
    };
  }
此時並沒有發起請求,只是構建除了call,具體來說構建的是ExecutorCallbackCall,真正用來執行請求的是call內部的代理delegate,這個代理的真實身份是OkHttpCall,Retrofit2相當於將請求全權交給OKhttp處理,非同步請求enqueue方法,同步請求execute方法都是執行的Okhttpclient對應的方法,
一下是okhttp請求的真實建立方法
  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;
  }
非同步執行完之後,利用callbackExecutor將回撥拋回主執行緒。

Retrofit2比Retrofit簡化了很多,但是功能卻更加強大了。真的非常奇妙。作為網路請求框架,整個程式碼中居然沒有用執行緒池,因為Okhttp本身對非同步處理已經做的很好了。充分發揮了其它模組的功能,簡化了自身邏輯。



歡迎掃描二維碼,關注公眾號





相關文章