妙不可言的Retrofit

水煮雞蛋也好吃發表於2020-02-13

前言

噢!親愛的朋友們,快來看看這優秀的Retrofit,它實在太美妙了,如果你不看的話,我保證會用我的靴子狠狠地踢你的屁股!(狗頭保命)

妙不可言的Retrofit

正文

1. 什麼是Retrofit?

官網 中對它的描述:

A type-safe HTTP client for Android and Java

大概意思也就是針對 JavaAndroid的 一種型別安全的HTTP庫.

Retrofit是Square開源的一款優秀的網路框架,它不會自己去請求網路,而是對OkHttp進行了封裝。

僅僅會使用還不夠,學習原始碼有助於我們更好的成長。

2. Retrofit的使用

我們先來看看官網上的案例:

  1. 先定義你的網路介面
public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
}
複製程式碼
  1. 建立Retrofit物件
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .build();
複製程式碼
  1. 獲得網路請求API的例項
GitHubService service = retrofit.create(GitHubService.class);
複製程式碼
  1. 呼叫API方法
Call<List<Repo>> call = service.listRepos("octocat");
複製程式碼
  1. 執行網路請求
// 同步
try {
    List<Repo> repos = call.execute().body();
} catch (IOException e) {
    e.printStackTrace();
}

// 非同步
call.enqueue(new Callback<List<Repo>>() {
    @Override
    public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
        // 資料返回成功
    }

    @Override
    public void onFailure(Call<List<Repo>> call, Throwable t) {
       // 資料返回失敗
    }
});
複製程式碼

至此,Retrofit的一次網路請求就結束了,是不是很簡單。 Retrofit大大減輕了開發者對於網路請求的操作。

3.Retrofit的URL和引數型別

3.1 Url規則和配置

支援的協議:GET / POST / PUT / DELETE / HEAD / PATCH

比如:

@GET("users/{user}/repos")

GET中的value 要和baseUrl整合一起

我們來看看baseUrlvalue的整合規則:

第一種:

path 是 絕對路徑形式:
path = "/apath", baseUrl = "http://host:port/a/b"
Url = "http://host:port/apath"
複製程式碼

第二種:

path 是相對路徑,baseUrl 目錄形式:
path = "apath", baseUrl = "http://host:port/a/b/"
Url = "http://host:port/a/b/apath"
複製程式碼

第三種:

path 是相對路徑,baseUrl 是檔案形式:
path = "apath", baseUrl = "http://host:port/a/b"
Url = "http://host:port/a/apath"
複製程式碼

第四種:

path 是完整Url:
path = "http://host:port/aa/apath", baseUrl = "http://host:port/a/b"
Url = "http://host:port/aa/apath"
複製程式碼

建議整個專案都統一使用一種路徑方式,一般選擇第二種方式。

3.2 引數型別

通過註解的形式令Http請求的引數更加直接。

3.2.1 Query & QueryMap

Query 其實就是Url之後的 key-value

比如:

url :"www.println.net/?cate=andro…"

其中 cate=android 就是 Query。

interface PrintlnServer{ 
@GET("/")
Call<String> cate(@Query("cate") String cate);
 } 
//引數 cate 就是 它的 key,傳入的值 就是它的 value
複製程式碼

如果擁有多個引數的話 , 可以使用 QueryMap

3.2.2 Field & FieldMap

在專案中,大部分情況下我們是使用POST

   @FormUrlEncoded
   @POST("/")
   Call<ResponseBody> example(@Field("name") String name,
                              @Field("occupation") String occupation);
       // 需要注意的是 使用Field 的時候,要加 @FormUrlEncoded 用來格式化。
複製程式碼

如果 你有多個引數需要填寫, 當然可以使用 FieldMap

3.2.3 Part & PartMap

用於上傳檔案

案例:

public interface FileUploadService {
@Multipart
@POST("upload")
Call<ResponseBody> upload(@Part("description") RequestBody description,
@Part MultipartBody.Part file);
}
// 注意使用 註解 @Multipart

************************ 使用案例 ***********************

//先建立 service
FileUploadService service = retrofit.create(FileUploadService.class);

//構建要上傳的檔案
File file = new File(filename);
RequestBody requestFile =
RequestBody.create(MediaType.parse("application/otcet-stream"), file);

MultipartBody.Part body =
MultipartBody.Part.createFormData("aFile", file.getName(), requestFile);

String descriptionString = "This is a description";
RequestBody description =
RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString);

Call<ResponseBody> call = service.upload(description, body);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call,
Response<ResponseBody> response) {
System.out.println("success");
}

@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
}
});
複製程式碼

當然如果需要多個Part引數,可以使用PartMap

接下來我們來看下Retrofit的原理是如何完成的。

4. Retrofit的原理

4.1 Retrofit的create方法

我們首先進入到create方法中:

public <T> T create(final Class<T> service) {
  // 在這裡驗證 介面是否合理
  validateServiceInterface(service);
  // 用到了Java的動態代理技術
  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 @Nullable Object invoke(Object proxy, Method method,
            @Nullable Object[] args) throws Throwable {

          // 如果物件是Object,不用管它
          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);
        }
      });
}
複製程式碼

在這裡用到了Java的動態代理,

可以看到我們在invoke中,我們進入到了loadServiceMethod中:

ServiceMethod<?> loadServiceMethod(Method method) {
  ServiceMethod<?> result = serviceMethodCache.get(method);
  if (result != null) return result;

  synchronized (serviceMethodCache) {
    result = serviceMethodCache.get(method);
    if (result == null) {
      result = ServiceMethod.parseAnnotations(this, method);
      serviceMethodCache.put(method, result);
    }
  }
  return result;
}
複製程式碼

在這裡運用了快取的技術,因為動態代理和建立一個ServiceMethod是比較耗時間的,而且一個api可能伴隨著頻繁的呼叫,所以在這裡使用快取技術可以有效的減少時間的消耗。

當快取中不存在的時候,就需要我們去建立一個新的ServiceMethod,於是呼叫 ServiceMethod.parseAnnotations(this, method):

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.");
  }

  return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
複製程式碼

讓我們再進入到 HttpServiceMethod 中看看:

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
    Retrofit retrofit, Method method, RequestFactory requestFactory) {
  //....省略部分程式碼
  CallAdapter<ResponseT, ReturnT> callAdapter =
    createCallAdapter(retrofit, method, adapterType, annotations);
 //.....
Converter<ResponseBody, ResponseT> responseConverter =
    createResponseConverter(retrofit, method, responseType);
//. . . . 

return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
//....

}
複製程式碼

先來看看 callAdapter 是怎麼建立的?

進入到 createCallAdapter 中:

private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter(
    Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
  try {
    //noinspection unchecked
    return (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations);
  } catch (RuntimeException e) { // Wide exception range because factories are user code.
    throw methodError(method, e, "Unable to create call adapter for %s", returnType);
  }
}
複製程式碼

再進入到 retrofit.callAdapter(returnType, annotations)

public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
  return nextCallAdapter(null, returnType, annotations);
}
複製程式碼

進入到nextCallAdapter() :


public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
    Annotation[] annotations) {
  Objects.requireNonNull(returnType, "returnType == null");
  Objects.requireNonNull(annotations, "annotations == null");


  int start = callAdapterFactories.indexOf(skipPast) + 1;
  for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
    CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
    if (adapter != null) {
      return adapter;
    }
  }
 // ...... 省略部分程式碼
}
複製程式碼

可以看到是從 callAdapterFactories 這個list集合中獲取的,如果我們沒有新增過CallAdapterFactory 即:

      Retrofit retrofit = new Retrofit.Builder()
              .baseUrl("https://api.github.com/")
              .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
              .build();
複製程式碼

那麼會自動使用一個預設的CallAdapterFactory:DefaultCallAdapterFactory

這個物件在build() 的時候會新增到集合中去:

public Retrofit build() {
  // .....
  //在這裡新增 預設的 CallAdapterFactory
  List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
  callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
  // .... 
}
複製程式碼

我們再轉回來,最終會返回一個CallAdapted的例項,這個類的父類其實就是 HttpServiceMethod

static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> 
複製程式碼

就這樣,在Retrofitcreate的方法中loadServiceMethod 最終也是 返回了 一個 CallAdapted 例項,

這個例項呼叫了 invoke方法,但是CallAdapted類沒有invoke方法,那麼追溯到它的父類 HttpServiceMethod

@Override final @Nullable ReturnT invoke(Object[] args) {
  Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
  return adapt(call, args);
}
複製程式碼

在這裡建立了 一個 OkHttpCall ,然後呼叫了 adapt方法,CallAdapted類實現了這個抽象方法:

@Override protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
  return callAdapter.adapt(call);
}
複製程式碼

這個 callAdapter 就是預設的DefaultCallAdapter

@Override public Call<Object> adapt(Call<Object> call) {
  return executor == null
      ? call
      : new ExecutorCallbackCall<>(executor, call);
}
複製程式碼

API呼叫的時候返回的Call物件就是這個了。 在這裡 executor 是不為null的,所以我們得到的 Call例項 是 ExecutorCallbackCall

因為在 Retrofit類的build() 的方法中:

Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
  callbackExecutor = platform.defaultCallbackExecutor();
}
複製程式碼

所以 拿到的是 platformdefaultCallbackExecutor()

先看看 platform 的建立:

private static Platform findPlatform() {
  try {
    Class.forName("android.os.Build");
    if (Build.VERSION.SDK_INT != 0) {
      return new Android();
    }
  } catch (ClassNotFoundException ignored) {
  }
  return new Platform(true);
}
複製程式碼

如果是 Android的話,則建立Andoid類:

static final class Android extends Platform {
  Android() {
    super(Build.VERSION.SDK_INT >= 24);
  }

  @Override public Executor defaultCallbackExecutor() {
    return new MainThreadExecutor();
  }

  static class MainThreadExecutor implements Executor {
    private final Handler handler = new Handler(Looper.getMainLooper());

    @Override public void execute(Runnable r) {
      handler.post(r);
    }
  }
}
複製程式碼

所以 callbackExecutor是存在的,且資料會通過handler傳遞到主執行緒中。

4.2 Retrofit的網路請求

現在我們來看一下 Retrofit的網路請求:

call.enqueue(new Callback<List<Repo>>() {
    @Override
    public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
        // 資料返回成功
        Log.i("zxy", "onResponse: success");
    }

    @Override
    public void onFailure(Call<List<Repo>> call, Throwable t) {
       // 資料返回失敗
        Log.i("zxy", "onFailure: fail");
    }
});
複製程式碼

首先我們知道了 這個callExecutorCallbackCall的 例項:

讓我們找到 enqueue 方法:

@Override public void enqueue(final Callback<T> callback) {
  Objects.requireNonNull(callback, "callback == null");
  // 這個 delegate 就是 OKHttpCall 
  delegate.enqueue(new Callback<T>() {
    
    @Override public void onResponse(Call<T> call, final Response<T> response) {
      // callbackExecutor 會通過handler 將資料回撥到主執行緒
      callbackExecutor.execute(() -> {
        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) {
      callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));
    }
  });
}
複製程式碼

已經知道delegate 就是 OKHttpCall,現在進入到OKHttpCall 中去找到 enqueue方法:

@Override public void enqueue(final Callback<T> callback) {

  okhttp3.Call call;
  Throwable failure;
      // .....
       try {
           // 在這裡建立 OkHttp3的Call
            call = rawCall = createRawCall();
           } catch (Throwable t) {
             throwIfFatal(t);
               failure = creationFailure = t;
           }
  // .....
  // 如果存在異常,就回撥出去
  if (failure != null) {
    callback.onFailure(this, failure);
    return;
  }
    // ......
  // OkHttp 執行 請求
  call.enqueue(new okhttp3.Callback() {
    @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
      Response<T> response;
      try {
       // 將 response 進行處理
        response = parseResponse(rawResponse);
      } catch (Throwable e) {
        throwIfFatal(e);
        callFailure(e);
        return;
      }

      try {
        // 成功就回撥出去
        callback.onResponse(OkHttpCall.this, response);
      } catch (Throwable t) {
        throwIfFatal(t);
        t.printStackTrace(); // TODO this is not great
      }
    }

    @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) {
        throwIfFatal(t);
        t.printStackTrace(); // TODO this is not great
      }
    }
  });
}
複製程式碼

其實可以看到最終的網路請求是由OkHttp3 做出的。

我們看這個方法 createRawCall

private okhttp3.Call createRawCall() throws IOException {
  okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
  if (call == null) {
    throw new NullPointerException("Call.Factory returned null.");
  }
  return call;
}
複製程式碼

這裡的callFactory 其實就是 OkHttp3OkHttpClient 的例項, 在Retrofitbuild()中:

okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
  callFactory = new OkHttpClient();
}
複製程式碼

在讓我們看requestFactory,這個物件 在ServiceMethod中 就有生成:

static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
  RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
 // .......
  return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
複製程式碼

requestFactorycreate()方法中:

okhttp3.Request create(Object[] args) throws IOException {
  //......
  RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl,
      headers, contentType, hasBody, isFormEncoded, isMultipart);
  // .....
  return requestBuilder.get()
      .tag(Invocation.class, new Invocation(method, argumentList))
      .build();
}
複製程式碼

create最後返回的是OkHttp3Request物件。

所以其實Retrofit中的網路請求是由OkHttp3來執行的。

4.3 關於ConverterFactory

在最開始的實現Retrofit的物件的時候:

.addConverterFactory(GsonConverterFactory.create())
複製程式碼

HttPServiceMethod中的parseAnnotations

// 這個就是 獲得 responseConverter
Converter<ResponseBody, ResponseT> responseConverter =
    createResponseConverter(retrofit, method, responseType);
複製程式碼

讓我們繼續進入到 createResponseConverter 中:

private static <ResponseT> Converter<ResponseBody, ResponseT> createResponseConverter(
    Retrofit retrofit, Method method, Type responseType) {
  Annotation[] annotations = method.getAnnotations();
  try {
    return retrofit.responseBodyConverter(responseType, annotations);
  } catch (RuntimeException e) { // Wide exception range because factories are user code.
    throw methodError(method, e, "Unable to create converter for %s", responseType);
  }
}
複製程式碼

繼續進入到retrofit#responseBodyConverter 中:

public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
  return nextResponseBodyConverter(null, type, annotations);
}

public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
    @Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
//......
  int start = converterFactories.indexOf(skipPast) + 1;
  for (int i = start, count = converterFactories.size(); i < count; i++) {
    // 在converter物件
    Converter<ResponseBody, ?> converter =
        converterFactories.get(i).responseBodyConverter(type, annotations, this);
    if (converter != null) {
      //noinspection unchecked
      return (Converter<ResponseBody, T>) converter;
    }
  }
//......
}
複製程式碼

GsonConverterFactory為例:

@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
    Retrofit retrofit) {
  TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
   // 在這裡就是返回了Converter物件
  return new GsonResponseBodyConverter<>(gson, adapter);
}
複製程式碼

那麼它在哪裡被使用呢?

call 執行enqueue 的時候,通過回撥onResponse返回資料,在parseResponse()方法中會對資料進行處理:

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
  ResponseBody rawBody = rawResponse.body();
    //......
  ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
  try {
    // 這裡的responseConverter 就是 GsonResponseBodyConverter
    T body = responseConverter.convert(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;
  }
}
複製程式碼

GsonResponseBodyConverter 的重寫方法convert中就會對資料進行處理:

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

到這裡,Retrofit的分析就結束了。

總結

Retrofit的程式碼簡單優雅,值得我們去學習,學習原始碼有助於我們更好的思考。從原始碼中不僅僅是學習優秀的程式碼也是學習大神們的思想。

這裡有一些優秀的博文值得學習:

從架構角度看Retrofit的作用、原理和啟示

Retrofit是如何工作的?

相關文章