先看一個問題,Retrofit2 中定義的接口能夠直接返回一個ResponseBody
的String
嗎?html
public interface RestClientV1 {
@GET("order/detail")
String getOrderDetail(@Query("orderId") long orderId);
}
複製代碼
若是你不能確定的回答能夠,同時不能清楚的知道該怎麼作,很是推薦閱讀這篇文章。這是一篇 Retrofit2 的進階用法的文章,若是不熟悉 Retrofit2 的基本用法,建議先去 官網 看一下教程,再過來看這篇文章。若是你正在考慮如何使用 Retrofit2 來封裝一個網絡層,這篇文章講到的示例、原理和設計思想應該會很是適合你。java
咱們先看兩段源碼:android
CallAdapter.java
public interface CallAdapter<R, T> {
Type responseType();
T adapt(Call<R> call);
abstract class Factory {
public abstract @Nullable CallAdapter<?, ?> get(Type returnType,
Annotation[] annotations,
Retrofit retrofit);
//省略
}
}
複製代碼
這個適配器的做用是將一個R
,轉化爲一個自定義類型T
;其中的適配器工廠會根據returnType
(就是上面接口中定義的返回類型)來返回相應的適配器。git
Converter.java
public interface Converter<F, T> {
T convert(F value) throws IOException;
abstract class Factory {
public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,
Annotation[] annotations, Retrofit retrofit) {
return null;
}
//省略
}
}
複製代碼
這個轉換器的做用就是將F
對象轉換爲T
對象;其中轉換器工廠中的responseBodyConverter(..)
須要根據type
(就是咱們自定義的響應結果類型)來返回相應的轉換器,而且這個轉換器中的F
被指定爲了ResponseBody
。github
你們可能對這兩個接口的設計和做用仍是有點困惑,不要緊,下面我們會反覆說到這兩個接口。api
下面開始寫咱們的實現代碼:網絡
StringCallAdapterFactory
和StringCallAdapter
public class StringCallAdapterFactory extends CallAdapter.Factory {
@Nullable
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if(returnType == String.class)
return new StringCallAdapter();
return null;
}
class StringCallAdapter implements CallAdapter<String,String>{
@Override
public Type responseType() {
return String.class;
}
@Override
public String adapt(Call<String> call) {
try {
return call.execute().body();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
}
}
複製代碼
咱們在StringCallAdapterFactory
的get(..)
方法中,當發現接口的返回類型是String
時,返回咱們自定義的StringCallAdapter
,而StringCallAdapter
,顧名思義就是Call
(retrofit2.Call
)的一個適配器,做用就是將Call轉化成String
(這個邏輯具體是在adapt(..)
方法裏面處理)。特別地,responseType()
方法的做用是告訴Converter
,我須要一個String.class
的響應數據。
app
StringConverterFactory
和StringConverter
public class StringConverterFactory extends Converter.Factory {
@Nullable
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if (type == String.class) {
return new StringConverter();
}
return null;
}
class StringConverter implements Converter<ResponseBody, String> {
@Override
public String convert(ResponseBody value) throws IOException {
return value.string();
}
}
}
複製代碼
類似的,咱們在StringConverterFactory
的responseBodyConverter(..)
方法中,當發現參數type
(就是上一步的responseType()
方法返回值)是String
的時候,返回一個自定義的StringConverter
,這個適配器的做用是把 http 響應數據ResponseBody
轉化爲String
,其實現也很簡單,直接調用ResponseBody.string()
方法便可。框架
當咱們在寫跟界面無關的代碼的時候,特別推薦使用單元測試來驗證邏輯的正確性,這是一件省時省力,又能夠有效確保質量的作法。下面是咱們寫的一個簡單的測試示例:異步
@Before
public void create() {
mockWebServer = new MockWebServer();
mockWebServer.setDispatcher(new MockDispatcher());
OkHttpClient client = new OkHttpClient.Builder().build();
restClientV1 = new Retrofit.Builder()
.baseUrl(mockWebServer.url("/"))
.client(client)
.addCallAdapterFactory(new StringCallAdapterFactory())
.addConverterFactory(new StringConverterFactory())
.build()
.create(RestClientV1.class);
}
@Test
public void test() {
System.out.println(restClientV1.getOrderDetail(1));
}
複製代碼
運行結果: hi man,this is order detail(orderId=1)
完整可運行的代碼在這裏
分析框架原理的時候,通常我會先去找切入點,或者是疑惑點。基於上面的例子,有 3 個疑惑點:
RestClientV1.getOrderDetail(..)
方法返回類型(稱爲returnType
) 是如何起做用的?CallAdapterFactory
是如何起做用的?ConverterFactory
是如何起做用的?下面咱們順着這個思路來查看相應源碼:
returnType
到CallAdapter<T, R>
在源碼中咱們先找到獲取CallAdapter<T, R>
的地方
private CallAdapter<T, R> createCallAdapter() {
Type returnType = method.getGenericReturnType();
//省略
Annotation[] annotations = method.getAnnotations();
try {
//<-關鍵代碼
return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(e, "Unable to create call adapter for %s", returnType);
}
}
複製代碼
代碼中的 method 對象就是
RestClientV1.getOrderDetail(..)
方法
上面關鍵代碼處以returnType
爲參數調用了retrofit.callAdapter(..)
方法獲取一個適配器,接着看
public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
return nextCallAdapter(null, returnType, annotations);
}
public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
int start = adapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = adapterFactories.size(); i < count; i++) {
CallAdapter<?, ?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
}
複製代碼
核心是這一行adapterFactories.get(i).get(returnType, annotations, this)
,就是循環調用CallAdapter.Factory
的 get 方法來獲取一個可用的適配器,一旦找到就返回。注意這裏的returnType
就是String.class
,再回過頭來看咱們以前的 2.1 的代碼,
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if(returnType == String.class)
return new StringCallAdapter();
return null;
}
複製代碼
這時咱們就拿到了一個StringCallAdapter
對象。拿到後還作了一件事情,再看代碼
callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
//省略
responseConverter = createResponseConverter();
複製代碼
其中把callAdapter.responseType()
方法的結果存了下來,而後調用了createResponseConverter()
responseType
到Converter<F, T>
再看createResponseConverter()
方法代碼:
private Converter<ResponseBody, T> createResponseConverter() {
Annotation[] annotations = method.getAnnotations();
try {
return retrofit.responseBodyConverter(responseType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(e, "Unable to create converter for %s", responseType);
}
}
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<ResponseBody, ?> converter =
converterFactories.get(i).responseBodyConverter(type, annotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter<ResponseBody, T>) converter;
}
}
}
複製代碼
經過上面的代碼追蹤,咱們知道在retrofit.responseBodyConverter(..)
中的responseType
參數正是StringCallAdapter
中返回的String.class
,再看核心的獲取Converter
的方法,跟獲取CallAdapter
的方法基本一致,結合 2.2 的代碼
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if (type == String.class) {
return new StringConverter();
}
return null;
}
複製代碼
顯然,這時會返回一個StringConverter
對象。
CallAdapter<T, R>
和Converter<F, T>
如何起做用?這時框架已經獲取到的StringCallAdapter
和StringConverter
,而後它們會被存放在一個ServiceMethod
對象中
final class ServiceMethod<R, T> {
final CallAdapter<R, T> callAdapter;
private final Converter<ResponseBody, R> responseConverter;
}
複製代碼
咱們再看看這兩個對象分別是在哪一個地方起做用的
CallAdapter<T, R>
的做用處public <T> T create(final Class<T> service) {
//省略部分代碼
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
//省略部分代碼
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);//<- 關鍵代碼在這裏
}
});
}
複製代碼
上面的代碼使用動態代理返回了service
接口的一個實現類,調用restClient.getOrderDetail(1)
方法時,它的返回值就是invoke(..)
方法的返回值,這個返回值就是調用StringCallAdapter.adapt(..)
的返回值。再看 2.1 中我們自定義適配器的代碼
@Override
public String adapt(Call<String> call) {
try {
return call.execute().body();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
複製代碼
咱們直接將body
以String
的形式返回了。Retrofit
這個設計很是的巧妙和靈活,咱們能夠將一個 http 請求包裝成任何對象返回,能夠選擇包裝的時候直接執行 http 請求(就像咱們這個例子),也可使其再調用新的包裝對象的某個方法再執行 http 請求(好比自帶的Call
)。
Converter<F, T>
的做用處前面咱們看到實際執行 http 請求的是OkHttpCall
,在請求執行完成後有一段解析Response
的代碼
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) {
catchingBody.throwIfCaught();
throw e;
}
}
複製代碼
這個方法的做用是將okhttp3.Response
轉化爲retrofit2.Response
。
由於 Retrofit2 最終也是使用 Okhttp 來發起 http 請求的。 再看上面關鍵代碼的實現
/** Builds a method return value from an HTTP response body. */
R toResponse(ResponseBody body) throws IOException {
return responseConverter.convert(body);
}
複製代碼
其實很簡單,直接調用了咱們自定義的StringConverter
來獲取一個自定義對象,再看 2.2 中自定義的轉換器的代碼
@Override
public String convert(ResponseBody value) throws IOException {
return value.string();
}
複製代碼
這個也很是簡單,就很少解釋了。
最後我們再用圖形的方式來捋一下整個調用流程:
前面這個例子咱們在實際中並不會這麼用,下面咱們使用 Retrofit2
來作一個有實用價值的 App 網絡層封裝,在這以前咱們先看看在網絡層封裝時基本都會遇到的幾個問題:
Activity
/Fragment
)已被銷燬時致使的崩潰爲了解決上面的問題,咱們設計本身的Call
接口,在看Call
接口的代碼前,有幾點須要說明下:
public class ApiResponse<T> {
/** * api業務響應狀態 */
private String status;
/** * api業務數據 */
private T content;
/** * api業務響應失敗錯誤碼 */
private String errorCode;
/** * 錯誤字符串信息 */
private String errorMsg;
}
複製代碼
正常狀況下,服務端會返回請求業務成功( ok )或者失敗( fail );異常狀況下,客戶端增長一個 error 的 status ,用於表示非 200 的請求以及調用拋出異常的狀況
ProgressOperation
接口public interface ProgressOperation {
/**加載數據失敗*/
void showFailed();
/**加載數據成功*/
void showContent();
/**開始加載數據*/
void showProgress();
}
複製代碼
這樣當咱們須要使用不一樣的界面 展現形式(如:
ProgressDialog
或者Progressbar
)來實現 Loading 的時候,只要分別作一個實現類便可
下面是自定義的Call
接口代碼:
public interface Call<T> {
/** * 設置http200 ok的回調 */
Call<T> ok(@NonNull Observer<T> observer);
/** * 設置http200 fail的回調 */
Call<T> fail(@NonNull Observer<ApiResponse<T>> observer);
/** * 設置error的回調 */
Call<T> error(@NonNull Observer<ApiResponse<T>> observer);
/** * 設置進度監聽 */
Call<T> progress(@NonNull ProgressOperation progressOperation);
/** * 執行異步請求,綁定組件生命週期(獲取所有狀態結果) */
void enqueue(@NonNull LifecycleOwner owner, @NonNull Observer<ApiResponse<T>> observer);
/** * 執行異步請求,綁定組件生命週期(獲取部分狀態結果) */
void enqueue(@NonNull LifecycleOwner owner);
/** * 執行異步請求,但不須要綁定組件生命週期(獲取部分狀態結果) */
void enqueue();
/** * 執行異步請求,但不須要綁定組件生命週期(獲取所有狀態結果) */
void enqueue(@NonNull Observer<ApiResponse<T>> observer);
/** * 發起同步網絡請求 */
ApiResponse<T> execute();
/** * 取消請求 * 一、對於單個http請求,取消時若是尚未開始執行,則不執行;若是在執行中,則會確保執行結束不會回調,不確保必定能被取消 * 二、對於多個連續http請求,除了1的特性外,取消後剩下的未開始執行請求也不會被執行 */
void cancel();
/** * 是否被取消 * * @return */
boolean isCancelled();
}
複製代碼
上面的代碼註釋已經比較詳細,你們能夠仔細看下
LiveData
爲基礎作了單個請求Call
的實現類,實現這塊就再也不講解,能夠有多種實現方式,有興趣你們能夠自行查看源碼。下面我們看一下如何使用這個Call
restClientV1.ok("1").progress(progress).ok(content -> {
System.out.println(content.getName());
}).enqueue(lifecycleOwner);
複製代碼
其中
lifecycleOwner
能夠直接使用 supportv26 包中的Activity
或Fragment
,傳入這個對象後,若是組件已處於 destroy 狀態,則回調不會被執行
Task task1 = ((lifeState, apiResponse) -> restClientV1.okArray("1").execute());
Task task2 = ((lifeState, apiResponse) -> restClientV1.ok("1").execute());
Call<Content> call = MergeCall.task(task1, task2);
call.enqueue(lifecycleOwner,apiResponse -> {
if(apiResponse.isOk()){
//更新UI
}else{
//顯示錯誤信息
}
});
複製代碼
上面咱們使用了本身定義的
Task
接口來描述一個任務,執行多個任務的時候,只有上個任務成功,纔會執行下一個任務,不然會直接執行回調
public class CheckTokenInterceptor implements Call.Interceptor {
public static final CheckTokenInterceptor INSTANCE = new CheckTokenInterceptor();
/** * 返回true表示中止下一步執行 */
@Override
public boolean preExecute() {
return false;
}
/** * 對於異步請求,返回true表示中止下一步執行 */
@Override
public boolean onResponse(ApiResponse apiResponse) {
return checkTokenExpired(apiResponse.getErrorCode());
}
private boolean checkTokenExpired(String errorCode) {
//檢查token是否過時
return false;
}
}
複製代碼
而後能夠這樣設置攔截器
Retrofit
對象時調用方法addCallAdapterFactory(new CustomCallAdapterFactory(CheckTokenInterceptor.INSTANCE))
;MergeCall
的時候傳入攔截器public static Call task(Executor executor, List<Interceptor> interceptors, Task... tasks) {
return new MergeCall(Arrays.asList(tasks), interceptors, executor);
}
public static Call task(Task... tasks) {
return task(AsyncTask.THREAD_POOL_EXECUTOR, tasks);
}
//<- 關鍵在這裏
public static Call task(Executor executor, Task... tasks) {
return MergeCall.task(executor, Arrays.asList(CheckTokenInterceptor.INSTANCE), tasks);
}
複製代碼
小結
從上面對於單個和多個串行請求的設計和用法中能夠看到,咱們解決了前面提到的4個問題。在這種設計下,咱們使用本身的Call
接口作爲網絡層和其它層交互的紐帶,其它層(Presenter
/ViewModel
、Activity
/Fragment
)徹底不知道底層使用的是什麼網絡框架,那麼若是哪天有一個更好用的網絡框架,咱們替換起來也是很是方便。最後再放一下本文 demo 的 源碼連接。
在 demo 中,你們能夠從 test 目錄下相應的測試用例開始看起
立刻過年了,祝你們新年快樂^_^