Retrofit2使用方式和原始碼解析
簡單介紹
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 預設使用okhttpRetrofit2 預設使用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本身對非同步處理已經做的很好了。充分發揮了其它模組的功能,簡化了自身邏輯。
歡迎掃描二維碼,關注公眾號
相關文章
- Retrofit2原始碼解析原始碼
- Retrofit2原始碼解析(一)原始碼
- 【原始碼SOLO】Retrofit2原始碼解析(一)原始碼
- 【原始碼SOLO】Retrofit2原始碼解析(二)原始碼
- RxBinding使用和原始碼解析原始碼
- Picasso的使用和原始碼解析原始碼
- EventBus的使用和原始碼解析原始碼
- Binder的使用方法和原始碼解析原始碼
- Spring @Profile註解使用和原始碼解析Spring原始碼
- net/http包的使用模式和原始碼解析HTTP模式原始碼
- GYHttpMock:使用及原始碼解析HTTPMock原始碼
- spark的基本運算元使用和原始碼解析Spark原始碼
- RecyclerView用法和原始碼深度解析View原始碼
- HandlerThread和IntentService原始碼解析threadIntent原始碼
- redis原始碼解析----epoll的使用Redis原始碼
- 熔斷器 Hystrix 原始碼解析 —— 執行命令方式原始碼
- EOS原始碼解析 建立賬號的三種方式。原始碼
- myBatis原始碼解析-二級快取的實現方式MyBatis原始碼快取
- TextWatcher的使用及原始碼解析原始碼
- DialogFragment使用到原始碼完全解析Fragment原始碼
- [原始碼解析] PyTorch 如何使用GPU原始碼PyTorchGPU
- Webpack原始碼基礎-Tapable從使用Hook到原始碼解析Web原始碼Hook
- Glide原始碼解析四(解碼和轉碼)IDE原始碼
- Android依賴注入Dagger的使用和原始碼解析(上篇)Android依賴注入原始碼
- 【原始碼解析】- ArrayList原始碼解析,絕對詳細原始碼
- await 錯誤捕獲實現方式原始碼示例解析AI原始碼
- HashMap原始碼解析和設計解讀HashMap原始碼
- UGUI原始碼解析(Toggle和ToggleGroup)UGUI原始碼
- OkHttp 開源庫使用與原始碼解析HTTP原始碼
- PyTorch ResNet 使用與原始碼解析PyTorch原始碼
- ffmpeg在iOS的使用-iFrameExtractor原始碼解析iOS原始碼
- Android 圖片載入框架 Picasso 基本使用和原始碼完全解析Android框架原始碼
- Spark原始碼-SparkContext原始碼解析Spark原始碼Context
- WMRouter使用和原始碼分析原始碼
- CountDownLatch原始碼解析CountDownLatch原始碼
- LeakCanary原始碼解析原始碼
- vuex原始碼解析Vue原始碼
- ArrayBlockQueue原始碼解析BloC原始碼