【原始碼SOLO】Retrofit2原始碼解析(一)

darkwh1230發表於2019-03-01

版權宣告:本文為博主原創文章,未經博主允許不得轉載
Github:github.com/Darkwh
若有錯誤或疑問歡迎小夥伴們留言評論

友情提示!!!

本人英文渣,文章中哪些單詞翻譯的不夠形象的話。。。。那你到是來打我呀O(∩_∩)O

系列回顧

【原始碼SOLO】Retrofit2原始碼解析(一)

【原始碼SOLO】Retrofit2原始碼解析(二)

文章目錄

【原始碼SOLO】Retrofit2原始碼解析(一)

前言

相信大多數Android開發者都聽過並使用過Retrofit,一款由square公司開源的網路請求庫。自己也用了一段時間,retrofit在okhttp的基礎上再一次簡化我們的網路請求操作,並且支援rxjava,用起來更加的得心應手。今天給大家分享一下自己閱讀retrofit原始碼的成果,記錄的同時希望也能將收穫分享給大家,個人水平有限,有錯誤的地方也請大神包涵和指正。

Retrofit的基本使用

Retrofit的使用方法網上資料很多,這裡不多說了,簡單貼一下使用方法,以後的分析原始碼會跟著呼叫順序去走。

建立介面檔案

interface TestService{
    @GET("data/Android/10/1")
    fun getMsg(): Call<MsgBean>
}
複製程式碼

建立接收網路請求的實體類

class MsgBean {
    var error: Boolean = false
}
複製程式碼

建立Retrofit物件

val retrofit: Retrofit = Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl("http://gank.io/api/")
                .build()
複製程式碼

建立介面的代理物件

val service: TestService = retrofit.create(TestService::class.java)
複製程式碼

獲取Call物件

val call: Call<MsgBean> = service.getMsg()
複製程式碼

執行請求並監聽返回結果

call.enqueue(object : Callback<MsgBean> {
            override fun onFailure(call: Call<MsgBean>?, t: Throwable?) {
                Log.i("wh", "onFailure")
            }
            override fun onResponse(call: Call<MsgBean>?, response: Response<MsgBean>?) {
                Log.i("wh", "onResponse")
            }
        })
複製程式碼

原始碼目錄結構及專案組成

原始碼目錄

retrofit的原始碼類比較少,非常適合新手閱讀(比如我),其中http包下面都是一些自定義的註解(@GET、@POST等),這裡就不一一列出了

【原始碼SOLO】Retrofit2原始碼解析(一)

比較重要的幾個類

介面:Call、CallAdapter、Converter、Callback

類:Retrofit、ServiceMethod、ParameterHandler、OkHttpCall


接下來讓我們瞭解一下這幾個類大致是做什麼的,首先我們從介面開始,因為介面屬於高度抽象,瞭解介面的定義有助於我們理解原始碼。

1.Call

/**
 * An invocation of a Retrofit method that sends a request to a webserver and returns a response.
 * Each call yields its own HTTP request and response pair. Use {@link #clone} to make multiple
 * calls with the same parameters to the same webserver; this may be used to implement polling or
 * to retry a failed call.
 *
 * <p>Calls may be executed synchronously with {@link #execute}, or asynchronously with {@link
 * #enqueue}. In either case the call can be canceled at any time with {@link #cancel}. A call that
 * is busy writing its request or reading its response may receive a {@link IOException}; this is
 * working as designed.
 *
 * @param <T> Successful response body type.
 */
public interface Call<T> extends Cloneable {
    ......
}
複製程式碼

一個向伺服器傳送請求並獲得響應的呼叫器,主要的方法為

Response<T> execute() throws IOException;

void enqueue(Callback<T> callback);
複製程式碼

這兩個方法小夥伴們應該很熟悉了,execute是傳送同步請求,enqueue是傳送非同步請求

2.CallAdapter

/**
 * Adapts a {@link Call} with response type {@code R} into the type of {@code T}. Instances are
 * created by {@linkplain Factory a factory} which is
 * {@linkplain Retrofit.Builder#addCallAdapterFactory(Factory) installed} into the {@link Retrofit}
 * instance.
 */
public interface CallAdapter<R, T> {
    ......
}
複製程式碼

中文意思大致就是說CallAdapter接收一個Call物件(帶著返回型別引數R)並轉換為新的型別T。CallAdapter會被 Retrofit.Builder.addCallAdapterFactory(Factory)指定的Factory建立(注意builder可以指定多個Factory的,後面會說明這個問題)

上面的文字可能會讓你覺得比較亂,沒關係我們來看一下這個介面中的其中一個抽象方法:

T adapt(Call<R> call);
複製程式碼

這個方法就是CallAdapter最為核心的一個方法,如同註釋中的描述一樣,接收一個Call物件,Call物件帶著引數R,方法返回T型別。

再看CallAdapter介面中的另一個方法:

Type responseType();
複製程式碼

程式碼中的註解描述為:當把HTTP返回體轉換為JAVA類的時候返回引數型別,例如你得自定義介面中的方法返回值為Call<Foo>,那麼Type型別則為Foo。

總結來說CallAdapter將Call<R> 轉換為T的這麼一個轉換介面卡

CallAdapter介面中還有一個內部工廠類:

 abstract class Factory {
    public abstract @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
        Retrofit retrofit);

    protected static Type getParameterUpperBound(int index, ParameterizedType type) {
      return Utils.getParameterUpperBound(index, type);
    }

    protected static Class<?> getRawType(Type type) {
      return Utils.getRawType(type);
    }
  }
複製程式碼

典型的工廠模式,通過get方法來獲取CallAdapter例項

3.Converter

/**
 * Convert objects to and from their representation in HTTP. Instances are created by {@linkplain
 * Factory a factory} which is {@linkplain Retrofit.Builder#addConverterFactory(Factory) installed}
 * into the {@link Retrofit} instance.
 */
public interface Converter<F, T> {
    ......
}
複製程式碼

Converter介面的作用是將Java實體類和Http內容相互轉換
同CallAdapter一樣,Converter介面中也有一個內部工廠類:

  abstract class Factory {
    public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,
        Annotation[] annotations, Retrofit retrofit) {
      return null;
    }

    public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }

    public @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }

    protected static Type getParameterUpperBound(int index, ParameterizedType type) {
      return Utils.getParameterUpperBound(index, type);
    }

    protected static Class<?> getRawType(Type type) {
      return Utils.getRawType(type);
    }
  }
複製程式碼
  • responseBodyConverter方法將Http內容轉換為Java實體
  • requestBodyConverter方法將Java實體轉換為Http內容(RequestBody)

4.Callback

public interface Callback<T> {

  void onResponse(Call<T> call, Response<T> response);

  void onFailure(Call<T> call, Throwable t);
}
複製程式碼

網路請求成功/失敗的回撥介面,不多說了吧就。

以上為Retrofit原始碼中的四個介面及其簡單介紹。接下來讓我們再瞭解一下其他幾個比較重要的類

1.Retrofit

就這個名字,和專案名同款,意味這個類的地位是多麼重要,具體程式碼後面分析。

2.ServiceMethod

/**
 * Adapts an invocation of an interface method into an HTTP call.
 */
final class ServiceMethod<R, T> {
    ......
}
複製程式碼

將一個介面方法轉換為HTTP請求的呼叫器
非常核心的一個類,它的作用同原始碼中的註釋一樣,會將你定義的介面中的方法轉換為Http請求。具體的程式碼我們同樣放到後面分析。

3.ParameterHandler

這個類原始碼沒什麼註釋,故名思議是處理引數的,這裡處理的是請求引數。
ParameterHandler的定義如下

abstract class ParameterHandler<T> {

    abstract void apply(RequestBuilder builder, @Nullable T value) throws IOException;
  
    final ParameterHandler<Iterable<T>> iterable() {
    return new ParameterHandler<Iterable<T>>() {
      @Override void apply(RequestBuilder builder, @Nullable Iterable<T> values)
          throws IOException {
        if (values == null) return; // Skip null values.

        for (T value : values) {
          ParameterHandler.this.apply(builder, value);
        }
      }
    };
  }

  final ParameterHandler<Object> array() {
    return new ParameterHandler<Object>() {
      @Override void apply(RequestBuilder builder, @Nullable Object values) throws IOException {
        if (values == null) return; // Skip null values.

        for (int i = 0, size = Array.getLength(values); i < size; i++) {
          //noinspection unchecked
          ParameterHandler.this.apply(builder, (T) Array.get(values, i));
        }
      }
    };
  }
}
複製程式碼

ParameterHandler是一個抽象類,擁有一個抽象方法apply和兩個遍歷方法,兩個遍歷方法分別適用於Iterable 和陣列,遍歷集合(陣列)並執行apply方法。

ParameterHandler有許多的子類,我們來看其中的幾個:

  static final class RelativeUrl extends ParameterHandler<Object> {
    @Override void apply(RequestBuilder builder, @Nullable Object value) {
      checkNotNull(value, "@Url parameter is null.");
      builder.setRelativeUrl(value);
    }
  }
複製程式碼
  static final class Header<T> extends ParameterHandler<T> {
  private final String name;
  private final Converter<T, String> valueConverter;

  Header(String name, Converter<T, String> valueConverter) {
    this.name = checkNotNull(name, "name == null");
    this.valueConverter = valueConverter;
  }

  @Override void apply(RequestBuilder builder, @Nullable T value) throws IOException {
    if (value == null) return; // Skip null values.

    String headerValue = valueConverter.convert(value);
    if (headerValue == null) return; // Skip converted but null values.

    builder.addHeader(name, headerValue);
  }
}
複製程式碼
static final class Part<T> extends ParameterHandler<T> {
  private final Headers headers;
  private final Converter<T, RequestBody> converter;

  Part(Headers headers, Converter<T, RequestBody> converter) {
    this.headers = headers;
    this.converter = converter;
  }

  @Override void apply(RequestBuilder builder, @Nullable T value) {
    if (value == null) return; // Skip null values.

    RequestBody body;
    try {
      body = converter.convert(value);
    } catch (IOException e) {
      throw new RuntimeException("Unable to convert " + value + " to RequestBody", e);
    }
    builder.addPart(headers, body);
  }
}
複製程式碼

不難看出,ParameterHandler作用就是為RequestBuilder構建引數,RequestBuilder是retrofit原始碼中的一個累,其build方法會返回一個Request物件,Request物件是OkHttp中的類,沒看過,就不分(裝)析(逼)了。

不知道小夥伴們有沒有注意到Part(Headers headers, Converter<T, RequestBody> converter)這行程式碼,這就是前面說的Converter介面其中一個作用,將Java物件轉換為Http內容。

3.OkHttpCall

這是Retrofit中對Call介面內建的預設實現,這個類其實並沒有Retrofit和ServiceMethod重要,考慮是預設實現,所以也會著重分析一下,來讓我們SOLO下程式碼:

嗯。。。程式碼有點多,就不貼了,還是來讓我們看看其中比較重要的幾個變數和方法吧:

private final ServiceMethod<T, ?> serviceMethod;

private @Nullable
okhttp3.Call rawCall;

複製程式碼

OkHttpCall物件持有一個ServiceMethod引用和一個okhttp3.Call引用

再看OkHttpCall的其他方法,這裡我們主要看一下enqueue非同步請求方法,其它程式碼小夥伴們可自行研究:

    @Override
    public void enqueue(final Callback<T> callback) {
        //檢查傳入的Callback是否為空,為空丟擲異常
        checkNotNull(callback, "callback == null");
        okhttp3.Call call;
        Throwable failure;
        synchronized (this) {
            //此OkHttpCall物件如果執行過,再次呼叫會丟擲異常
            //一般不會拋這個異常,因為每次都是建立一個新的OkHttpCall物件
            //所以原始碼中並沒有executed=false這種設定,只有executed = true
            if (executed) throw new IllegalStateException("Already executed.");
            executed = true;
            call = rawCall;
            failure = creationFailure;
            if (call == null && failure == null) {
                try {
                    //此處建立原始的Call物件
                    call = rawCall = createRawCall();
                } catch (Throwable t) {
                    //如果為指定的三種異常,則丟擲異常(為RxJava支援)
                    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)
                    throws IOException {
                Response<T> response;
                try {
                    //解析響應引數
                    response = parseResponse(rawResponse);
                } catch (Throwable e) {
                    //如有解析異常則回撥給callback
                    callFailure(e);
                    return;
                }
                callSuccess(response);
            }

            @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();
                }
            }

            private void callSuccess(Response<T> response) {
                try {
                    callback.onResponse(OkHttpCall.this, response);
                } catch (Throwable t) {
                    t.printStackTrace();
                }
            }
        });
    }
複製程式碼

enqueue方法總結來說是首先建立一個原始的Call物件(注意這裡為okhttp3的Call物件),然後呼叫Call.enqueue方法,並對返回結果進行解析,請求出錯或者解析出錯會回撥到你設定的Callback物件中的onFailure方法,解析成功則會回撥callSuccess方法

來看一下建立原始Call物件的方法定義:

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;
   }
複製程式碼

通過ServiceMethod的toRequest方法構造出一個Request隊形,然後通過指定的callFactory來建立call物件並返回。這裡預設的callFactory前面講解Retrofit.Builder的build方法中有介紹,未指定的時候預設使用OkHttpClient。
看一下toRequest方法的定義:

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

這個方法的作用就是通過遍歷上面介紹過的ParameterHandler陣列並呼叫apply函式將方法中的引數轉換成Http請求引數。

再來看一下解析返回結果的方法定義:

    Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
        ResponseBody rawBody = rawResponse.body();

        ......

        ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
        try {
            //解析引數
            T body = serviceMethod.toResponse(catchingBody);
            return Response.success(body, rawResponse);
        } catch (RuntimeException e) {
            // If the underlying source threw an exception, propagate that rather than indicating it was
            // a runtime exception.
            catchingBody.throwIfCaught();
            throw e;
        }
    }
複製程式碼

其中會呼叫ServiceMethod.toResponse,稍微跟一下:

    R toResponse(ResponseBody body) throws IOException {
        return responseConverter.convert(body);
    }
複製程式碼

可以看到此處使用你指定的Convert來對響應資料進行轉換(比如我們最常用的json轉實體類)

總結

以上為大家介紹了一下retrofit的基本使用和幾個重要的類,下一篇我將按照retrofit使用的順序來分析一下原始碼,將我說理解的內容分先給大家,如有錯誤的地方還請包涵和指正!勿噴!!!

相關文章