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 void todayGank(Class responseClazz,NetCallback callback) {
Request request = new Request.Builder().url(“http://gank.io/api/today”)
.get()
.build();
okHttpClient.newCall(request).enqueue(new WrapperOkHttpCallback<>(responseClazz,callback));
}
public static void xianduGank(int count, int page,Class responseClazz,NetCallback 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 implements Callback {
private static Gson gson = new Gson();
private Class clazz;
private NetCallback callback;
public WrapperOkHttpCallback(Class responseClazz, NetCallback 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来解析,以及网络请求的异步回调 NetCallback
我们不直接使用OkHttp提供的Callback 而是在内部简单的做了封装转换成项目自己的NetCallback,因为对项目的开发人员来说,更希望的是能够直接在Callback的success回调中直接得到响应的Json数据对应的JavaBean.
本次提交详细代码:https://github.com/Knight-ZXW/MiniRetrofit/commit/8c5443b752bd85706b4290c0b54b35a13e58c4e2
思考项目现状
上文模拟的代码只是一个简单的例子,可能会有更好的封装方式,但这并不是我们这篇文章想要讨论的重点。我们回到示例中RestService类中的代码部分,看下目前网络请求的写法
因为我们项目中已经有了OKHttp这个网络库了,有关Http具体的连接及通信的脏话累活都可以交给他来处理,对于项目开发者,事实上我们只需要配置以下Http请求部分
- 请求的url 地址
- 请求的方式 (GET、POST、PUT…)
- 请求内容
假设我们已经具备了 Java注解 以及 动态代理的相关知识,知道以下信息
- 注解可以添加在方法上
- Retention为RUNTIME的注解可以在虚拟机运行时也获取到注解上的信息
- Java的动态代理可以运行时生成原接口类型的代理实现类并hook方法的调用
每一个网络接口调用请求的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对象
有了大概思路了,我们接下来先简单的实现这样一个小例子来验证我们的想法是否可行
编码实现
3.1 简单实现一个支持GET、POST请求的Retrofit
新建一个注解类@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 类只有一个方法publicT createService(final Classservice) 它接收一个抽象类,并生成该抽象类的代理实现。
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 createService(final Class 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请求的动态代理封装,下面我们在自己的项目中验证一下
3.2 在项目中验证
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的请求方式大概如下
- @DELETE
- @GET
- @HEAD
- @PATCH
- @POST
- @PUT
- @OPTIONS
3.3 继续实现POST注解
为了加深理解,我们继续简单的实现一个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 createService(final Class 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对象了。
3.4 实现ConverterFactory 解耦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 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 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 adapter;
GsonRequestBodyConverter(Gson gson, TypeAdapter 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;
}
}
}
}
3.5 实现CallAdapter 支持方法返回类型
继续回到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对象作为方法的返回值,但是这个设想存在这样几个问题
-
要实现直接返回Http结果则方法调用是同步调用,如果在主线程做IO请求肯定是不合理的
-
如果内部IO异常了,或者JSON转换失败了方法返回的是什么呢?为null吗?
因此更合理的话,在应用我们希望的是返回一个包装的支持异步调用的类型
总结:
各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。
-
BAT大厂面试题、独家面试工具包,
-
资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
,但是这个设想存在这样几个问题
-
要实现直接返回Http结果则方法调用是同步调用,如果在主线程做IO请求肯定是不合理的
-
如果内部IO异常了,或者JSON转换失败了方法返回的是什么呢?为null吗?
因此更合理的话,在应用我们希望的是返回一个包装的支持异步调用的类型
总结:
各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。
-
BAT大厂面试题、独家面试工具包,
-
资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,
[外链图片转存中…(img-lVHSZ7In-1715181787756)]
[外链图片转存中…(img-xpsKCVrx-1715181787757)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!