Android進階:7、Retrofit2.0原理解析之最簡流程

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

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

一.retrofit入門

  • 定義網絡請求的API接口:
interface GithubApiService {
        @GET("users/{name}/repos")
        Call<ResponseBody> searchRepoInfo(@Path("name") String name);
    }
複製代碼

使用了註解代表請求方式,和參數類型,這是retrofit的特性,也正是簡化了咱們的網絡請求過程的地方!git

  • 初始化一個retrofit的實例:
Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.github.com/")
                .build();
複製代碼

retrofit的實例化很簡單,採用鏈式調用的設計,把須要的參數傳進去便可,複雜的參數咱們這裏就不舉例了。github

  • 生成接口實現類:
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類型的接口,方便簡潔。api

下面咱們就看看retrofit的核心工做是如何完成的!數組

二.retrofit初始化

retrofit的初始化採用了鏈式調用的設計安全

Retrofit retrofit = new Retrofit.Builder()
                       .baseUrl("https://api.github.com/")
                       .build();
複製代碼

很明顯這個方法是在傳一些須要的參數,咱們簡單的跟蹤一下:性能優化

首先看看Builder()的源碼:bash

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方法已經幫咱們實現了咱們定義的接口,並返回咱們須要的值。

四.請求參數整理

咱們定義的接口已經被實現,可是咱們仍是不知道咱們註解的請求方式,參數類型等是如何發起網絡請求的呢?
這時咱們可能應該關注一下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];
複製代碼

而後遍歷取出參數的類型:

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
          + ") doesn't 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想要進階的同窗來講頗有好處,不只教會咱們如何設計代碼更多的是解決思想。

喜歡本篇文章的話點個讚的哦

若是喜歡個人文章,想與一羣資深開發者一塊兒交流學習的話,歡迎加入個人合做羣Android Senior Engineer技術交流羣。有flutter—性能優化—移動架構—資深UI工程師 —NDK相關專業人員和視頻教學資料

後續會整理出有關Retrofit相關資料分享

羣號:925019412
相關文章
相關標籤/搜索