本篇文章將採用按部就班的編碼方式,從零開始實現一個Retorift框架,在實現過程當中不斷提出問題並分析實現,最終開發出一個mini版的Retrofit框架java
爲了更好的演示框架的實現過程,這裏我先建立了一個簡單的Demo項目android
這個Demo項目中主要包含3個部分git
@Data @ToString public class BaseResponse<T> { private boolean error; private T results; }
package com.knight.sample.entity; import java.util.List; import java.util.Map; public class XianduResponse extends BaseResponse<List<GankEntity>> { }
package com.knight.sample; import java.io.IOException; /** * 項目封裝的統一網絡請求的回調 * @param <T> */ public interface NetCallback<T> { void onFailure(Exception e); void onSuccess(T data); }
package com.knight.sample; import android.util.Log; import com.google.gson.Gson; import com.google.gson.stream.JsonReader; import java.io.IOException; import okhttp3.Call; import okhttp3.Callback; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class RestService { private static OkHttpClient okHttpClient; public static void init() { okHttpClient = new OkHttpClient.Builder() .build(); } public static<T> void todayGank(Class<T> responseClazz,NetCallback<T> callback) { Request request = new Request.Builder().url("http://gank.io/api/today") .get() .build(); okHttpClient.newCall(request).enqueue(new WrapperOkHttpCallback<>(responseClazz,callback)); } public static<T> void xianduGank(int count, int page,Class<T> responseClazz,NetCallback<T> callback) { Request request = new Request.Builder() .url("http://gank.io/api/xiandu/data/id/appinn/count/" + count + "/page/" + page) .get().build(); okHttpClient.newCall(request).enqueue(new WrapperOkHttpCallback<>(responseClazz,callback)); } static class WrapperOkHttpCallback<T> implements Callback { private static Gson gson = new Gson(); private Class<T> clazz; private NetCallback<T> callback; public WrapperOkHttpCallback(Class<T> responseClazz, NetCallback<T> netCallback) { this.clazz = responseClazz; this.callback = netCallback; } @Override public void onFailure(Call call, IOException e) { Log.e("WrapperOkHttpCallback", "onFailure"); e.printStackTrace(); callback.onFailure(e); } @Override public void onResponse(Call call, Response response) throws IOException { JsonReader jsonReader = gson.newJsonReader(response.body().charStream()); T entity = gson.getAdapter(clazz).read(jsonReader); Log.d("response", entity.toString()); callback.onSuccess(entity); } } }
在NetworkService類中咱們目前定義了2個Http 請求 todayGank 和 xianduGank ,目前兩個請求方式都是 Get 其中 xianduGank 須要傳入 count及 page參數分別表示每頁數據的數據以及請求的頁碼,除此以外這兩個網絡請求都須要傳入 一個Class對象表示響應的Json數據對應的Model,以便在內部使用Gson來解析,以及網絡請求的異步回調 NetCallbackgithub
咱們不直接使用OkHttp提供的Callback 而是在內部簡單的作了封裝轉換成項目本身的NetCallback,由於對項目的開發人員來講,更但願的是可以直接在Callback的success回調中直接獲得響應的Json數據對應的JavaBean.面試
本次提交詳細代碼: https://github.com/Knight-ZXW...
上文模擬的代碼只是一個簡單的例子,可能會有更好的封裝方式,但這並非咱們這篇文章想要討論的重點。咱們回到示例中RestService類中的代碼部分,看下目前網絡請求的寫法編程
由於咱們項目中已經有了OKHttp這個網絡庫了,有關Http具體的鏈接及通訊的髒話累活均可以交給他來處理,對於項目開發者,事實上咱們只須要配置如下Http請求部分json
假設咱們已經具有了 Java註解 以及 動態代理的相關知識,知道如下信息api
每個網絡接口調用請求的url地址和請求方式都是惟一的 ,那麼對於一個簡單的網絡請求 咱們能不能使用 註解 + 動態代理 來簡化這一過程,改成聲明式的編程方式來實現網絡調用,好比就像這樣性能優化
/** * Created by zhuoxiuwu * on 2019/4/25 * email nimdanoob@gmail.com */ public interface NetRestService { @GET("http://gank.io/api/today") public Call todayGank(); }
咱們在一個抽象接口類中添加了一個方法,在方法上添加了註解 @GET 表示這是一個Http GET請求的調用,註解中GET帶的默認參數表示GET請求的地址。聲明這個方法後,咱們再經過Java動態代理技術在運行時解析這個方法上的註解的信息,內部經過調用OKHttp的相關方法生成一個 Call對象網絡
有了大概思路了,咱們接下來先簡單的實現這樣一個小例子來驗證咱們的想法是否可行
新建一個註解類@GET
package retrofit2.http; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created by zhuoxiuwu * on 2019/4/25 * email nimdanoob@gmail.com */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface GET { //註解中 方法名寫成value 這樣的話,在使用註解傳入參數時就不用帶key了,它會做爲一個默認的調用 String value(); }
新建一個處理Http接口類的動態代理的類Retrofit,由於咱們實際網絡請求的調用是依賴OKHttp,因此咱們要求構造函數傳入OkHttp對象
目前Retrofit 類只有一個方法 它接收一個抽象類,並生成該抽象類的代理實現。
package retrofit2; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import okhttp3.OkHttpClient; import okhttp3.Request; import retrofit2.http.GET; public class Retrofit { private OkHttpClient mOkHttpClient; public Retrofit(OkHttpClient mOkHttpClient) { this.mOkHttpClient = mOkHttpClient; } @SuppressWarnings("unchecked") public <T> T createService(final Class<T> service) { return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //獲取方法全部的註解 final Annotation[] annotations = method.getAnnotations(); for (int i = 0; i < annotations.length; i++) { if (annotations[i] instanceof GET) { //若是註解是GET類型 final GET annotation = (GET) annotations[i]; final String url = annotation.value(); final Request request = new Request.Builder() .url(url) .get().build(); return mOkHttpClient.newCall(request); } } return null; } }); } }
目前咱們主要的目標是爲了驗證這個方案的可行性,所以createService方法內部的邏輯很簡單
1.獲取方法上的全部註解
//獲取方法全部的註解 final Annotation[] annotations = method.getAnnotations();
2.判斷若是存在@GET註解則獲取註解內的值做爲請求的地址
if (annotations[i] instanceof GET) { //若是註解是GET類型 final GET annotation = (GET) annotations[i]; final String url = annotation.value();
3.根據url構造GET請求的Request對象,並做爲參數調用OkHttpClient的newCall方法生成Call對象做爲該方法調用的返回值
final Request request = new Request.Builder() .url(url) .get().build(); return mOkHttpClient.newCall(request);
以上完成了一個對@GET註解申明的Http請求的動態代理封裝,下面咱們在本身的項目中驗證一下
1.建立一個接口類,並添加一個方法,方法的返回類型爲Call,方法是添加了@GET註解
package com.knight.sample; import okhttp3.Call; import retrofit2.http.GET; /** * Created by zhuoxiuwu * on 2019/4/25 * email nimdanoob@gmail.com */ public interface NetRestService { @GET("http://gank.io/api/today") public Call todayGank(); }
2.在項目中添加測試方法並調用
private void getToDayGankByRetrofit() { final Retrofit retrofit = new Retrofit(new OkHttpClient()); retrofit.createService(NetRestService.class).todayGank().enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { JsonReader jsonReader = gson.newJsonReader(response.body().charStream()); TodayGankResponse todayGankResponse = gson.getAdapter(TodayGankResponse.class).read(jsonReader); showHttpResult(todayGankResponse.toString()); Log.d("RetrofitTest","調用成功,結果爲"+todayGankResponse.toString()); } }); }
運行以後,方法調用成功並獲得了響應結果
D/RetrofitTest: 調用成功,結果爲BaseResponse(error=false, results={Android=[GankEntity(url=https://github.com/iqiyi/Neptune, desc=適用於Android的靈活,強大且輕量級的插件框架...
經過簡單的一個實現,咱們成功驗證了使用註解加動態代理的方式實現一個聲明式的網絡請求框架是可行的,那麼後續咱們須要繼續完善這個項目,提供對更多請求方式 以及參數的支持
對於其餘請求方式的支持,咱們能夠添加更多的表示請求方式的註解,當用戶設置了不一樣的註解,在內部咱們使用OKHttp調用相應的方法。Http的請求方式大概以下
爲了加深理解,咱們繼續簡單的實現一個POST請求,並支持傳入一個參數對象,做爲POST請求的JSON數據
首先咱們添加一個POST註解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface POST { String value(); }
package retrofit2; import com.google.gson.Gson; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.Type; import okhttp3.Call; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import retrofit2.http.GET; import retrofit2.http.POST; public class Retrofit { private OkHttpClient mOkHttpClient; public Retrofit(OkHttpClient mOkHttpClient) { this.mOkHttpClient = mOkHttpClient; } @SuppressWarnings("unchecked") public <T> T createService(final Class<T> service) { return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //獲取方法全部的註解 final Annotation[] annotations = method.getAnnotations(); for (int i = 0; i < annotations.length; i++) { if (annotations[i] instanceof GET) { //若是註解是GET類型 final GET annotation = (GET) annotations[i]; return parseGet(annotation.value(), method, args); } else if (annotations[i] instanceof POST) { final POST annotation = (POST) annotations[i]; return parsePost(annotation.value(), method, args); } } return null; } }); } private Call parseGet(String url, Method method, Object args[]) { final Request request = new Request.Builder() .url(url) .get().build(); return mOkHttpClient.newCall(request); } private Gson gson = new Gson(); private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8"); private Call parsePost(String url, Method method, Object args[]) { final Type[] genericParameterTypes = method.getGenericParameterTypes(); if (genericParameterTypes.length > 0) { final Class<?> clazz = Utils.getRawType(genericParameterTypes[0]); final String jsonBody = gson.toJson(args[0], clazz); final Request request = new Request.Builder() .url(url) .post(RequestBody.create(MEDIA_TYPE, jsonBody)) .build(); return mOkHttpClient.newCall(request); } return null; } }
在 paresePost方法中咱們首先經過Method的getGenericParameterTypes方法獲取全部參數的Type類型,而且經過Type類得到參數的原始Class類型,以後就可使用Gson轉換成對應的Json對象了。
在上面的例子中,咱們直接在框架Retrofit中使用了Gson庫作Json轉換,但做爲一個框架來講 咱們不但願直接強耦合一個第三方Json轉換庫,這部分更但願交由開發者根據具體狀況自由選擇;所以咱們能夠對這部分作下抽象封裝,提取成一個負責Json轉換的接口 由應用層傳入具體的實現.
package retrofit2; import java.lang.reflect.Type; import javax.annotation.Nullable; import okhttp3.RequestBody; /** * Created by zhuoxiuwu * on 2019/4/25 * email nimdanoob@gmail.com */ public interface Converter<F, T> { @Nullable T convert(F value); abstract class Factory { public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type) { return null; } } }
應用層須要傳入一個ConverterFactory,該工廠類負責根據傳入的Type類型,返回一個可以將該Type類型的對象轉換成RequestBody的Converter
咱們對Retrofit的構造函數以及paresePost方法作下修改,要求構造函數中傳入一個ConverterFactory的實現,並在paresePost方法中使用這個ConverterFactory來作Java對象到ReqeustBody的轉換
public class Retrofit { private OkHttpClient mOkHttpClient; private Converter.Factory mConverterFactory; public Retrofit(OkHttpClient mOkHttpClient, Converter.Factory mConverterFactory) { this.mOkHttpClient = mOkHttpClient; this.mConverterFactory = mConverterFactory; } //..省略部分代碼 private Call parsePost(String url, Method method, Object args[]) { final Type[] genericParameterTypes = method.getGenericParameterTypes(); if (genericParameterTypes.length > 0) { //直接調用獲得RequestBody final RequestBody requestBody = requestBodyConverter(genericParameterTypes[0]).convert(args[0]); final Request request = new Request.Builder() .url(url) .post(requestBody) .build(); return mOkHttpClient.newCall(request); } return null; } public <T> Converter<T, RequestBody> requestBodyConverter(Type type) { return (Converter<T, RequestBody>) mConverterFactory.requestBodyConverter(type); }
在應用層,咱們實現並傳入一個Gson的ConvertFactory的實現
package com.knight.sample; import com.google.gson.Gson; import com.google.gson.TypeAdapter; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.lang.reflect.Type; import java.nio.charset.Charset; import okhttp3.MediaType; import okhttp3.RequestBody; import okio.Buffer; import retrofit2.Converter; /** * Created by zhuoxiuwu * on 2019/4/25 * email nimdanoob@gmail.com */ public class GsonConverterFactory extends Converter.Factory { public static GsonConverterFactory create() { return create(new Gson()); } public static GsonConverterFactory create(Gson gson) { if (gson == null) throw new NullPointerException("gson == null"); return new GsonConverterFactory(gson); } private final Gson gson; private GsonConverterFactory(Gson gson) { this.gson = gson; } @Override public Converter<?, RequestBody> requestBodyConverter(Type type) { //經過Type 轉換成Gson的TypeAdapter //具體類型的json轉換依賴於這個TypeAdapter TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new GsonRequestBodyConverter<>(gson, adapter); } final static class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> { private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8"); private static final Charset UTF_8 = Charset.forName("UTF-8"); private final Gson gson; private final TypeAdapter<T> adapter; GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) { this.gson = gson; this.adapter = adapter; } @Override public RequestBody convert(T value) { Buffer buffer = new Buffer(); Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8); JsonWriter jsonWriter = null; try { jsonWriter = gson.newJsonWriter(writer); adapter.write(jsonWriter, value); jsonWriter.close(); return RequestBody.create(MEDIA_TYPE, buffer.readByteString()); } catch (IOException e) { e.printStackTrace(); return null; } } } }
繼續回到Http請求的聲明中,目前咱們方法所支持的返回類型都是OKHttp的Call對象,而Call對象從使用上來講,目前仍是有些繁瑣,原生的Call對象返回的是ResponseBody還須要開發者本身處理並作轉換。
public interface NetRestService { @GET("http://gank.io/api/today") public Call todayGank(); }
也許咱們但願這個方法能夠這樣定義
public interface NetRestService { @GET("http://gank.io/api/today") public TodayGankResponse todayGank(); }
也許咱們能夠在框架內部經過判斷方法的返回類型是否是Call對象,若是不是,就在框架內部直接同步調用網絡請求獲得響應的Json內容後直接轉換成JavaBean對象做爲方法的返回值,可是這個設想存在這樣幾個問題
所以更合理的話,在應用咱們但願的是返回一個包裝的支持異步調用的類型
好比咱們的項目本身新增了一個支持異步調用的NetCall抽象接口
/** * Created by zhuoxiuwu * on 2019/4/26 * email nimdanoob@gmail.com */ public interface NetCall<T> { public void execute(NetCallback<T> netCallback); }
咱們但願咱們的方法能夠這樣申明
public interface NetRestService { @GET("http://gank.io/api/today") public NetCall<TodayGankResponse> todayGank(); }
這樣的話在應用層咱們調用的時候就能夠像這樣使用
retrofit.createService(NetRestService.class).todayGank() .execute(new NetCallback<TodayGankResponse>() { @Override public void onFailure(Exception e) { } @Override public void onSuccess(TodayGankResponse data) { Log.d("RetrofitTest","調用成功,結果爲"+data.toString()); showHttpResult(data.toString()); } });
那麼具體要怎麼實現呢,這個功能至關於讓Retrofit框架支持 對方法返回類型的自定義適配,和Converter接口同樣的思路,咱們在框架能夠定義一個 CallAdapter接口,讓應用層來具體實現並傳入
package retrofit2; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import okhttp3.Call; /** * Created by zhuoxiuwu * on 2019/4/26 * email nimdanoob@gmail.com */ public interface CallAdapter<T> { T adapt(Call call); abstract class Factory { public abstract CallAdapter<?> get(Type returnType,Retrofit retrofit); /** * 這是一個框架提供給開發者的util方法 * 用於獲取類型的泛型上的類型 * 好比 Call<Response> 則 第0個泛型是Response.class */ protected static Type getParameterUpperBound(int index, ParameterizedType type) { return Utils.getParameterUpperBound(index, type); } /** * 獲取Type對應的Class * @param type * @return */ protected static Class<?> getRawType(Type type) { return Utils.getRawType(type); } } }
在應用層咱們能夠實現一個NetCallAdapter,支持Call對象到 NetCall對象的轉換
package com.knight.sample; import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Response; import retrofit2.CallAdapter; import retrofit2.Retrofit; /** * Created by zhuoxiuwu * on 2019/4/26 * email nimdanoob@gmail.com */ package com.knight.sample; import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Response; import retrofit2.CallAdapter; import retrofit2.Retrofit; /** * Created by zhuoxiuwu * on 2019/4/26 * email nimdanoob@gmail.com */ public class NetCallAdapterFactory extends CallAdapter.Factory { /** * returnType參數 和 retroift參數 由底層框架傳遞給開發者 * @param returnType * @param retrofit * @return */ @Override public CallAdapter<?> get(final Type returnType, final Retrofit retrofit) { //判斷返回類型是不是 NetCall if (getRawType(returnType) != NetCall.class) { return null; } //要求開發者方法的返回類型必須寫成 NetCall<T> 或者NetCall<? extends Foo> 的形式,泛型內的類型就是Json數據對應的Class if (!(returnType instanceof ParameterizedType)) { throw new IllegalStateException( "NetCall return type must be parameterized as NetCall<Foo> or NetCall<? extends Foo>"); } final Type innerType = getParameterUpperBound(0, (ParameterizedType) returnType); return new CallAdapter<NetCall>() { @Override public NetCall adapt(final Call call) { return new NetCall() { @Override public void execute(final NetCallback netCallback) { call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { netCallback.onFailure(e); } @Override public void onResponse(Call call, Response response) throws IOException { //由retrofit 提供 ResponseBody 到 某個Type Class的轉換 final Object value = retrofit.responseBodyTConverter(innerType).convert(response.body()); netCallback.onSuccess(value); } }); } }; } }; } }
到目前爲止咱們已經實現了一個簡單的Retrofit框架,也許代碼不夠精簡,邊界處理沒有十分嚴謹,但已經初具雛形。咱們能夠繼續思考現有項目的不足,添加更多的支持。
好比在網絡請求方面目前只支持GET、POST,那麼咱們後續須要添加更多請求方式的支持。
以上提出的一些優化點,你們能夠本身先思考實現並從新閱讀寫Retrofit源碼來加深本身的理解。從整個思考流程及實現上來看Retrofit的實現並不複雜,可是從實現一個簡單可用的網絡封裝庫到實現一個拓展性強、職責分離的框架,中間的過程仍是有不少細節的,若是你看完了這篇文章,能夠再抽1個小時左右的時間從新看下Retorift框架的源碼,相信從中還會有更多的收穫。
合理利用本身每一分每一秒的時間來學習提高本身,不要再用"沒有時間「來掩飾本身思想上的懶惰!趁年輕,使勁拼,給將來的本身一個交代!