
实现的效果作者:不欠专辑的jay超酷
转载地址:https://juejin.cn/post/7104846835902054413
Retrofit通过接口和抽象方法来管理网络请求,希望在不改变这个接口中抽象方法的返回值(继续使用Observable返回值)的情况下(如果返回值改变了,代码需要大改,这并不是希望看到的结果)实现Mock的功能,并且能通过开关配置Mock的开关,在Release环境强制的关掉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();
}
以上,第一部分的工作就完成了,需求明确,实现简单;
实现第二个部分第一个部分完成了自定义直接,第二个部分的需求是这个样子的
如何才能实现上面的要求呢,这里是这样设计的(不要问我为什么这样设计,是真的想不起来是如何分析的);
自定义一个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 + " 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 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 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 MockCallAdaptermockUrl()方法implements CallAdapter { 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 call) { // 将mockUrl替换掉原来的relativeUrl call = MockUtil.mockUrl(call, mockUrl); Observable > 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); } }
拿到OKHttpCall对象中的requestFactory属性,再次反射去修改relativeUrl的值,并返回call对象。
public staticCall mockUrl(Call 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组装网络请求的理解还是有很大的帮助的。