Retrofit原理解析最簡潔的思路

文章修改了一下,最近把源碼擼了好幾遍,感覺現在這樣寫才是思路最簡單的,歡迎大家找茬。


retrofit 已經流行很久了,它是Square開源的一款優秀的網路框架,這個框架對okhttp進行了封裝,讓我們使用okhttp做網路請求更加簡單。但是光學會使用只是讓我們多了一個技能,學習其源碼才能讓我們更好的成長。

本篇文章是在分析retrofit的源碼流程,有大量的代碼,讀者最好把源碼下載下來導入IDE,然後跟著一起看,效果會更好

square/retrofit?

github.com圖標

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 Object[] args) 方法,這個方法會傳入我們需要的實現的方法,和參數,並返回我們需要的返回值。

retrofit在重寫這個方法的時候做了三件事:

  • 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); }

這個方法挺長的,刪了些無關緊要的代碼還是很長。首先一開始先獲取幾個重要對象:callAdapterresponseTyperesponseConverter,這三個對象都跟最後的結果有關,我們先不管。

看到一個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; }

看到核心代碼是converterstringConverter(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裡面!我們一個一個看吧:

  1. Request request()方法:
  2. enqueue(final Callback callback)方法
  3. Response execute()的方法

很明顯上面三個方法都是retrofit的發起網路請求的方式,分別是同步請求和非同步請求。我們的示例中在最後一步就是調用了request方法和enqueue方法發起網路請求。至此我們已經疏通了retrofit是如何進行網路請求的了。

總結:當我們調用Retrofit的網路請求方式的時候,就會調用okhttp的網路請求方式,參數使用的是實現介面的方法的時候拿到的信息構建的RequestBuilder對象,然後在build方法中構建okhttp的Request,最終發起網路請求

總結

至此retrofit的流程講完了,文章很長,代碼很多,讀者最好下載代碼導入IDE,跟著文章一起看代碼。

Retrofit主要是在create方法中採用動態代理模式實現介面方法,這個過程構建了一個ServiceMethod對象,根據方法註解獲取請求方式,參數類型和參數註解拼接請求的鏈接,當一切都準備好之後會把數據添加到Retrofit的RequestBuilder中。然後當我們主動發起網路請求的時候會調用okhttp發起網路請求,okhttp的配置包括請求方式,URL等在Retrofit的RequestBuilderbuild()方法中實現,並發起真正的網路請求。

Retrofit封裝了okhttp框架,讓我們的網路請求更加簡潔,同時也能有更高的擴展性。當然我們只是窺探了Retrofit源碼的一部分,他還有更複雜更強大的地方等待我們去探索包括返回值轉換工廠,攔截器等,這些都屬於比較難的地方,我們需要循序漸進的去學習,當我們一點一點的看透框架的本質之後,我們使用起來才會熟能生巧。大神的代碼,對於Android想要進階的同學來說很有好處,不僅教會我們如何設計代碼更多的是解決思想。

最後大家支持歡迎關注公眾號:碼老闆

weixin.qq.com/r/liqYgFf (二維碼自動識別)

推薦閱讀:

TAG:Android開發 | Retrofit | 源碼閱讀 |