Android 網路框架之Retrofit2使用詳解及從原始碼中解析原理
就目前來說Retrofit2使用的已相當的廣泛,那麼我們先來了解下兩個問題:
1 . 什麼是Retrofit?
Retrofit是針對於Android/Java的、基於okHttp的、一種輕量級且安全的、並使用註解方式的網路請求框架。
2 . 我們為什麼要使用Retrofit,它有哪些優勢?
首先,Retrofit使用註解方式,大大簡化了我們的URL拼寫形式,而且註解含義一目瞭然,簡單易懂;
其次,Retrofit使用簡單,結構層次分明,每一步都能清晰的表達出之所以要使用的寓意;
再者,Retrofit支援同步和非同步執行,使得請求變得異常簡單,只要呼叫enqueue/execute即可完成;
最後,Retrofit更大自由度的支援我們自定義的業務邏輯,如自定義Converters。
好,知道了Retrofit是什麼,有了哪些優勢,現在我們來學習下怎麼使用。
一 Retrofit2使用詳解:
在使用之前,你必須先匯入必要的jar包,以androidStudio為例:
新增依賴:
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
因為Retrofit2是依賴okHttp請求的,而且請檢視它的META-INF->META-INF\maven\com.squareup.retrofit2\retrofit->pom.xml檔案,
<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
...
</dependencies>
由此可見,它確實是依賴okHttp,okHttp有會依賴okio所以它會制動的把這兩個包也匯入進來。
新增許可權:
既然要請求網路,在我們android手機上是必須要有訪問網路的許可權的,下面把許可權新增進來
<uses-permission android:name="android.permission.INTERNET"/>
好了,下面開始介紹怎麼使用Retrofit,既然它是使用註解的請求方式來完成請求URL的拼接,那麼我們就按註解的不同來分別學習:
首先,我們需要建立一個java介面,用於存放請求方法的:
public interface GitHubService {
}
然後逐步在該方法中新增我們所需要的方法(按照請求方式):
1 :Get : 是我們最常見的請求方法,它是用來獲取資料請求的。
①:直接通過URL獲取網路內容:
public interface GitHubService {
@GET("users/octocat/repos")
Call<List<Repo>> listRepos();
}
在這裡我們定義了一個listRepos()的方法,通過@GET註解標識為get請求,請求的URL為“users/octocat/repos”。
然後看看Retrofit是怎麼呼叫的,程式碼如下:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
Call<List<Repo>> repos = service.listRepos();
repos.enqueue(new Callback<List<Repo>>(){
@Override
public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response){
}
@Override
public void onFailure(Call<List<Repo>> call, Throwable t){
}
});
程式碼解釋:首先獲取Retrofit物件,然後通過動態代理獲取到所定義的介面,通過呼叫接口裡面的方法獲取到Call型別返回值,最後進行網路請求操作(這裡不詳細說明Retrofit 實現原理,後面會對它進行原始碼解析),這裡必須要說的是請求URL的拼接:在構建Retrofit物件時呼叫baseUrl所傳入一個String型別的地址,這個地址在呼叫service.listRepos()時會把@GET(“users/octocat/repos”)的URL拼接在尾部。
ok,這樣就完成了,我們這次的請求,但是我們不能每次請求都要建立一個方法呀?這時我們就會想起動態的構建URL了
②:動態獲取URL地址:@Path
我們再上面的基礎上進行修改,如下:
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
這裡在Get註解中包含{user},它所對應的是@Path註解中的“user”,它所標示的正是String user,而我們再使用Retrofit物件動態代理的獲取到GitHubService,當呼叫listRepos時,我們就必須傳入一個String型別的User,如:
...
Call<List<Repo>> repos = service.listRepos("octocat");
...
如上程式碼,其他的程式碼都是不變的,而我們只需要使用@Path註解就完全的實現了動態的URL地址了,是不是很方便呢,這還不算什麼,通常情況下,我們去獲取一些網路資訊,因為資訊量太大,我們會分類去獲取,也就是攜帶一些必要的元素進行過濾,那我們該怎麼實現呢?其實也很簡單,因為Retrofit已經為我們封裝好了註解,請看下面(官網例項):
③:動態指定條件獲取資訊:@Query
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort);
我們只需要使用@Query註解即可完成我們的需求,在@Query(“sort”)中,short就好比是URL請求地址中的鍵,而它說對應的String sort中的sort則是它的值。
但是我們想,在網路請求中一般為了更精確的查詢到我們所需要的資料,過濾更多不需要的無關的東西,我們往往需要攜帶多個請求引數,當然可以使用@Query註解,但是太麻煩,很長,容易遺漏和出錯,那有沒有更簡單的方法呢,有,當然後,我們可以直接放到一個map鍵值對中:
④:動態指定條件組獲取資訊:@QueryMap
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options);
使用@QueryMap註解可以分別地從Map集合中獲取到元素,然後進行逐個的拼接在一起。
ok,到這裡,我們使用@Get註解已經可以完成絕大部分的查詢任務了,下面我們再來看看另一種常用的請求方式–post
2 POST : 一種用於攜帶傳輸資料的請求方式
稍微瞭解點Http的同學們,可能都會知道:相對於get請求方式把資料存放在uri位址列中,post請求傳輸的資料時存放在請求體中,所以post才能做到對資料的大小無限制。而在Retrofit中,它又是怎麼使用的呢?請看下面:
①:攜帶資料型別為物件時:@Body
@POST("users/new")
Call<User> createUser(@Body User user);
當我們的請求資料為某物件時Retrofit是這麼處理使用的:
首先,Retrofit用@POST註解,標明這個是post的請求方式,裡面是請求的url;
其次,Retrofit仿照http直接提供了@Body註解,也就類似於直接把我們要傳輸的資料放在了body請求體中,這樣應用可以更好的方便我們理解。
來看下應用:
Call<List<User>> repos = service.createUser(new User(1, "管滿滿", "28", "http://write.blog.csdn.net/postlist"));
這樣我們直接把一個新的User物件利用註解@Body存放在body請求體,並隨著請求的執行傳輸過去了。
但是有同學在這該有疑問了,Retrofit就只能傳輸的資料為物件嗎?當然不是,下面請看
②:攜帶資料型別為表單鍵值對時:@Field
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
當我們要攜帶的請求資料為表單時,通常會以鍵值對的方式呈現,那麼Retrofit也為我們考慮了這種情況,它首先用到@FormUrlEncoded註解來標明這是一個表單請求,然後在我們的請求方法中使用@Field註解來標示所對應的String型別資料的鍵,從而組成一組鍵值對進行傳遞。
那你是不是有該有疑問了,假如我是要上傳一個檔案呢?
③:單檔案上傳時:@Part
@Multipart
@PUT("user/photo")
Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);
此時在上傳檔案時,我們需要用@Multipart註解註明,它表示允許多個@Part,@Part則對應的一個RequestBody 物件,RequestBody 則是一個多型別的,當然也是包括檔案的。下面看看使用
File file = new File(Environment.getExternalStorageDirectory(), "ic_launcher.png");
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file);
RequestBody descriptionRequestBody = RequestBody.create(null, "this is photo.");
Call<User> call = service.updateUser(photoRequestBody, descriptionRequestBody);
這裡我們建立了兩個RequestBody 物件,然後呼叫我們定義的updateUser方法,並把RequestBody傳遞進入,這樣就實現了檔案的上傳。是不是很簡單呢?
相比單檔案上傳,Retrofit還進一步提供了多檔案上傳的方式:
④:多檔案上傳時:@PartMap
@Multipart
@PUT("user/photo")
Call<User> updateUser(@PartMap Map<String, RequestBody> photos, @Part("description") RequestBody description);
這裡其實和單檔案上傳是差不多的,只是使用一個集合型別的Map封裝了檔案,並用@PartMap註解來標示起來。其他的都一樣,這裡就不多講了。
3 Header : 一種用於攜帶訊息頭的請求方式
Http請求中,為了防止攻擊或是過濾掉不安全的訪問或是為新增特殊加密的訪問等等以減輕伺服器的壓力和保證請求的安全,通常都會在訊息頭中攜帶一些特殊的訊息頭處理。Retrofit也為我們提供了該請求方式:
@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();
-----------------------------------------------------------
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);
以上兩種是靜態的為Http請求新增訊息頭,只需要使用@Headers註解,以鍵值對的方式存放即可,如果需要新增多個訊息頭,則使用{}包含起來,如上所示。但要注意,即使有相同名字得訊息頭也不會被覆蓋,並共同的存放在訊息頭中。
當然有靜態新增那相對的也就有動態的新增訊息頭了,方法如下:
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
使用@Header註解可以為一個請求動態的新增訊息頭,假如@Header對應的訊息頭為空的話,則會被忽略,否則會以它的.toString()方式輸出。
ok,到這裡已基本講解完Retrofit的使用,還有兩個重要但簡單的方法也必須在這裡提一下:
1 call.cancel();它可以終止正在進行的請求,程式只要一旦呼叫到它,不管請求是否在終止都會被停止掉。
2 call.clone();當你想要多次請求一個介面的時候,直接用 clone 的方法來生產一個新的,否則將會報錯,因為當你得到一個call例項,我們呼叫它的 execute 方法,但是這個方法只能呼叫一次。多次呼叫則發生異常。
好了,關於Retrofit的使用我們就講這麼多,接下來我們從原始碼的角度簡單的解析下它的實現原理。
二 Retrofit2 從原始碼解析實現原理
首先先看一下Retrofit2標準示例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHubService service = retrofit.create(GitHubService.class);
service.enqueue();
service.execute();
由上面我們基本可以看出,Retrofit是通過構造者模式創建出來的,那麼我們就來看看Builder這個構造器的原始碼:
public static final class Builder {
...
Builder(Platform platform) {
this.platform = platform;
converterFactories.add(new BuiltInConverters());
}
public Builder() {
this(Platform.get());
}
public Builder client(OkHttpClient client) {
return callFactory(checkNotNull(client, "client == null"));
}
public Builder callFactory(okhttp3.Call.Factory factory) {
this.callFactory = checkNotNull(factory, "factory == null");
return this;
}
public Builder baseUrl(String baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
HttpUrl httpUrl = HttpUrl.parse(baseUrl);
if (httpUrl == null) {
throw new IllegalArgumentException("Illegal URL: " + baseUrl);
}
return baseUrl(httpUrl);
}
public Builder baseUrl(HttpUrl baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
List<String> pathSegments = baseUrl.pathSegments();
if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
}
this.baseUrl = baseUrl;
return this;
}
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
adapterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
public Builder callbackExecutor(Executor executor) {
this.callbackExecutor = checkNotNull(executor, "executor == null");
return this;
}
public Builder validateEagerly(boolean validateEagerly) {
this.validateEagerly = validateEagerly;
return this;
}
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();
}
List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);
}
}
原始碼講解:
1:當我們使用new Retrofit.Builder()來建立時,在Builder構造器中,首先就獲得當前的裝置平臺資訊,並且把內建的轉換器工廠(BuiltInConverters)加添到工廠集合中,它的主要作用就是當使用多種Converters的時候能夠正確的引導並找到可以消耗該型別的轉化器。
2:從我們的基本示例中看到有呼叫到.baseUrl(BASE_URL)這個方法,實際上沒當使用Retrofit時,該方法都是必須傳入的,並且還不能為空,從原始碼中可以看出,當baseUrl方法傳進的引數來看,如果為空的話將會丟擲NullPointerException空指標異常。
3:addConverterFactory該方法是傳入一個轉換器工廠,它主要是對資料轉化用的,請網路請求獲取的資料,將會在這裡被轉化成我們所需要的資料型別,比如通過Gson將json資料轉化成物件型別。
4 : 從原始碼中,我們看到還有一個client方法,這個是可選的,如果沒有傳入則就預設為OkHttpClient,在這裡可以對OkHttpClient做一些操作,比如新增攔截器列印log等
5:callbackExecutor該方法從名字上看可以得知應該是回撥執行者,也就是Call物件從網路服務獲取資料之後轉換到UI主執行緒中。
6:addCallAdapterFactory該方法主要是針對Call轉換了,比如對Rxjava的支援,從返回的call物件轉化為Observable物件。
7:最後呼叫build()方法,通過new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);構造方法把所需要的物件傳遞到Retrofit物件中。
ok,當我們通過Builder構造器構造出Retrofit物件時,然後通過Retrofit.create()方法是怎麼把我們所定義的介面轉化成介面例項的呢?來看下create()原始碼:
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
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);
}
});
}
當看到Proxy時,是不是多少有點明悟了呢?沒錯就是動態代理,動態代理其實已經封裝的很簡單了,主要使用newProxyInstance()方法來返回一個類的代理例項,其中它內部需要傳遞一個類的載入器,類本身以及一個InvocationHandler處理器,主要的動作都是在InvocationHandler中進行的,它裡面只有一個方法invoke()方法,每當我們呼叫代理類裡面的方法時invoke()都會被執行,並且我們可以從該方法的引數中獲取到所需要的一切資訊,比如從method中獲取到方法名,從args中獲取到方法名中的引數資訊等。
而Retrofit在這裡使用到動態代理也不會例外:
首先,通過method把它轉換成ServiceMethod ;
然後,通過serviceMethod, args獲取到okHttpCall 物件;
最後,再把okHttpCall進一步封裝並返回Call物件。
下面來逐步詳解。
1:將method把它轉換成ServiceMethod
ServiceMethod serviceMethod = loadServiceMethod(method);
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;
}
loadServiceMethod原始碼方法中非常的好理解,主要就是通過ServiceMethod.Builder()方法來構建ServiceMethod,並把它給快取取來,以便下次可以直接回去ServiceMethod。那下面我們再來看看它是怎麼構建ServiceMethod方法的:
public Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();
this.parameterTypes = method.getGenericParameterTypes();
this.parameterAnnotationsArray = method.getParameterAnnotations();
}
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();
...
return new ServiceMethod<>(this);
}
首先在Builder()中初始化一些引數,然後在build()中返回一個new ServiceMethod<>(this)物件。
下面來詳細的解釋下build()方法,完全理解了該方法則便於理解下面的所有執行流程。
①:構建CallAdapter物件,該物件將會在第三步中起著至關重要的作用。
現在我們先看看它是怎麼構建CallAdapter物件的:createCallAdapter()方法原始碼如下:
private CallAdapter<?> createCallAdapter() {
Type returnType = method.getGenericReturnType();
if (Utils.hasUnresolvableType(returnType)) {
throw methodError(
"Method return type must not include a type variable or wildcard: %s", returnType);
}
if (returnType == void.class) {
throw methodError("Service methods cannot return void.");
}
Annotation[] annotations = method.getAnnotations();
try {
return retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(e, "Unable to create call adapter for %s", returnType);
}
}
在createCallAdapter方法中主要做的是事情就是獲取到method的型別和註解,然後呼叫retrofit.callAdapter(returnType, annotations);方法:
public CallAdapter<?> callAdapter(Type returnType, Annotation[] annotations) {
return nextCallAdapter(null, returnType, annotations);
}
public CallAdapter<?> nextCallAdapter(CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
checkNotNull(returnType, "returnType == null");
checkNotNull(annotations, "annotations == null");
int start = adapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = adapterFactories.size(); i < count; i++) {
CallAdapter<?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
...
}
輾轉到Retrofit中nextCallAdapter()中,在for 迴圈中分別從adapterFactories中來獲取CallAdapter物件,但是adapterFactories中有哪些CallAdapter物件呢,這就需要返回到構建Retrofit物件中的Builder 構造器中查看了
public static final class Builder {
...
public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
adapterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
...
public Retrofit build() {
List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
...
}
}
CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
if (callbackExecutor != null) {
return new ExecutorCallAdapterFactory(callbackExecutor);
}
return DefaultCallAdapterFactory.INSTANCE;
}
從上面的程式碼中可以看到,不管有沒有通過addCallAdapterFactory新增CallAdapter,adapterFactories集合至少都會有一個ExecutorCallAdapterFactory物件。當我們從adapterFactories集合中回去CallAdapter物件時,那我們都會獲得ExecutorCallAdapterFactory這個物件。而這個物件將在第三步中和後面執行同步或非同步請求時起著至關重要的作用。
②:構建responseConverter轉換器物件,它的作用是尋找適合的資料型別轉化
該物件的構建和構建CallAdapter物件的流程基本是一致的,這裡就不在贅述。同學們可自行檢視原始碼。
2:通過serviceMethod, args獲取到okHttpCall 物件
第二步相對比較簡單,就是物件傳遞:
OkHttpCall(ServiceMethod<T> serviceMethod, Object[] args) {
this.serviceMethod = serviceMethod;
this.args = args;
}
3:把okHttpCall進一步封裝並返回Call物件
這一步也是一句話 return serviceMethod.callAdapter.adapt(okHttpCall);但是想理解清楚必須先把第一步理解透徹,通過第一步我們找得到serviceMethod.callAdapter就是ExecutorCallAdapterFactory物件,那麼呼叫.adapt(okHttpCall)把okHttpCall怎麼進行封裝呢?看看原始碼:
<R> T adapt(Call<R> call);
一看,嚇死寶寶了,就這麼一句,這是嘛呀,但是經過第一步的分析,我們已知道serviceMethod.callAdapter就是ExecutorCallAdapterFactory,那麼我們可以看看在ExecutorCallAdapterFactory類中有沒有發現CallAdapter的另類應用呢,一看,果不其然在重寫父類的get()方法中我們找到了答案:
@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);
}
};
}
當看到return new CallAdapter中的adapt(Call call)我們就完全知其所以然了,至於ExecutorCallbackCall怎麼應用的我們在發起網路請求的時候講解。
ok,當我們得到介面的代理例項之後,通過代理介面呼叫裡面的方法,就會觸發InvocationHandler物件中的invoke方法,從而完成上面的三個步驟並且返回一個Call物件,通過Call物件就可以去完成我們的請求了,Retrofit為我們提供兩種請求方式,一種是同步,一種是非同步。我們這裡就以非同步方式來講解:
service.enqueue(new Callback<List<User>>() {
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
Log.d("response body",response.body());
}
@Override
public void onFailure(Call<BoardInfo> call, Throwable t) {
Log.i("response Throwable",t.getMessage().toString());
}
});
從上面我們可以看到enqueue方法中有一個回撥函式,回撥函式裡面重寫了兩個方法分別代表請求成功和失敗的方法,但是我們想知道它是怎麼實現的原理呢?那麼請往下面看:
在上面獲取介面的代理例項時,通過代理介面呼叫裡面的方法獲取一個Call物件,我們上面也分析了其實這個Call物件就是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()) {
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);
}
});
}
});
}
在ExecutorCallbackCall類中,封裝了兩個物件一個是callbackExecutor,它主要是把現在執行的執行緒切換到主執行緒中去,一個是delegate物件,這個物件就是真真正正的執行網路操作的物件,那麼它的真身到底是什麼呢?還記得我們在獲取代理介面第三步執行的serviceMethod.callAdapter.adapt(okHttpCall)的分析吧,經過輾轉幾步終於把okHttpCall傳遞到了new ExecutorCallbackCall<>(callbackExecutor, call);中,然後看看ExecutorCallbackCall的構造方法:
ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
this.callbackExecutor = callbackExecutor;
this.delegate = delegate;
}
由此可以明白delegate 就是okHttpCall物件,那麼我們在看看okHttpCall是怎麼執行非同步網路請求的:
@Override
public void enqueue(final Callback<T> callback) {
if (callback == null) throw new NullPointerException("callback == null");
okhttp3.Call call;
Throwable failure;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
call = rawCall;
failure = creationFailure;
if (call == null && failure == null) {
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();
}
}
});
}
從上面程式碼中,我們很容易就看出,其實它就是這裡面封裝了一個okhttp3.Call,直接利用okhttp進行網路的非同步操作,至於okhttp是怎麼進行網路請求的我們就不再這裡講解了,感興趣的朋友可以自己去檢視原始碼。
好了,到這裡整個Retrofit的實現原理基本已解析完畢,相信大家學習過都能夠很好的掌握了。ok,今天就講到這裡吧,本來還打算再寫個例項放上來的,看看篇幅也就放棄了,實戰部分會在下一篇和Rxjava一起放出來,Rxjava我自己學習的都心花怒放了。看完記住關注微信平臺。
更多資訊請關注微信平臺,有部落格更新會及時通知。愛學習愛技術。