1. 程式人生 > >源碼分析Retrofit請求流程

源碼分析Retrofit請求流程

tro 默認 抽象工廠 同步方法 str 內部實現 .get ltm 應用

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

0x00 基本用法

Retrofit 的項目地址為:https://github.com/square/retrofit

打開項目目錄下的 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 使用流程

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

技術分享圖片

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

源碼分析Retrofit請求流程