Android技术知识

Retrofit自定义CallAdapter完成动态mock数据

2022-06-06  本文已影响0人  搬砖小老弟

作者:不欠专辑的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();
}

以上,第一部分的工作就完成了,需求明确,实现简单;

实现第二个部分

第一个部分完成了自定义直接,第二个部分的需求是这个样子的

  1. 需要配置开关,在release环境关闭mock功能,避免忘记删除注解导致生产事故;
  2. 能够拿到方法上的注解信息
  3. 要保留之前的返回值类型,这样可以减少代码的改动量;
  4. 需要拦截到真实的网络请求,把mockUrl塞进去,替换掉原来的值。

如何才能实现上面的要求呢,这里是这样设计的(不要问我为什么这样设计,是真的想不起来是如何分析的);

按照上面的分析思路,一步步的实现代码

配置开关

自定义一个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组装网络请求的理解还是有很大的帮助的。

上一篇下一篇

猜你喜欢

热点阅读