Retrofit 的神秘面紗
初識 Retrofit ,覺得很神奇,體現在以下兩點:
神奇之一:僅憑注解和接口便可執行網絡請求。
public interface GithubService {
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);
}
神奇之二:接口方法的返回值變幻無窮。
無論你是用默認的方式,或者是RxJava,無論是在Java或是Android中調用,Retrofit均可滿足,體現在接口的返回值,如下代碼:
public interface GithubService {
//默認方式
@GET("users/{username}")
Call<User> getUserByDefault(@Path("username") String username);
//RxJava方式
@GET("users/{username}")
Observable<User> getUserByRxJava(@Path("username") String username);
}
這兩個神奇之處,猶如兩層神秘面紗,將Retrofit原本俊俏嬌羞的臉龐,遮掩得若隱若現。若不揭開面紗,一睹芳容,便無處釋放程序員們最原始的沖動,于是一起來閱讀源碼吧。
尋找接口的實現類
對于第一個神奇之處,首先我們來看看GithubService接口是如何調用的:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
Call<User> call = githubService.getUser("geniusmart");
仔細斟酌這段代碼, GitHubService 本身是個接口,并調用了 getUser() 方法獲得返回值,那么問題來了:
- GitHubService 是由我們定義的接口,但是我們并沒有定義實現類,實現類到底在哪里?
- 既然我們沒有定義實現類,那么大膽假設該實現類是由Retrofit框架提供的。對于框架而言,該接口是由開發者自定義的,框架無法做到未卜先知,那么他是如何做到的?
以上兩個問題可以總結為:框架如何動態生成 GitHubService 的實現類? 答案也不難猜測——動態代理模式。(對動態代理有疑問的同學,可以先看下 《公共技術點之 Java 動態代理》 。
接下來驗證我們的猜測, GitHubService 的來源在此行代碼中:
GitHubService service = retrofit.create(GitHubService.class);
查看 create() 的源碼,所有謎底都將揭開:
通過JDK實現動態代理
這個方法生成并返回了 GitHubService 的動態代理對象,在執行接口的每個方法時,實際執行的是 invoke() ,而該方法里的邏輯便是此框架的主體流程,如下代碼:
public Object invoke(Object proxy, Method method, Object... args){
//1.負責注解的解析
ServiceMethod serviceMethod = loadServiceMethod(method);
//2.負責與OKHttp3的對接,處理同步和異步發送網絡請求
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
//3.第二層面紗,下文再做解釋
return serviceMethod.callAdapter.adapt(okHttpCall);
}
這個方法里,做了三件事情:
- 解析方法的注解,比如 get/post、url、Headers 等,解析的結果存放于 ServiceMethod 對象中。
- 創建 OkHttpCall ,該對象負責與OKHttp3對接,處理同步或異步的網絡請求。
- 這是第二層神秘面紗的謎底,下文再做解釋。
至此,我們揭開了Retrofit的第一層神秘面紗,這是一位簡單而優雅的小女子,表面樸實無華,恬靜清新,令人迫不及待想要揭開她的第二層面紗,知曉她的內心世界。
變幻無窮的適配
首先來回顧下上文提到的第二個神秘之處,同樣是獲取用戶信息的網絡請求,可以返回 Call<User> 或 Observable<User> ,這是如何做到的?
上文中, invoke() 做的第三件事情便是獲取返回值,如下:
return serviceMethod.callAdapter.adapt(okHttpCall);
其中, adapt 是適配的意思,其目的是變廢為寶,將指定的輸入類型適配成實際需要的輸出類型,來查看下它的源碼:
public interface CallAdapter<T> {
<R> T adapt(Call<R> call);
}
我們重點關注下 adapt 的輸入輸出。這里的設計非常大膽,輸入是有約束的,即 Call 類型, 而輸出為泛型 T ,也就是說輸出是沒有任何約束的 ,如此一來,擴展性極強,可以指定任何的類型。
剩下來的問題便是:
-
adapt 由哪個對象觸發,即 CallAdapter 的具體實現是什么?
-
輸入類型 Call 的具體實現是什么?
-
輸出類型 T 的具體實現是什么?
這里直接給出答案,如下兩圖:
默認方式的適配
RxJava方式的適配
-
通過上面兩圖,可以看到,輸入對象是固定的,即 OkHttpCall 對象,網絡請求實際上便是由該對象發起的。
-
默認方式的輸出對象是 ExecutorCallbackCall ,它持有 OkHttpCall 對象,指定了網絡請求執行結束后的回調函數在UI線程中執行。
-
RxJava方式的輸出對象是 Observable ,持有該對象便具備函數式編程的能力。
-
adapt() 的調用者——適配器 CallAdapter 是動態配置的,默認方式無需配置,如果使用RxJava,則需要配置適配器的工廠對象,如下代碼:
Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build();
這樣設計簡直是解耦得一塌糊涂,我們可以任意變換調用方式,也可以任意擴展 CallAdapter 來定義新的調用方式,而框架無需做任何調整。比如 JakeWharton 前幾天開源的RxJava2的適配—— retrofit2-rxjava2-adapter ,比如針對Google函數式編程庫 Agera的適配—— retrofit-agera-call-adapter ,簡直OCP得一塌糊涂。
至此,第二層神秘面紗緩緩而落,這是一位心靈手巧的奇女子,你給她一針一線,她還你一幅刺繡,刺繡上可以是錦繡山河,也可以是紫氣東來;你給她新鮮的食材,她變幻出色香味俱全的美味佳肴。
總結
動態代理、適配是Retrofit的核心,理解清楚這兩點,再去梳理框架的其他細節,方可事半功倍。除此之外,框架內部使用了大量的Builder模式和Factory模式,這兩個模式使得創建對象和獲取對象更有條理,更清晰易懂。值得一提的是,這個框架配備了完整的單元測試用例,非常值得我們學習。
參考文章
http://square.github.io/retrofit/
https://github.com/square/retrofit
來自:http://www.jianshu.com/p/0730fc155996