Retrofit原理解析最簡潔的思路
文章修改了一下,最近把源碼擼了好幾遍,感覺現在這樣寫才是思路最簡單的,歡迎大家找茬。
retrofit 已經流行很久了,它是Square開源的一款優秀的網路框架,這個框架對okhttp進行了封裝,讓我們使用okhttp做網路請求更加簡單。但是光學會使用只是讓我們多了一個技能,學習其源碼才能讓我們更好的成長。
本篇文章是在分析retrofit的源碼流程,有大量的代碼,讀者最好把源碼下載下來導入IDE,然後跟著一起看,效果會更好
square/retrofitretrofit入門
- 定義網路請求的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.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 Object[] args)
方法,這個方法會傳入我們需要的實現的方法,和參數,並返回我們需要的返回值。
- 1、先判斷了這個方法的類是不是一個
Object.class)
,就直接返回方法原有的返回值。 - 2、判斷這個方法是不是
DefaultMethod
,大家都知道這個方法是Java 8出來的新屬性,表示介面的方法體。 - 3、構建一個
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
方法已經幫我們實現了我們定義的介面,並返回我們需要的值。
請求參數整理
我們定義的介面已經被實現,但是我們還是不知道我們註解的請求方式,參數類型等是如何發起網路請求的呢?
這時我們可能應該關注一下ServiceMethod<Object, Object>
對象的構建了:ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method);
主要的邏輯都在這個loadServiceMethod(method)
裡面,我們看看方法體:
ServiceMethod<?, ?> loadServiceMethod(Method method) { ServiceMethod<?, ?> result = serviceMethodCache.get(method); if (result != null) return result; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); if (result == null) { result = new ServiceMethod.Builder<>(this, method).build(); serviceMethodCache.put(method, result); } } return result; }
邏輯很簡單,就是先從一個 serviceMethodCache
中取ServiceMethod<?, ?>
對象,如果沒有,則構建ServiceMethod<?, ?>
對象,然後放進去serviceMethodCache
中,這個serviceMethodCache
是一個HashMap
:
private final Map<Method, ServiceMethod<?, ?>> serviceMethodCache = new ConcurrentHashMap<>();
所以構建ServiceMethod<?, ?>
對象的主要邏輯還不在這個方法里,應該在new ServiceMethod.Builder<>(this, method).build();
裡面。這也是個鏈式調用,一般都是參數賦值,我們先看看Builder<>(this, method)
方法:
Builder(Retrofit retrofit, Method method) { this.retrofit = retrofit; this.method = method; this.methodAnnotations = method.getAnnotations(); this.parameterTypes = method.getGenericParameterTypes(); this.parameterAnnotationsArray = method.getParameterAnnotations(); }
果然,這裡獲取了幾個重要的參數:
retrofit
實例method
,介面方法- 介面方法的註解
methodAnnotations
,在retrofit
里一般為請求方式 - 參數類型
parameterTypes
- 參數註解數組
parameterAnnotationsArray
,一個參數可能有多個註解
我們再看看build()
的方法:
public ServiceMethod build() { callAdapter = createCallAdapter(); responseType = callAdapter.responseType(); responseConverter = createResponseConverter(); for (Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); } if (httpMethod == null) { throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.)."); } int parameterCount = parameterAnnotationsArray.length; parameterHandlers = new ParameterHandler<?>[parameterCount]; for (int p = 0; p < parameterCount; p++) { Type parameterType = parameterTypes[p]; if (Utils.hasUnresolvableType(parameterType)) { throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s", parameterType); } Annotation[] parameterAnnotations = parameterAnnotationsArray[p]; if (parameterAnnotations == null) { throw parameterError(p, "No Retrofit annotation found."); } parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations); } return new ServiceMethod<>(this); }
這個方法挺長的,刪了些無關緊要的代碼還是很長。首先一開始先獲取幾個重要對象:callAdapter
、responseType
和responseConverter
,這三個對象都跟最後的結果有關,我們先不管。
看到一個for
循環,遍歷方法的註解,然後解析:
for (Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); }private void parseMethodAnnotation(Annotation annotation) { if (annotation instanceof DELETE) { parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false); } else if (annotation instanceof GET) { parseHttpMethodAndPath("GET", ((GET) annotation).value(), false); } ....
這個方法的方法體我刪掉了後面的一部分,因為邏輯都是一樣,根據不同的方法註解作不同的解析,得到網路請求的方式httpMethod
。但是主要的方法體還是if
裡面的方法:
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) { .... // Get the relative URL path and existing query string, if present. int question = value.indexOf(?); if (question != -1 && question < value.length() - 1) { // Ensure the query string does not have any named parameters. String queryParams = value.substring(question + 1); Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams); if (queryParamMatcher.find()) { throw methodError("URL query string "%s" must not have replace block. " + "For dynamic query parameters use @Query.", queryParams); } } this.relativeUrl = value; this.relativeUrlParamNames = parsePathParameters(value); }
邏輯不複雜,就是校驗這個value
的值 是否合法,規則就是不能有「?」如果有則需要使用@Query
註解。最後this.relativeUrl = value;
。這個relativeUrl
就相當於省略域名的URL,一般走到這裡我們能得到的是:users/{name}/repos
這樣的。裡面的「{name}」是一會我們需要賦值的變數。
我們繼續看剛才的build()
方法:
parameterHandlers = new ParameterHandler<?>[parameterCount];
然後遍歷取出參數的類型:
Type parameterType = parameterTypes[p];
取出參數註解:
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
然後把參數類型、參數註解都放在一起進行解析,解析的結果放到剛才實例化的數組parameterHandlers
裡面:
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
那我們再看看這個方法里做了什麼:
private ParameterHandler<?> parseParameter(int p, Type parameterType, Annotation[] annotations) { ParameterHandler<?> result = null; for (Annotation annotation : annotations) { ParameterHandler<?> annotationAction = parseParameterAnnotation( p, parameterType, annotations, annotation); } }
這個方法的主要代碼也很簡單,解析參數註解,得到一個ParameterHandler<?> annotationAction
對象。
那我繼續看方法裡面的代碼。當我們點進parseParameterAnnotation( p, parameterType, annotations, annotation);
的源碼裡面去之後發現這個方法的代碼接近500行!但是大部分邏輯類似,都是通過if else
判斷參數的註解,我們取一段我們剛才的例子相關的代碼出來:
if (annotation instanceof Path) { if (gotQuery) { throw parameterError(p, "A @Path parameter must not come after a @Query."); } if (gotUrl) { throw parameterError(p, "@Path parameters may not be used with @Url."); } if (relativeUrl == null) { throw parameterError(p, "@Path can only be used with relative url on @%s", httpMethod); } gotPath = true; Path path = (Path) annotation; String name = path.value(); validatePathName(p, name); Converter<?, String> converter = retrofit.stringConverter(type, annotations); return new ParameterHandler.Path<>(name, converter, path.encoded()); }
前面做了一些校驗,後面取出註解的名字:name
,然後用正則表達校驗這個name
是否合法。然後構建一個Converter<?, String>對象
:
Converter<?, String> converter = retrofit.stringConverter(type, annotations);
點擊去看看:
public <T> Converter<T, String> stringConverter(Type type, Annotation[] annotations) { .... for (int i = 0, count = converterFactories.size(); i < count; i++) { Converter<?, String> converter = converterFactories.get(i).stringConverter(type, annotations, this); if (converter != null) { //noinspection unchecked return (Converter<T, String>) converter; } } return (Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE; }
看到核心代碼是converter
的stringConverter(type, annotations, this)
方法:
addConverterFactory(ConverterFactory)
添加一個ConverterFactory
,所以這裡會返回一個空:public @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; }
所以最後會執行最後一句代碼: return (Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE;
INSTANCE
:static final ToStringConverter INSTANCE = new ToStringConverter();
是BuiltInConverters
內的內部類ToStringConverter
的單例。所以這裡我們得到的就
BuiltInConverters.ToStringConverter
的實例。最後用這個對象構建一個Path
(因為示例中的參數類型是path,所以我們看這個代碼):
new ParameterHandler.Path<>(name, converter, path.encoded());
我們看看這個Path
類的構造函數:
Path(String name, Converter<T, String> valueConverter, boolean encoded) { this.name = checkNotNull(name, "name == null"); this.valueConverter = valueConverter; this.encoded = encoded; }
只是賦值,並且我們看到這個類繼承自:ParameterHandler<T>
,所以我們回到剛才的build()
方法,發現把參數類型,參數註解放在一起解析之後存儲到了這個ParameterHandler<T>
數組中,中間主要做了多種合法性校驗,並根據註解的類型,生成不同的 ParameterHandler<T>
子類,如註解是Url
則生成ParameterHandler.RelativeUrl()
對象,如果註解是Path
,則生成: ParameterHandler.Path<>(name, converter, path.encoded())
對象等等。
ParameterHandler<T>
類,發現它有一個抽象方法:abstract void apply(RequestBuilder builder, @Nullable T value) throws IOException;
這個方法每個子類都必須複寫,那我們看看Path
裡面怎麼複寫的:
@Override void apply(RequestBuilder builder, @Nullable T value) throws IOException { builder.addPathParam(name, valueConverter.convert(value), encoded); }
就是把value
被添加到RequestBuilder
中,我們看一下這個addPathParam
方法:
void addPathParam(String name, String value, boolean encoded) { relativeUrl = relativeUrl.replace("{" + name + "}", canonicalizeForPath(value, encoded)); }
這個方法把我們傳進來的值value
按照編碼格式轉換,然後替換relativeUrl
中的{name}
,構成一個有效的省略域名的URL。至此,URL的拼接已經完成!
總結:Retrofit使用動態代理模式實現我們定義的網路請求介面,在重寫invoke方法的時候構建了一個ServiceMethod對象,在構建這個對象的過程中進行了方法的註解解析得到網路請求方式httpMethod
,以及參數的註解分析,拼接成一個省略域名的URL
Retrofit網路請求
我們剛才解析了apply
方法,我們看看apply方法是誰調用的呢?跟蹤一下就發先只有toCall(args);
方法:
okhttp3.Call toCall(@Nullable Object... args) throws IOException { RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers, contentType, hasBody, isFormEncoded, isMultipart); @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types. ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers; int argumentCount = args != null ? args.length : 0; if (argumentCount != handlers.length) { throw new IllegalArgumentException("Argument count (" + argumentCount + ") doesnt match expected count (" + handlers.length + ")"); } for (int p = 0; p < argumentCount; p++) { handlers[p].apply(requestBuilder, args[p]); } return callFactory.newCall(requestBuilder.build()); }
這個方法一開始就構建了RequestBuilder
,傳進去的參數包含: httpMethod,baseUrl,relativeUrl,headers,contentType,hasBody,isFormEncoded,isMultipart
!
然後獲取了parameterHandlers
,我們上邊分析的時候,知道這個數組是存參數註解的解析結果的,並對其進行遍歷調用了如下方法:
for (int p = 0; p < argumentCount; p++) { handlers[p].apply(requestBuilder, args[p]); }
把參數值傳進RequestBuilder
中。
callFactory.newCall(requestBuilder.build())
生成一個okhttp3.Call
。 我們看一下這個build
方法:Request build() { HttpUrl url; HttpUrl.Builder urlBuilder = this.urlBuilder; if (urlBuilder != null) { url = urlBuilder.build(); } else { // No query parameters triggered builder creation, just combine the relative URL and base URL. //noinspection ConstantConditions Non-null if urlBuilder is null. url = baseUrl.resolve(relativeUrl); if (url == null) { throw new IllegalArgumentException( "Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl); } } RequestBody body = this.body; if (body == null) { // Try to pull from one of the builders. if (formBuilder != null) { body = formBuilder.build(); } else if (multipartBuilder != null) { body = multipartBuilder.build(); } else if (hasBody) { // Body is absent, make an empty body. body = RequestBody.create(null, new byte[0]); } } MediaType contentType = this.contentType; if (contentType != null) { if (body != null) { body = new ContentTypeOverridingRequestBody(body, contentType); } else { requestBuilder.addHeader("Content-Type", contentType.toString()); } } return requestBuilder .url(url) .method(method, body) .build(); }
可以看到okhttp的請求體在這裡構建,當所有的參數滿足的時候,則調用了
Request.Builder requestBuilder .url(url) .method(method, body) .build();
這是發起okhttp的網路請求 。
那這個toCall(args);
誰調用的呢?繼續往回跟!private okhttp3.Call createRawCall() throws IOException { okhttp3.Call call = serviceMethod.toCall(args); return call; }
那誰調用了createRawCall()
呢?繼續看誰調用了!於是發現調用方有三個地方,並且都是OkHttpCall
裡面!我們一個一個看吧:
- Request request()方法:
- enqueue(final Callback callback)方法
- Response execute()的方法
很明顯上面三個方法都是retrofit的發起網路請求的方式,分別是同步請求和非同步請求。我們的示例中在最後一步就是調用了request
方法和enqueue
方法發起網路請求。至此我們已經疏通了retrofit是如何進行網路請求的了。
總結:當我們調用Retrofit的網路請求方式的時候,就會調用okhttp的網路請求方式,參數使用的是實現介面的方法的時候拿到的信息構建的RequestBuilder
對象,然後在build
方法中構建okhttp的Request
,最終發起網路請求
總結
至此retrofit的流程講完了,文章很長,代碼很多,讀者最好下載代碼導入IDE,跟著文章一起看代碼。
Retrofit主要是在create
方法中採用動態代理模式實現介面方法,這個過程構建了一個ServiceMethod對象,根據方法註解獲取請求方式,參數類型和參數註解拼接請求的鏈接,當一切都準備好之後會把數據添加到Retrofit的RequestBuilder
中。然後當我們主動發起網路請求的時候會調用okhttp發起網路請求,okhttp的配置包括請求方式,URL等在Retrofit的RequestBuilder
的build()
方法中實現,並發起真正的網路請求。
Retrofit封裝了okhttp框架,讓我們的網路請求更加簡潔,同時也能有更高的擴展性。當然我們只是窺探了Retrofit源碼的一部分,他還有更複雜更強大的地方等待我們去探索包括返回值轉換工廠,攔截器等,這些都屬於比較難的地方,我們需要循序漸進的去學習,當我們一點一點的看透框架的本質之後,我們使用起來才會熟能生巧。大神的代碼,對於Android想要進階的同學來說很有好處,不僅教會我們如何設計代碼更多的是解決思想。
最後大家支持歡迎關注公眾號:碼老闆
http://weixin.qq.com/r/liqYgFfEBQp6rRPj93_E (二維碼自動識別)
推薦閱讀: