Retrofit自定义CallAdapter完成动态mock数据
作者:不欠专辑的jay超酷
转载地址:https://juejin.cn/post/7104846835902054413
实现的效果
Retrofit通过接口和抽象方法来管理网络请求,希望在不改变这个接口中抽象方法的返回值(继续使用Observable返回值)的情况下(如果返回值改变了,代码需要大改,这并不是希望看到的结果)实现Mock的功能,并且能通过开关配置Mock的开关,在Release环境强制的关掉mock功能。
分析思路
主要的实现思路可以划分为两部分,
- 第一部分:标识方法,并携带一定的参数信息;(自定义注解)
- 第二部分:拦截到网络请求发起的地方,把Mock的信息塞进去。
实现第一部分
实现对方法的标识,并携带Mock网络请求的url,优秀的程序员马上想到了自定义注解,通过注解的方式很容易的就实现了这个需求;
下面是自定义注解的代码,因为我这里设计的mock和真实的网络请求的差别仅在相对的url上,就像下面这样
Mock网络请求的url : mock/user/info
真实网络请求的url : user/info
所以只需要携带mock后的url参数即可完成后面参数值的替换;
// 设置RUNTIME级别配合后面的反射,并要求只能在Method上使用该注解
@Retention(RetentionPolicy.RUNTIME)
public @Target({ElementType.METHOD})
@interface Mock {
String mockUrl();
}
以上,第一部分的工作就完成了,需求明确,实现简单;
实现第二个部分
第一个部分完成了自定义直接,第二个部分的需求是这个样子的
- 需要配置开关,在release环境关闭mock功能,避免忘记删除注解导致生产事故;
- 能够拿到方法上的注解信息
- 要保留之前的返回值类型,这样可以减少代码的改动量;
- 需要拦截到真实的网络请求,把mockUrl塞进去,替换掉原来的值。
如何才能实现上面的要求呢,这里是这样设计的(不要问我为什么这样设计,是真的想不起来是如何分析的);
-
自定义一个CallAdapter.Factory的实现类,这个实现类配置了mock开关和生产模式开关,只有在非生产模式并打开了mock开关的情况下才工作,完成第1点;
-
CallAdapter.Factory的get()函数中,将方法上的注解通过数组传递了过来;完成第2点;
-
自定义CallAdapter中持有一个RxJava2CallAdapterFactory在非mock的情况下,保证返回值不修改的情况下发起真实的网络请求,在mock的情况下,将mockurl保存到自定义CallAdapter里面并且也模仿RxJavaCallAdapter的写法实现一次,完成第3点;
-
在CallAdapter里面有一个adapt()方法,会传入一个Call类型的OKHttpCall对象,这个对象是负责组装网络请求的对象,里面有一个requestFactory对象,这个对象存储了网络请求的一些参数,通过反射的方式把mockUrl替换掉requestFactory对象的relativeUrl即可完成mock,完成第4点。
按照上面的分析思路,一步步的实现代码
配置开关
自定义一个CallAdapter.Factory,并配置好开关配置 isRelease开关标识当前的开发环境是否为生产,mockEnable开关标识当前是否打开mock开关
public class MockRxJava2CallAdapterFactory extends CallAdapter.Factory {
private boolean mockEnable;
private boolean isRelease;
private RxJava2CallAdapterFactory rxJava2CallAdapterFactory;
private MockRxJava2CallAdapterFactory(boolean mockEnable, boolean isRelease) {
this.mockEnable = mockEnable;
this.isRelease = isRelease;
rxJava2CallAdapterFactory = RxJava2CallAdapterFactory.create();
}
public static MockRxJava2CallAdapterFactory create(boolean mockEnable, boolean isRelease) {
return new MockRxJava2CallAdapterFactory(mockEnable, isRelease);
}
}
重写get()方法,拿到Mock注解中的mockUrl
通过annotations数组,拿到Mock注解,并拿到mockUrl字符串返回
@Nullable
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
String mockUrl = MockUtil.getMockUrl(annotations);
return null;
}
public static String getMockUrl(Annotation[] annotations) {
for (Annotation ann : annotations) {
if (ann.annotationType() == Mock.class) {
return ((Mock) ann).mockUrl();
}
}
return null;
}
实现返回CallAdapter的逻辑
当mock开关打开,并且是非生产环境,且方法上的mockUrl有值的情况下,返回自定义的CallAdapter,否则交给RxJava2CallAdapterFactory处理。
@Nullable
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
String mockUrl = MockUtil.getMockUrl(annotations);
if (mockEnable && !isRelease && mockUrl != null) {
return getCallAdapter(returnType, mockUrl);
} else {
return rxJava2CallAdapterFactory.get(returnType, annotations, retrofit);
}
}
实现getCallAdapter()方法
这里是模仿RxJava2CallAdapter的写法,做了一些微调,主要是返回的自定义CallAdapter类
public MockCallAdapter getCallAdapter(Type returnType, String mockUrl) {
Class<?> rawType = MockUtil.getRawType(returnType);
if (rawType == Completable.class) {
// Completable is not parameterized (which is what the rest of this method deals with) so it
// can only be created with a single configuration.
return new MockCallAdapter(Void.class, false, true, false, false,
false, true, mockUrl);
}
boolean isFlowable = rawType == Flowable.class;
boolean isSingle = rawType == Single.class;
boolean isMaybe = rawType == Maybe.class;
if (rawType != Observable.class && !isFlowable && !isSingle && !isMaybe) {
return null;
}
boolean isResult = false;
boolean isBody = false;
Type responseType;
if (!(returnType instanceof ParameterizedType)) {
String name = isFlowable ? "Flowable"
: isSingle ? "Single"
: isMaybe ? "Maybe" : "Observable";
throw new IllegalStateException(name + " return type must be parameterized"
+ " as " + name + "<Foo> or " + name + "<? extends Foo>");
}
Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
Class<?> rawObservableType = getRawType(observableType);
if (rawObservableType == Response.class) {
if (!(observableType instanceof ParameterizedType)) {
throw new IllegalStateException("Response must be parameterized"
+ " as Response<Foo> or Response<? extends Foo>");
}
responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
} else if (rawObservableType == Result.class) {
if (!(observableType instanceof ParameterizedType)) {
throw new IllegalStateException("Result must be parameterized"
+ " as Result<Foo> or Result<? extends Foo>");
}
responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
isResult = true;
} else {
responseType = observableType;
isBody = true;
}
return new MockCallAdapter(responseType, isResult, isBody, isFlowable,
isSingle, isMaybe, false, mockUrl);
}
自定义CallAdapter
自定义CallAdapter也同样的模仿RxJava2CallAdapter类(直接CV,然后微调)
主要是把拿到的mockUrl替换掉原来的网络请求地址,所以下面里面的关键是MockUtil.mockUrl()方法的实现;
当然模仿RxJava2CallAdapter的时候会出现找不到类的情况,原因是因为有几个类是没有访问权限的,还得模仿一次,主要有:BodyObservable、CallExecuteObservable、ResultObservable;(模仿的代码就不贴了)
// 模仿的RxJava2CallAdapter类
public final class MockCallAdapter<R> implements CallAdapter<R, Object> {
private final Type responseType;
private final boolean isResult;
private final boolean isBody;
private final boolean isFlowable;
private final boolean isSingle;
private final boolean isMaybe;
private final boolean isCompletable;
private final String mockUrl;
MockCallAdapter(Type responseType, boolean isResult, boolean isBody, boolean isFlowable,
boolean isSingle, boolean isMaybe, boolean isCompletable, String mockUrl) {
this.responseType = responseType;
this.isResult = isResult;
this.isBody = isBody;
this.isFlowable = isFlowable;
this.isSingle = isSingle;
this.isMaybe = isMaybe;
this.isCompletable = isCompletable;
this.mockUrl = mockUrl;
}
@NonNull
@Override
public Type responseType() {
return responseType;
}
@NonNull
@Override
public Object adapt(@NonNull Call<R> call) {
// 将mockUrl替换掉原来的relativeUrl
call = MockUtil.mockUrl(call, mockUrl);
Observable<Response<R>> responseObservable = new CallExecuteObservable<>(call);
Observable<?> observable;
if (isResult) {
observable = new ResultObservable<>(responseObservable);
} else if (isBody) {
observable = new BodyObservable<>(responseObservable);
} else {
observable = responseObservable;
}
if (isFlowable) {
return observable.toFlowable(BackpressureStrategy.LATEST);
}
if (isSingle) {
return observable.singleOrError();
}
if (isMaybe) {
return observable.singleElement();
}
if (isCompletable) {
return observable.ignoreElements();
}
return RxJavaPlugins.onAssembly(observable);
}
}
mockUrl()方法
拿到OKHttpCall对象中的requestFactory属性,再次反射去修改relativeUrl的值,并返回call对象。
public static <R> Call<R> mockUrl(Call<R> call, String mockUrl) {
Class<? extends Call> clz = call.getClass();
try {
Field requestFactory = clz.getDeclaredField("requestFactory");
requestFactory.setAccessible(true);
Object o1 = requestFactory.get(call);
Class<?> factoryClz = o1.getClass();
Field relativeUrl = factoryClz.getDeclaredField("relativeUrl");
relativeUrl.setAccessible(true);
relativeUrl.set(o1, mockUrl);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
return call;
}
至此,完整的功能就实现了。
总结
其实这一块的内容,从想要实现什么样的效果,如何去保证不修改返回值,如何去找到反射的切入点都花费了我一天多的时间,但是奈何文化有效,不知道怎么把自己查看源码、分析源码的过程描述出来,所以写这篇文章的时候直接把最终实现的思路分析了一下,大致的实现贴了一下,当然这个代码的局限性还是比较大的,也还有值得优化的点留给我慢慢的思考,不过实现这个功能,对于注解、反射、Retrofit组装网络请求的理解还是有很大的帮助的。