Android進階:七、Retrofit2.0原理解析之最簡流程【上】

想念你的Android發表於2019-04-08

Android進階:七、Retrofit2.0原理解析之最簡流程【上】

retrofit 已經流行很久了,它是Square開源的一款優秀的網路框架,這個框架對okhttp進行了封裝,讓我們使用okhttp做網路請求更加簡單。但是光學會使用只是讓我們多了一個技能,學習其原始碼才能讓我們更好的成長。

本篇文章是在分析retrofit的原始碼流程,有大量的程式碼,讀者最好把原始碼下載下來匯入IDE,然後跟著一起看,效果會更好(文末有原始碼獲取方式)

一.retrofit入門

  • 定義網路請求的API介面:
interface GithubApiService {
        @GET("users/{name}/repos")
        Call<ResponseBody> searchRepoInfo(@Path("name") String name);
    }
複製程式碼

使用了註解表明請求方式,和引數型別,這是retrofit的特性,也正是簡化了我們的網路請求過程的地方!

  • 初始化一個retrofit的例項:
Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.github.com/")
                .build();
複製程式碼

retrofit的例項化很簡單,採用鏈式呼叫的設計,把需要的引數傳進去即可,複雜的引數我們這裡就不舉例了。

  • 生成介面實現類:
GithubApiService githubService = retrofit.create(service)
Call<ResponseBody> call = githubService.searchRepoInfo("changmu175");
複製程式碼

我們呼叫retrofit的create方法就可以把我們定義的介面轉化成實現類,我們可以直接呼叫我們定義的方法進行網路請求,但是我們只定義了一個介面方法,也沒有方法體,請求方式和引數型別都是註解,create是如何幫我們整理引數,實現方法體的呢?一會我們通過原始碼解析再去了解。

  • 發起網路請求
//同步請求方式
 call.request();
 //非同步請求方式
 call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                //請求成功回撥
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                //請求與失敗回撥
            }
        });
複製程式碼

至此,retrofit的一次網路請求示例已經結束,基於對okhttp的封裝,讓網路請求已經簡化了很多。當然retrofit最適合的還是REST API型別的介面,方便簡潔。

下面我們就看看retrofit的核心工作是如何完成的!

二.retrofit初始化

retrofit的初始化採用了鏈式呼叫的設計

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

很明顯這個方法是在傳一些需要的引數,我們簡單的跟蹤一下:

首先看看Builder()的原始碼:

public Builder() {
      this(Platform.get());
    }
複製程式碼

這句程式碼很簡單就是呼叫了自己的另一個建構函式:

Builder(Platform platform) {
      this.platform = platform;
    }
複製程式碼

這個建構函式也很簡單,就是一個賦值,我們把之前的Platform.get()點開,看看裡面做在什麼:

private static final Platform PLATFORM = findPlatform();

static Platform get() {
    return PLATFORM;
  }
複製程式碼

我們發現這裡使用使用了一個餓漢式單例,使用Platform.get()返回一個例項,這樣寫的好處是簡單,執行緒安全,效率高,不會生成多個例項!

我們再看看findPlatform() 裡做了什麼:

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

    ....省略部分程式碼...
 }
複製程式碼

所以是判斷了一下系統,然後根據系統例項化一個物件。這裡面應該做了一些和Android平臺相關的事情,屬於細節,我們追究,感興趣的可以只看看。
再看看baseUrl(url)的原始碼

public Builder baseUrl(String baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      HttpUrl httpUrl = HttpUrl.parse(baseUrl);
      ....
      return baseUrl(httpUrl);
    }

public Builder baseUrl(HttpUrl baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      ....
      this.baseUrl = baseUrl;
      return this;
    }
複製程式碼

這兩段程式碼也很簡單,校驗URL,生成httpUrl物件,然後賦值給baseUrl

看看build() 方法在做什麼
引數基本設定完了,最後就要看看build() 這個方法在做什麼:

 public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }

      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }
      ....

      return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
          unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
    }
  }
}
複製程式碼

程式碼中有大量的引數校驗,有些複雜的引數我們沒有傳,所以我就把那些程式碼刪除了。簡單看一下也能知道,這段程式碼就是做一些引數校驗,baseUrl不能為空否則會拋異常,至於其他的引數如果為null則會建立預設的物件。其中callFactory就是okhttp的工廠例項,用於網路請求的。
最後我們看到,這個方法最終返回的是一個Retrofit的物件,初始化完成。

三.生成介面實現類

剛才我們就講過retrofit.create這個方法很重要,它幫我們生成了介面實現類,並完成了方法體的建立,省去了我們很多工作量。那我們來看看它是如何幫我們實現介面的。

public <T> T create(final Class<T> 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, @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);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.adapt(okHttpCall);
          }
        });
  }
複製程式碼

這段程式碼實際上是使用了動態代理的設計模式,而且這個方法封裝的非常好,我們只需要呼叫 方法就可以獲得我們需要的實現類,遵循了迪米特法則(最少知道原則)。

瞭解動態代理的人都知道我們要重寫Object invoke(Object proxy, Method method,[@Nullable](https://xiaozhuanlan.com/u/undefined) Object[] args) 方法,這個方法會傳入我們需要的實現的方法,和引數,並返回我們需要的返回值。

retrofit在重寫這個方法的時候做了三件事:

  • 先判斷了這個方法的類是不是一個Object.class),就直接返回方法原有的返回值。
  • 判斷這個方法是不是DefaultMethod,大家都知道這個方法是Java 8出來的新屬性,表示介面的方法體。
  • 構建一個ServiceMethod<Object, Object>物件和OkHttpCall<Object>物件,並呼叫
    serviceMethod.adapt(okHttpCall)方法將二者繫結。

我們看看這個方法的原始碼:

T adapt(Call<R> call) {
    return callAdapter.adapt(call);
  }
複製程式碼

這個callAdapter我們在初始化retrofit的時候沒有使用:
addCallAdapterFactory(CallAdapterFactory)傳值,所以這裡是預設的DefaultCallAdapterFactory

那我們再看看DefaultCallAdapterFactory裡的adapt(call)方法:

@Override public Call<Object> adapt(Call<Object> call) {
        return call;
      }
複製程式碼

直接返回引數,也就是OkHttpCall<Object>的物件。所以如果沒有自定義callAdapter的時候,我們定義介面的時候返回值型別應該是個Call型別的。
那麼,至此這個create方法已經幫我們實現了我們定義的介面,並返回我們需要的值。

由於文字過長的緣由,我們暫且分為上下兩文,下文講講到請求引數整理丶Retrofit網路請求已經自己的一些總結,當然凡事無絕對,只是自己Retrofit原理的一些看法

文章開頭說的原始碼領取方式:關注我私信【資料】


相關文章