1. 程式人生 > >Retrofit2實現原始碼分析

Retrofit2實現原始碼分析

最近研究Retrofit2+RxJava實現網路請求及資料集處理,一部分的知識點就在Retrofit2,為了更好的理解程式碼,本人決定分析下Retrofit2原始碼,既然是分析原始碼就得帶著目的去分析,那麼說說本文要解決的問題吧,先看程式碼來說明

Retrofit retrofit = new Retrofit.Builder()
        .client(new OkHttpClient())
        .baseUrl(API_URL)
        .addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler
(Schedulers.io())) .addConverterFactory(GsonConverterFactory.create()) .build(); ZhuanLanApi api = retrofit.create(ZhuanLanApi.class); Call<ZhuanLanAuthor> call = api.getAuthor("qinchao"); call.enqueue(new Callback<ZhuanLanAuthor>() { @Override public void onResponse(Call
<ZhuanLanAuthor> call, Response<ZhuanLanAuthor> response) { System.out.println(response.body().getName()); } @Override public void onFailure(Call<ZhuanLanAuthor> call, Throwable t) { } });

以上程式碼就是單獨使用Retrofit2的基本方式,在寫該程式碼時,我有幾個疑惑,先列舉吧。

疑惑1call.enqueue
是怎麼發起網路請求的,和Okhttp3發起的網路請求為什麼這麼相似 疑惑2: 建立retrofit物件時client函式傳的OkHttpClient物件有何作用 疑惑3: addCallAdapterFactory函式傳參有起到什麼作用 疑惑4: addConverterFactory函式傳參起到什麼作用

下面就來解決這些疑惑

1、Retrofit2和OkHttp3的關係

實際上Retrofit2是用來解決Okhttp3用法複雜的問題的,先看看用OkHttp3實現一個非同步網路請求的程式碼

String url = "https://www.baidu.com/";
OkHttpClient okHttpClient = new OkHttpClient();
MediaType JSON = MediaType.parse("application/json;charset=utf-8");
RequestBody body = RequestBody.create(JSON, "{'name':'yjing','age':'12'}");
Request request = new Request.Builder()
        .post(body)
        .url(url)
        .build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        e.printStackTrace();
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        System.out.println("我是非同步執行緒,執行緒Id為:" + Thread.currentThread().getId());
    }
});

注意,這裡只是舉個例子,傳的引數是沒有意義的,從程式碼可以看出OkHttp3使用太複雜,每次呼叫都得配置手動配置引數,而Retrofit2就是對OkHttp3進行了封裝,使得能夠通過呼叫一個Java方法來發起網路請求,網路請求實際上還是由OkHttp3完成的,Retrofit2只是將Java方法反射成為對應的網路請求。

由OkHttp3的使用程式碼可知,使用OkHttp3發起網路請求使通過Call物件來完成的,而建立Call例項物件,需要OkHttpClient物件和Request物件(這裡需要注意)。

2、Retrofit2框架結構說明

在對Retrofit2原始碼進行解析之前,先說說其框架結構

這裡寫圖片描述

這裡的介面和類並不多

Converter:
負責轉換網路請求返回資料為對應格式資料的介面,其中GsonConverterFactory共廠類生產的GsonRequestBodyConverter類就是該介面的實現

CallAdapter:
負者決定retrofit.create()返回引數的介面,DefaultCallAdapterFactory以及RxJavaCallAdapterFactory連個工廠類生產的類都是該介面的實現

ServiceMethod:
這個類比較核心,幾乎儲存了一個API請求所有需要的資料,OkHttpCall需要從ServiceMethod中獲得一個Request物件,然後得到Response後,還需要傳入ServiceMethod用對應的Converter物件轉將Response資料換成資料格式

Retrofit:
生成Retrofit物件,並初始化上面的幾個介面實現類或ServiceMethod類物件

3、Retrofit類介紹

上面有程式碼說明Retrofit物件的初始化操作,這裡為了方便說明再貼一次

Retrofit retrofit = new Retrofit.Builder()
        .client(new OkHttpClient())
        .baseUrl(API_URL)
        .addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
        .addConverterFactory(GsonConverterFactory.create())
        .build();
ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);

通過build的一系列方法,完成了在Retrofit物件中例項化Convertor對應工廠類、CallAdapter對應工廠類以及OkHttpClient物件的初始化。

然後呼叫create方法

public <T> T create(final Class<T> service) {
  Utils.validateServiceInterface(service);
  if (validateEagerly) {
    eagerlyValidateMethods(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, 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 serviceMethod = loadServiceMethod(method);
          OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
          return serviceMethod.callAdapter.adapt(okHttpCall);
        }
      });
}

這裡的create方法中使用了動態代理,作用是當呼叫對應service方法時,會執行到動態代理程式碼中來,並且傳入service對應方法反射物件以及方法引數。而動態代理方法核心是最後三行程式碼,其中loadServiceMethod方法會例項化一個ServiceMethod物件,並例項化ServiceMethod物件中的一下幾個關鍵變數

下面先在三小節說說ServiceMethod方法的關鍵點

3、ServiceMethod類的關鍵點

ServiceMethod中有callAdapter變數、responseConverter變數、toRequest方法、toResponse方法這幾個關鍵點。

callAdapter變數

根據是否有呼叫addCallAdapterFactory方法來例項化,例如如果呼叫了addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))則根據該工廠類建立SimpleCallAdapter(這裡只是列舉了該工廠類能生產的其中一種)物件

 static final class SimpleCallAdapter implements CallAdapter<Observable<?>> {
    private final Type responseType;
    private final Scheduler scheduler;

    SimpleCallAdapter(Type responseType, Scheduler scheduler) {
      this.responseType = responseType;
      this.scheduler = scheduler;
    }

    @Override public Type responseType() {
      return responseType;
    }

    @Override public <R> Observable<R> adapt(Call<R> call) {
      Observable<R> observable = Observable.create(new CallOnSubscribe<>(call)) //
          .lift(OperatorMapResponseToBodyOrError.<R>instance());
      if (scheduler != null) {
        return observable.subscribeOn(scheduler);
      }
      return observable;
    }
  }

如果沒有呼叫addCallAdapterFactory方法則使用一下預設工廠類生成的CallAdapter物件。

final class DefaultCallAdapterFactory extends CallAdapter.Factory {
  static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();

  @Override
  public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }

    final Type responseType = Utils.getCallResponseType(returnType);
    return new CallAdapter<Call<?>>() {
      @Override public Type responseType() {
        return responseType;
      }

      @Override public <R> Call<R> adapt(Call<R> call) {
        return call;
      }
    };
  }
}

這裡一定熬注意兩種CallAdapter物件的adapt方法的差異,不同的CallAdapterFactory生產的CallAdapter類,其adapt方法的返回引數不一樣,DefaultCallAdapterFactory 對應的返回引數是Call,而SimpleCallAdapter 對應的返回引數是Observable。

responseConverter變數

該變數負責根據對應規則對Response資料進行格式轉化,在Retrofit類的nextResponseBodyConverter方法中可以看到,當有呼叫addConverterFactory(GsonConverterFactory.create())使用GsonConverterFactory或其它的工廠類生產的轉換器,從程式碼中可以看出當新增多個工廠類時,使用第一個工廠類生產的轉換器。

for (int i = start, count = converterFactories.size(); i < count; i++) {
  Converter<ResponseBody, ?> converter =
      converterFactories.get(i).responseBodyConverter(type, annotations, this);
  if (converter != null) {
    //noinspection unchecked
    return (Converter<ResponseBody, T>) converter;
  }
}

然後我們來看看GsonConverterFactory工廠類生產的物件

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
  private final Gson gson;
  private final TypeAdapter<T> adapter;

  GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
    this.gson = gson;
    this.adapter = adapter;
  }

  @Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      return adapter.read(jsonReader);
    } finally {
      value.close();
    }
  }
}

GsonResponseBodyConverter的convert方法會將ResponseBody 值轉化為對應Java方法的返回引數T型別,如下面的方法則轉化為ZhuanLanAuthor型別(解決疑惑4)。

Call<ZhuanLanAuthor> getAuthor(@Path("user") String user);

toResponse

T toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
}

這個方法將body資料轉化為對應引數資料。

toRequest

/** Builds an HTTP request from method arguments. */
Request toRequest(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 requestBuilder.build();
}

這個方法,提供OkHttp3發起的網路請求需要的Request物件。

4、再談Retrofit類的create方法

大致介紹完Retrofit類和ServiceMethod後,接著看Retrofit的create方法,前面說了其最後三行是關鍵程式碼

ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);

其中第一行程式碼講了,是例項化ServiceMehod物件,第二行是例項化OkHttpCall物件,該OkHttpCall是Call介面的實現,最後呼叫了callAdapter.adapt方法。前面備註了要注意的就是這個adapt方法,不同的CallAdapter其對應的adapter方法不一樣,如前面所說DefaultCallAdapterFactory 工廠類生產的CallAdapter adapt方法對應的返回引數是Call,而SimpleCallAdapter 對應adapt方法的返回引數是Observable,這就為之後RxJava結合Retrofit2使用留下了鋪墊(解決疑惑3)。

我們看到當不新增.addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))方法時,預設的CallAdater adapt方法直接來什麼引數返回什麼,也就是返回OKHttpCall物件。

到這裡應該知道在本文最開始在addCallAdapterFactory之後還使用了

 Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");

這種方式來返回Call在執行時,肯定是會報錯的,應為實際上api.getAuthor方法返回的是Observable變數,這地方只是為了方便後面講解,才那麼寫的。

5、call.enqueue發起網路請求

這裡的call.enqueue放棄網路請求的時候,實際上是OKHttpCall物件發起的網路請求,看起enqueue的部分程式碼

synchronized (this) {
  if (executed) throw new IllegalStateException("Already executed.");
  executed = true;

  call = rawCall;
  failure = creationFailure;
  if (call == null && failure == null) {
    try {
      call = rawCall = createRawCall();
    } catch (Throwable t) {
      failure = creationFailure = t;
    }
  }
}

if (failure != null) {
  callback.onFailure(this, failure);
  return;
}

if (canceled) {
  call.cancel();
}

call.enqueue(new okhttp3.Callback() {
  @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
      throws IOException {
    Response<T> response;
    try {
      response = parseResponse(rawResponse);
    } catch (Throwable e) {
      callFailure(e);
      return;
    }
    callSuccess(response);
  }
}

其中createRawCall中通過獲取ServiceMethod物件toRequest方法生成call物件,兒toRequest方法實際上就是獲取的我們傳入的.client(new OkHttpClient()) OkHttpClient物件(解決疑惑1、疑惑2)。

private okhttp3.Call createRawCall() throws IOException {
  Request request = serviceMethod.toRequest(args);
  okhttp3.Call call = serviceMethod.callFactory.newCall(request);
  if (call == null) {
    throw new NullPointerException("Call.Factory returned null.");
  }
  return call;
}

然後通過call物件呼叫call.enqueue方法發起網路請求,並執行回撥。

到這裡對Retrofit2的原始碼分析就算完成了,在分析的過程中也算是解決了文章開始提出的四個疑問。

6、參考文獻

1、Retrofit2 原始碼解析
http://www.jianshu.com/p/c1a3a881a144

2、OkHttp3的基本用法
http://www.jianshu.com/p/1873287eed87

7、Demo地址

Android Demo:https://github.com/Yoryky/AndroidDemo.git