Retrofit原始碼分析三 原始碼分析

BlackFlagBin發表於2018-05-17

Retrofit原始碼分析三 原始碼分析

使用方法

我們先來看一下Retrofit的常見使用方法:

//建立網路請求介面類
public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

//建立Retrofit例項物件
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

//通過動態代理建立網路介面代理物件
GitHubService service = retrofit.create(GitHubService.class);

//獲取Call物件
Call<List<Repo>> repos = service.listRepos("octocat");

//執行同步請求或非同步請求
repos.execute();
repos.enqueue(callback)
複製程式碼

上面是Retrofit的最基本使用方法,當然現在使用最多的還是RxJava2+Retrofit搭配使用,關於RxJava2,大家可以看我的另一篇 RxJava2原始碼分析 ,當然RxJava2與Retrofit搭配使用的解析我會在稍後分析,這裡我們先關注最基本的使用方法。

建立網路介面類

這一步的目的就是封裝我們網路請求相關的一些引數,沒什麼好多說的。

建立Retrofit例項物件

Retrofit例項物件的建立很明顯是採用了Builder模式,Builder模式在 Java語言中 建立一個 有很多可選配置引數 的物件的時候是很好的一種設計模式。Builder模式有兩個重點,一個是在 Java語言中 中,另一個是 有很多可選配置引數,其實在現在的Android開發中,使用Kotlin開發Android已經很普遍了,熟悉Kotlin語法的小夥伴可能很熟悉了,由於Kotlin中 預設引數 的存在,所以在Kotlin中使用Builder模式的意義不大。但由於Java語法的限制,在建立一個 有很多可選配置引數 的時候,Builder模式還是首要選擇。

我們來看一下Retrofit中的成員變數:

public final class Retrofit {
  //快取封裝好的ServiceMethod
  private final Map<Method, ServiceMethod<?, ?>> serviceMethodCache = new ConcurrentHashMap<>();
  //OKHttp中的網路請求工廠
  final okhttp3.Call.Factory callFactory;
  //BaseUrl
  final HttpUrl baseUrl;
  //資料轉換器工廠集合
  final List<Converter.Factory> converterFactories;
  //網路請求介面卡工廠集合
  final List<CallAdapter.Factory> callAdapterFactories;
  //處理執行緒切換
  final @Nullable Executor callbackExecutor;
  //不需要關注,預設為false
  final boolean validateEagerly;
  
  //忽略無關程式碼......
  }
複製程式碼

serviceMethodCache 是一個HashMap,它的Key是Method,代表我們定義的網路請求介面類中的方法,它的Value是ServiceMethod,代表對網路請求介面類中的方法的一個封裝,簡單看一下它就明白了:

final class ServiceMethod<R, T> {

  private final okhttp3.Call.Factory callFactory;
  private final CallAdapter<R, T> callAdapter;

  private final HttpUrl baseUrl;
  private final Converter<ResponseBody, R> responseConverter;
  private final String httpMethod;
  private final String relativeUrl;
  private final Headers headers;
  private final MediaType contentType;
  private final boolean hasBody;
  private final boolean isFormEncoded;
  private final boolean isMultipart;
  private final ParameterHandler<?>[] parameterHandlers;
  
  //忽略無關程式碼.......
  }
複製程式碼

很明顯了,ServiceMethod就是對網路請求引數的封裝類,包含請求頭、相對url、GET請求或者是POST請求等等對請求的配置資訊。serviceMethodCache 就是一個對ServiceMethod的快取,可以提高一定的效率。

在Retrofit中,預設的資料轉換器工廠就是 GsonConverterFactory ,所以其實如果我們是使用 Gson 來做資料轉換的,其實沒有必要去配置。在Android平臺,Retrofit中預設的網路請求介面卡工廠是 ExecutorCallAdapterFactory ,我們簡單瞅一眼它是如何被建立的:

static class Android extends Platform {
    @Override public Executor defaultCallbackExecutor() {
      return new MainThreadExecutor();
    }

    @Override CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
      if (callbackExecutor == null) throw new AssertionError();
      return new ExecutorCallAdapterFactory(callbackExecutor);
    }

    static class MainThreadExecutor implements Executor {
      //注意這裡建立了一個位於主執行緒的Handler
      private final Handler handler = new Handler(Looper.getMainLooper());

      @Override public void execute(Runnable r) {
        //通過handler.post(runnable)實現執行緒切換
        handler.post(r);
      }
    }
  }
複製程式碼

可以看到,在建立 ExecutorCallAdapterFactory 的同時傳入了一個 callbackExecutor ,這個 callbackExecutor 也是Retrofit中預設的 callbackExecutor ,在Android平臺中它是 MainThreadExecutor型別 ,可以看到,在它的內部建立了一個位於主執行緒的Handler。我們知道,使用Retrofit的時候不同於直接使用OKHttp,在使用Retrofit的非同步網路請求時,網路結果的回撥是位於主執行緒中的,那麼Retrofit是如何切換的執行緒,看過上面的程式碼應該會心裡有個數了,它是通過 handler.post(r) 來實現由子執行緒到主執行緒的切換。

通過動態代理建立網路介面代理物件

看過我的Retrofit原始碼分析第二篇:代理模式的小夥伴們應該都比較清楚了,在Retrofit的 create 方法中其實是使用了動態代理生成了一個代理物件,現在我們就來看一下 create 的原始碼:

  public <T> T create(final Class<T> service) {
    //忽略無關程式碼......
    
    //下面就是JDK給我們提供的動態代理了
    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 {
            //忽略無關程式碼......
            
            //獲取method的網路請求引數的封裝類ServiceMethod物件
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
                
            //獲取對OKHttp中的RealCall的一個包裝類OkHttpCall物件
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            //通過網路請求介面卡將原始的Call物件轉換成需要的物件,比如RxJavaCallAdapter會將Call物件轉換成Observable。
            return serviceMethod.adapt(okHttpCall);
          }
        });
  }
複製程式碼

create 方法中做了3件事:

  • 封裝網路請求方法
  • 封裝原始Call物件
  • 通過CallAdapter轉換Call物件 封裝網路請求方法很容易理解,關於ServiceMethod上面已經說過,不再贅述。封裝原始Call物件,對OKHttp原始碼熟悉的小夥伴應該知道,原始的Call其實就是OkHttp中的ReallCall。如果不熟悉OkHttp的同學可以看我之前的一篇 OkHttp原始碼分析 。我們可以簡單看一下這個包裝類 OkHttpCall :
final class OkHttpCall<T> implements Call<T> {

    //忽略無關程式碼......    
    
    @Override public Response<T> execute() throws IOException {
    okhttp3.Call call;

    //熟悉OkHttp原始碼的同學應該很熟悉了,完全照抄OkHttp中的程式碼,確保每個Call只會被執行一次
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      //忽略無關程式碼......
    
      call = rawCall;
      if (call == null) {
        try {
          //建立OkHttp中的RealCall物件
          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();
    }
    //解析OkHttp中的RealCall執行同步方法後返回的網路資料
    return parseResponse(call.execute());
  }
  
  
  @Override public void enqueue(final Callback<T> callback) {
    checkNotNull(callback, "callback == null");

    okhttp3.Call call;
    Throwable failure;

    //確保一個Call物件只被執行一次
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = rawCall;
      failure = creationFailure;
      if (call == null && failure == null) {
        try {
          //建立OkHttp中的RealCall物件
          call = rawCall = createRawCall();
        } catch (Throwable t) {
          throwIfFatal(t);
          failure = creationFailure = t;
        }
      }
    }

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

    if (canceled) {
      call.cancel();
    }
    
    //呼叫OkHttp中的RealCall的非同步請求網路方法
    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) {
          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();
        }
      }
    });
  }
}
複製程式碼

可以看到,Retrofit中的這個 OKHttpCall 完全是對OkHttp中的 RealCall 的一個包裝。在 OKHttpCall 中的同步網路方法 execute 和非同步網路方法 enQueue 中其實就做了三件事:獲取 RealCall 物件,呼叫 RealCall 中的 execute 或者 enQueue 方法,然後去解析OkHttp返回的原始資料並轉換成我們需要的型別,就這麼簡單。

接下來就 create 方法中就只剩下 通過CallAdapter轉換Call物件 了,我們上面說過,在沒有特別配置CallAdapter的時候,預設的CallAdapterFactory是 ExecutorCallAdapterFactory ,很明顯,CallAdapter是由Factory建立的,那我們看一下這個預設的CallAdapterFactory:

final class ExecutorCallAdapterFactory extends CallAdapter.Factory {
    
  //本質是傳入的MainThreadExecutor
  final Executor callbackExecutor;

  ExecutorCallAdapterFactory(Executor callbackExecutor) {
    this.callbackExecutor = callbackExecutor;
  }

  @Override
  public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }
    final Type responseType = Utils.getCallResponseType(returnType);
    
    //直接建立並返回一個CallAdapter的匿名物件
    return new CallAdapter<Object, Call<?>>() {
      @Override public Type responseType() {
        return responseType;
      }

      @Override public Call<Object> adapt(Call<Object> call) {
        //返回預設的CallAdapter轉換後的Call物件
        return new ExecutorCallbackCall<>(callbackExecutor, call);
      }
    };
  }

  static final class ExecutorCallbackCall<T> implements Call<T> {
    //本質是傳入的MainThreadExecutor
    final Executor callbackExecutor;
    //就是OKHttpCall,從名字也能理解,代理Call物件
    final Call<T> delegate;

    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
      this.callbackExecutor = callbackExecutor;
      this.delegate = delegate;
    }

    @Override public void enqueue(final Callback<T> callback) {
      checkNotNull(callback, "callback == null");

       //呼叫OkHttp中RealCall的非同步網路請求
      delegate.enqueue(new Callback<T>() {
        @Override public void onResponse(Call<T> call, final Response<T> response) {
            //注意,OkHttp的非同步回撥是在子執行緒的,Retrofit這裡實現了執行緒的切換,本質是呼叫了handler.post(runnable)
          callbackExecutor.execute(new Runnable() {
            @Override public void run() {
              if (delegate.isCanceled()) {
                callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
              } else {
                callback.onResponse(ExecutorCallbackCall.this, response);
              }
            }
          });
        }

        @Override public void onFailure(Call<T> call, final Throwable t) {
            //注意,OkHttp的非同步回撥是在子執行緒的,Retrofit這裡實現了執行緒的切換,本質是呼叫了handler.post(runnable)
          callbackExecutor.execute(new Runnable() {
            @Override public void run() {
              callback.onFailure(ExecutorCallbackCall.this, t);
            }
          });
        }
      });
    }

    @Override public boolean isExecuted() {
      return delegate.isExecuted();
    }

    @Override public Response<T> execute() throws IOException {
       //由於同步方法不需要切執行緒,所以直接執行並返回OKHttpCall的同步網路方法
      return delegate.execute();
    }

    @Override public void cancel() {
      delegate.cancel();
    }

    @Override public boolean isCanceled() {
      return delegate.isCanceled();
    }

    @SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone.
    @Override public Call<T> clone() {
      return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone());
    }

    @Override public Request request() {
      return delegate.request();
    }
  }
}
複製程式碼

上面的程式碼其實也很清晰了 ExecutorCallAdapterFactory 這個CallAdapter工廠類直接建立並返回一個CallAdapter的匿名物件,這個其實就是我們Retrofit的預設CallAdapter,關鍵是這個CallAdapter的adapt方法,它返回了 ExecutorCallbackCall 這個對OkHttpCall的包裝類,我們關注這個包裝類的 enqueue 方法,在這個方法中,它通過呼叫 callbackExecutor.execute 來實現了子執行緒到主執行緒的執行緒切換。這個 callbackExecutor 就是 MainThreadExecutor ,這個類我們上面提到過, MainThreadExecutor 中的 execute 方法內部就是 handler.post(r); ,這個 handler 其實就是主執行緒的,因此實現了執行緒切換。

獲取Call物件並執行同步或非同步方法

其實經過上一步的分析,我們已經知道了,我們獲取的Call物件,其實是通過動態代理中 serviceMethod.adapt(okHttpCall) 返回的Call物件,這個其實就是 ExecutorCallbackCall ,這個我們都很清楚了,它是對OkHttp中RealCall的一個包裝類,在它的非同步方法中通過呼叫 callbackExecutor.execute 實現了執行緒的切換,當然本質還是通過 Handler 機制。

其實到此為止,基本的流程已經分析完畢了,我們到這裡也會很清楚Retrofit的定位:Retrofit並不是一個網路請求的框架,而是一封裝網路請求引數,解析網路返回結果的框架。下面我們來分析一下使用了RxJava2CallAdapter的情況。

RxJava2CallAdapter的原理

CallAdapter的重點是它的adapt方法,我們來看一下RxJava2CallAdapter中的adapt方法:

@Override public <R> Object adapt(Call<R> call) {
    //建立一個發射網路返回結果的Observable
    Observable<Response<R>> responseObservable = new CallObservable<>(call);

    Observable<?> observable;
    if (isResult) {
      observable = new ResultObservable<>(responseObservable);
    } else if (isBody) {
      observable = new BodyObservable<>(responseObservable);
    } else {
      observable = responseObservable;
    }
    
    //預設scheduler為空
    if (scheduler != null) {
      observable = observable.subscribeOn(scheduler);
    }

    if (isFlowable) {
      return observable.toFlowable(BackpressureStrategy.LATEST);
    }
    if (isSingle) {
      return observable.singleOrError();
    }
    if (isMaybe) {
      return observable.singleElement();
    }
    if (isCompletable) {
      return observable.ignoreElements();
    }
    return observable;
  }
複製程式碼

這段程式碼本質是建立一個發射網路返回結果的Observable,我們看一下 CallObservable 的內部實現,看過RxJava2原始碼的同學應該都知道,Observable的關鍵方法是 subscribeActual,我們就看一下這個方法內部都做了什麼:

@Override protected void subscribeActual(Observer<? super Response<T>> observer) {
    // Since Call is a one-shot type, clone it for each new observer.
    Call<T> call = originalCall.clone();
    observer.onSubscribe(new CallDisposable(call));

    boolean terminated = false;
    try {
      //呼叫了OKHttpCall的同步網路訪問方法,並獲取網路資料
      Response<T> response = call.execute();
      if (!call.isCanceled()) {
        //將獲取到的網路資料傳送到觀察者observer
        observer.onNext(response);
      }
      if (!call.isCanceled()) {
        terminated = true;
        //傳送結束事件
        observer.onComplete();
      }
    } catch (Throwable t) {
      Exceptions.throwIfFatal(t);
      if (terminated) {
        RxJavaPlugins.onError(t);
      } else if (!call.isCanceled()) {
        try {
          //傳送錯誤事件
          observer.onError(t);
        } catch (Throwable inner) {
          Exceptions.throwIfFatal(inner);
          RxJavaPlugins.onError(new CompositeException(t, inner));
        }
      }
    }
  }
複製程式碼

熟悉RxJava2的同學看完就應該懂了:RxJava2CallAdapter通過adapt方法生成並返回一個 CallObservable 物件,在這個 CallObservable 內部通呼叫 OKHttpCallexecute() 方法進行網路訪問,並將獲取到的資料傳送到下一級的觀察者observer中。

到此為止,Retrofit的原始碼分析終於結束了,其實它並不難,但想要完全理解整個網路訪問流程,除了要明白Retrofit,還需要了解OKHttp甚至是RxJava,對後兩者不太熟悉的小夥伴們可以看我的另外兩篇原始碼分析:OkHttp原始碼分析RxJava2原始碼分析 ,在熟悉了OKHttp和RxJava2的基礎上再回過頭來看Retrofit,相信你會有更深的理解。

相關文章