retrofit 已經流行很久了,它是Square開源的一款優秀的網路框架,這個框架對okhttp進行了封裝,讓我們使用okhttp做網路請求更加簡單。但是光學會使用只是讓我們多了一個技能,學習其原始碼才能讓我們更好的成長。
本篇文章是在分析retrofit的原始碼流程,有大量的程式碼,讀者最好把原始碼下載下來匯入IDE,然後跟著一起看,效果會更好(文末有原始碼獲取方式)
一.retrofit入門
- 定義網路請求的API介面:
interface GithubApiService {
@GET("users/{name}/repos")
Call<ResponseBody> searchRepoInfo(@Path("name") String name);
}
複製程式碼
使用了註解表明請求方式,和引數型別,這是retrofit的特性,也正是簡化了我們的網路請求過程的地方!
- 初始化一個retrofit的例項:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
複製程式碼
retrofit的例項化很簡單,採用鏈式呼叫的設計,把需要的引數傳進去即可,複雜的引數我們這裡就不舉例了。
- 生成介面實現類:
GithubApiService githubService = retrofit.create(service)
Call<ResponseBody> call = githubService.searchRepoInfo("changmu175");
複製程式碼
我們呼叫retrofit的create方法就可以把我們定義的介面轉化成實現類,我們可以直接呼叫我們定義的方法進行網路請求,但是我們只定義了一個介面方法,也沒有方法體,請求方式和引數型別都是註解,create是如何幫我們整理引數,實現方法體的呢?一會我們通過原始碼解析再去了解。
- 發起網路請求
//同步請求方式
call.request();
//非同步請求方式
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
//請求成功回撥
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
//請求與失敗回撥
}
});
複製程式碼
至此,retrofit的一次網路請求示例已經結束,基於對okhttp的封裝,讓網路請求已經簡化了很多。當然retrofit最適合的還是REST API型別的介面,方便簡潔。
下面我們就看看retrofit的核心工作是如何完成的!
二.retrofit初始化
retrofit的初始化採用了鏈式呼叫的設計
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
複製程式碼
很明顯這個方法是在傳一些需要的引數,我們簡單的跟蹤一下:
首先看看Builder()的原始碼:
public Builder() {
this(Platform.get());
}
複製程式碼
這句程式碼很簡單就是呼叫了自己的另一個建構函式:
Builder(Platform platform) {
this.platform = platform;
}
複製程式碼
這個建構函式也很簡單,就是一個賦值,我們把之前的Platform.get()點開,看看裡面做在什麼:
private static final Platform PLATFORM = findPlatform();
static Platform get() {
return PLATFORM;
}
複製程式碼
我們發現這裡使用使用了一個餓漢式單例,使用Platform.get()返回一個例項,這樣寫的好處是簡單,執行緒安全,效率高,不會生成多個例項!
我們再看看findPlatform() 裡做了什麼:
private static Platform findPlatform() {
try {
Class.forName("android.os.Build");
if (Build.VERSION.SDK_INT != 0) {
return new Android();
}
} catch (ClassNotFoundException ignored) {
}
....省略部分程式碼...
}
複製程式碼
所以是判斷了一下系統,然後根據系統例項化一個物件。這裡面應該做了一些和Android平臺相關的事情,屬於細節,我們追究,感興趣的可以只看看。
再看看baseUrl(url)的原始碼
public Builder baseUrl(String baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
HttpUrl httpUrl = HttpUrl.parse(baseUrl);
....
return baseUrl(httpUrl);
}
public Builder baseUrl(HttpUrl baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
....
this.baseUrl = baseUrl;
return this;
}
複製程式碼
這兩段程式碼也很簡單,校驗URL,生成httpUrl物件,然後賦值給baseUrl
看看build() 方法在做什麼
引數基本設定完了,最後就要看看build() 這個方法在做什麼:
public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
}
....
return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}
}
}
複製程式碼
程式碼中有大量的引數校驗,有些複雜的引數我們沒有傳,所以我就把那些程式碼刪除了。簡單看一下也能知道,這段程式碼就是做一些引數校驗,baseUrl不能為空否則會拋異常,至於其他的引數如果為null則會建立預設的物件。其中callFactory就是okhttp的工廠例項,用於網路請求的。
最後我們看到,這個方法最終返回的是一個Retrofit的物件,初始化完成。
三.生成介面實現類
剛才我們就講過retrofit.create這個方法很重要,它幫我們生成了介面實現類,並完成了方法體的建立,省去了我們很多工作量。那我們來看看它是如何幫我們實現介面的。
public <T> T create(final Class<T> 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, @Nullable 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<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
}
});
}
複製程式碼
這段程式碼實際上是使用了動態代理的設計模式,而且這個方法封裝的非常好,我們只需要呼叫 方法就可以獲得我們需要的實現類,遵循了迪米特法則(最少知道原則)。
瞭解動態代理的人都知道我們要重寫Object invoke(Object proxy, Method method,[@Nullable](https://xiaozhuanlan.com/u/undefined) Object[] args)
方法,這個方法會傳入我們需要的實現的方法,和引數,並返回我們需要的返回值。
retrofit在重寫這個方法的時候做了三件事:
- 先判斷了這個方法的類是不是一個Object.class),就直接返回方法原有的返回值。
- 判斷這個方法是不是DefaultMethod,大家都知道這個方法是Java 8出來的新屬性,表示介面的方法體。
- 構建一個ServiceMethod<Object, Object>物件和OkHttpCall<Object>物件,並呼叫
serviceMethod.adapt(okHttpCall)方法將二者繫結。
我們看看這個方法的原始碼:
T adapt(Call<R> call) {
return callAdapter.adapt(call);
}
複製程式碼
這個callAdapter我們在初始化retrofit的時候沒有使用:
addCallAdapterFactory(CallAdapterFactory)傳值,所以這裡是預設的DefaultCallAdapterFactory
那我們再看看DefaultCallAdapterFactory裡的adapt(call)方法:
@Override public Call<Object> adapt(Call<Object> call) {
return call;
}
複製程式碼
直接返回引數,也就是OkHttpCall<Object>的物件。所以如果沒有自定義callAdapter的時候,我們定義介面的時候返回值型別應該是個Call型別的。
那麼,至此這個create方法已經幫我們實現了我們定義的介面,並返回我們需要的值。
由於文字過長的緣由,我們暫且分為上下兩文,下文講講到請求引數整理丶Retrofit網路請求已經自己的一些總結,當然凡事無絕對,只是自己Retrofit原理的一些看法
文章開頭說的原始碼領取方式:關注我私信【資料】