本篇文章将采用按部就班的编码方式,从零开始实现一个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框架的源码,相信从中还会有更多的收获。
合理利用本身每一分每一秒的时间来学习提高本身,不要再用"没有时间“来掩饰本身思想上的懒惰!趁年轻,使劲拼,给将来的本身一个交代!