原始碼分析Retrofit請求流程

陽仔Young發表於2018-11-04

Retrofitsquare 公司的另一款廣泛流行的網路請求框架。前面的一篇文章《原始碼分析OKHttp執行過程》已經對 OkHttp 網路請求框架有一個大概的瞭解。今天同樣地對 Retrofit 的原始碼進行走讀,對其底層的實現邏輯做到心中有數。

0x00 基本用法

Retrofit 的專案地址為:github.com/square/retr…

開啟專案目錄下的 samples 資料夾,從這裡可以瀏覽 Retrofit 專案的使用範例。

原始碼分析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 使用流程

  1. 定義 API
  2. 構造介面資料實體類
  3. 構造 retrofit 物件,指定baseUrl和資料轉換器(即介面資料解析器,如對jsonxmlprotobuf等資料型別的解析)
  4. 通過 retrofit 將程式猿定義的 API 介面變成"實現類"
  5. 執行“實現類”的方法
  6. 執行網路請求,獲取介面請求資料

這個流程關鍵點是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個引數:ClassLoaderClass<?>陣列、InvocationHandler 回撥。

這個 InvocationHandler 非常關鍵,當執行介面 Githubcontributors方法時,會委託給InvocationHandlerinvoke 方法來執行。即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));
  }
}
複製程式碼

HttpServiceMethodServiceMethod 的子類。而在parseAnnotations 方法中構造了HttpServiceMethod例項並返回。

因此,loadServiceMethod方法返回的是HttpServiceMehod物件

這樣下面程式碼的執行實際上是執行了 HttpServiceMehodinvoke 方法。

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中的內部實現類

原始碼分析Retrofit請求流程

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 物件
  • 執行 okhttpCall.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

相關文章