Retrofit
是 square
公司的另一款廣泛流行的網路請求框架。前面的一篇文章《原始碼分析OKHttp執行過程》已經對 OkHttp
網路請求框架有一個大概的瞭解。今天同樣地對 Retrofit
的原始碼進行走讀,對其底層的實現邏輯做到心中有數。
0x00 基本用法
Retrofit
的專案地址為:github.com/square/retr…
開啟專案目錄下的 samples
資料夾,從這裡可以瀏覽 Retrofit
專案的使用範例。
在本文中開啟SimpleService.java
這個類作為原始碼走讀的入口。這個類很簡單,展示了 Retrofit
的基本用法
public final class SimpleService {
//定義介面請求地址
public static final String API_URL = "https://api.github.com";
//定義介面返回資料的實體類
public static class Contributor {
public final String login;
public final int contributions;
public Contributor(String login, int contributions) {
this.login = login;
this.contributions = contributions;
}
}
//定義網路請求介面
public interface GitHub {
//這個是請求github專案程式碼貢獻者列表的介面
//使用@GET註解指定GET請求,並指定介面請求路徑,使用大括號{}定義的引數,是形參,retrofit會把方法中的
//@Path 傳入到請求路徑中
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> contributors(
@Path("owner") String owner,
@Path("repo") String repo);
}
public static void main(String... args) throws IOException {
// 建立一個retrofit,並且指定了介面的baseUrl
// 然後設定了一個gson轉換器,用於將介面請求下來的json字串轉換為Contributor實體類。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
// 這裡是魔法所在,retrofit將程式猿定義的介面變成“實現類”
GitHub github = retrofit.create(GitHub.class);
//通過retrofit這個“實現類”執行contributors方法
Call<List<Contributor>> call = github.contributors("square", "retrofit");
// 執行Call類中的execute方法,這是一個同步方法
// 當然跟okhttp一樣,非同步方法是enqueue,這個下文會提到
List<Contributor> contributors = call.execute().body();
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
}
複製程式碼
通過上面程式碼的閱讀,知道 retrofit
使用流程
- 定義
API
- 構造介面資料實體類
- 構造
retrofit
物件,指定baseUrl
和資料轉換器(即介面資料解析器,如對json
、xml
、protobuf
等資料型別的解析) - 通過
retrofit
將程式猿定義的API
介面變成"實現類" - 執行“實現類”的方法
- 執行網路請求,獲取介面請求資料
這個流程關鍵點是4、5、6,下文將詳細對這幾個步驟的原始碼進行閱讀。
在繼續下文之前,我們先看看這個SimpleService
的執行結果,它列印了retrofit
這個專案的程式碼貢獻者
JakeWharton (928)
swankjesse (240)
pforhan (48)
eburke (36)
dnkoutso (26)
NightlyNexus (26)
edenman (24)
loganj (17)
Noel-96 (16)
rcdickerson (14)
rjrjr (13)
kryali (9)
adriancole (9)
holmes (7)
swanson (7)
JayNewstrom (6)
crazybob (6)
Jawnnypoo (6)
danrice-square (5)
vanniktech (5)
Turbo87 (5)
naturalwarren (5)
guptasourabh04 (4)
artem-zinnatullin (3)
codebutler (3)
icastell (3)
jjNford (3)
f2prateek (3)
PromanSEW (3)
koalahamlet (3)
複製程式碼
0x01 構造過程
從上文的原始碼閱讀中,可以看出程式猿只是定義了一個介面,但是現在實現介面的工作是由 retrofit
來實現的
GitHub github = retrofit.create(GitHub.class);
Call<List<Contributor>> call = github.contributors("square", "retrofit");
複製程式碼
create
開啟 retrofit.create
方法
public <T> T create(final Class<T> service) {
//對介面進行校驗
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
//通過Proxy建立了一個代理
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@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);
}
//判斷是否為預設方法,Java8中介面也可以有預設方法,所以這裡有這個判斷
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
//關鍵點
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}
複製程式碼
這個方法很短,關鍵是通過 Proxy
建立了一個 Github
介面的代理類並返回該代理。
newProxyInstance
方法需要3個引數:ClassLoader
、Class<?>
陣列、InvocationHandler
回撥。
這個 InvocationHandler
非常關鍵,當執行介面 Github
的contributors
方法時,會委託給InvocationHandler
的invoke
方法來執行。即Github
將介面代理給了Proxy
來執行了。
InvocationHandler
接著看InvocationHandler
介面的實現。
在 invoke
方法中有三個引數,其中proxy
就是代理物件,而 method
就是程式猿定義的那個網路請求介面,顧名思義 args
就是方法的引數。
此方法最終是呼叫了
loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
複製程式碼
loadServiceMethod
開啟 loadServiceMethod
方法
ServiceMethod<?> loadServiceMethod(Method method) {
// 判斷是否有快取
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
//同步處理
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
//沒有獲取到快取則使用`ServiceMethod`方法來建立
result = ServiceMethod.parseAnnotations(this, method);
//最後快取起來
serviceMethodCache.put(method, result);
}
}
return result;
}
複製程式碼
這個方法就是通過 method
來獲取一個 ServiceMethod
物件。
ServiceMethod
開啟 ServiceMethod
發現它是一個抽象類,有一個靜態方法 parseAnnotations
和一個抽象方法 invoke
。
abstract class ServiceMethod<T> {
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
//對註解進行解析
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
//獲取方法的返回型別
Type returnType = method.getGenericReturnType();
//對返回型別進行校驗
if (Utils.hasUnresolvableType(returnType)) {
throw methodError(method,
"Method return type must not include a type variable or wildcard: %s", returnType);
}
if (returnType == void.class) {
throw methodError(method, "Service methods cannot return void.");
}
//最終使用到HttpServiceMethod類
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
abstract T invoke(Object[] args);
}
複製程式碼
parseAnnotations
方法就是對程式猿定義的介面中使用的註解進行解析。
最後是使用了HttpServiceMethod.parseAnnotations
方法
HttpServiceMethod
/** Adapts an invocation of an interface method into an HTTP call. */
final class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method);
//...省略部分程式碼
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
return new HttpServiceMethod<>(requestFactory, callFactory, callAdapter, responseConverter);
}
//...省略部分程式碼
@Override ReturnT invoke(Object[] args) {
return callAdapter.adapt(
new OkHttpCall<>(requestFactory, args, callFactory, responseConverter));
}
}
複製程式碼
HttpServiceMethod
是 ServiceMethod
的子類。而在parseAnnotations
方法中構造了HttpServiceMethod
例項並返回。
因此,loadServiceMethod
方法返回的是HttpServiceMehod
物件
這樣下面程式碼的執行實際上是執行了 HttpServiceMehod
的 invoke
方法。
loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
複製程式碼
再次翻看上文中HttpServiceMethod
類
@Override ReturnT invoke(Object[] args) {
return callAdapter.adapt(
new OkHttpCall<>(requestFactory, args, callFactory, responseConverter));
}
複製程式碼
invoke
方法裡有執行了callAdapter.adapt
方法,引數為OkHttpCall
,這個類實際上就是對okhttp
網路請求的封裝,這裡也可以看出**retrofit
內部是使用了okhttp
來執行網路請求的**
CallAdapter
public interface CallAdapter<R, T> {
//..省略部分程式碼
T adapt(Call<R> call);
//CallAdapter抽象工廠類
abstract class Factory {
//返回CallAdapter例項
public abstract @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
Retrofit retrofit);
//..省略部分程式碼
}
}
複製程式碼
這是一個介面,內部有一個Factory
抽象工廠類,用於獲取CallAdapter
物件。
CallAdapter
有很多子類,那 callAdapter.adapt
方法執行的是哪個具體類的方法呢?實際上,從除錯程式碼中可以發現是呼叫DefaultCallFactory
中的內部實現類
DefaultCallAapterFactory
final class DefaultCallAdapterFactory extends CallAdapter.Factory {
static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();
@Override public @Nullable CallAdapter<?, ?> get(
Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
}
final Type responseType = Utils.getCallResponseType(returnType);
//返回一個CallAapter例項
return new CallAdapter<Object, Call<?>>() {
@Override public Type responseType() {
return responseType;
}
@Override public Call<Object> adapt(Call<Object> call) {
//將引數返回,而這個引數就是OKHttpCall的例項
return call;
}
};
}
}
複製程式碼
可以發現,在adapt
方法中就是將引數call
返回。
所以下面程式碼返回的是OkHttpCall
物件。
loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
複製程式碼
綜上
//建立了Github介面的代理類
GitHub github = retrofit.create(GitHub.class);
//執行介面的方法,其實就是呼叫了代理類的方法,並最終返回了一個OKhttpCall物件
//而這個物件就是對Okhttp的封裝
Call<List<Contributor>> call = github.contributors("square", "retrofit");
複製程式碼
0x02 執行結果
上文中獲取到OKhttpCall
物件,它只是把介面請求過程進行了封裝,並沒有真正的獲取到介面資料。要獲取到介面資料還需要呼叫OkHttpCall.execute
方法
List<Contributor> contributors = call.execute().body();
複製程式碼
Call.execute 或 Call.enqueue
這裡的請求過程與前文中《原始碼分析OKHttp執行過程》介紹的是類似的。接一下
開啟OkHttpCall.execute
方法
@Override public Response<T> execute() throws IOException {
okhttp3.Call call;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
if (creationFailure != null) {
if (creationFailure instanceof IOException) {
throw (IOException) creationFailure;
} else if (creationFailure instanceof RuntimeException) {
throw (RuntimeException) creationFailure;
} else {
throw (Error) creationFailure;
}
}
call = rawCall;
if (call == null) {
try {
call = rawCall = createRawCall();
} catch (IOException | RuntimeException | Error e) {
throwIfFatal(e); // Do not assign a fatal error to creationFailure.
creationFailure = e;
throw e;
}
}
}
if (canceled) {
call.cancel();
}
return parseResponse(call.execute());
}
複製程式碼
這裡的執行邏輯也很簡單
- 使用
synchronized
進行同步操作 - 進行異常處理
- 呼叫
createRawCall
建立okhttp3.Call
物件 - 執行
okhttp
的Call.execute
方法,並解析response
後返回請求結果
同樣地,非同步請求操作也是類似的
開啟OkHttpCall.enqueue
方法
@Override public void enqueue(final Callback<T> callback) {
checkNotNull(callback, "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 {
//建立okhttp網路請求
call = rawCall = createRawCall();
} catch (Throwable t) {
throwIfFatal(t);
failure = creationFailure = t;
}
}
}
if (failure != null) {
callback.onFailure(this, failure);
return;
}
if (canceled) {
call.cancel();
}
//最終是執行了OkHttp中的call.enqueue方法
//並回撥相應的介面
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
response = parseResponse(rawResponse);
} catch (Throwable e) {
throwIfFatal(e);
callFailure(e);
return;
}
try {
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
t.printStackTrace();
}
}
@Override public void onFailure(okhttp3.Call call, IOException e) {
callFailure(e);
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
t.printStackTrace();
}
}
});
}
複製程式碼
這個方法其實最終都是執行了okhttp
的相應方法。
0x03 總結
Retrofit
其實一種更加高階的網路應用框架,通過代理模式簡化了介面的定義,無需提供介面的具體實現就可以完成網路介面請求的執行。它的底層實際上是封裝了 okhttp
的執行過程,也把對網路的操作進行了封裝,而對於程式猿來說只需要關注業務邏輯,對網路請求的具體實現不必關心。
例如在本文開頭的例項中我們只需要定義介面,定義實體類,其他工作都交給了 Retrofit
,接下來就是Magic
。