带你实现一个简单的Retrofit

155 阅读14分钟

在Android应用开发中,网络请求这一块,相信大家都比较熟悉Retrofit库,基本上都有用到过,在平常使用中有没有碰到什么疑惑和不解呢?如果能熟悉Retrofit源码,那么在使用过程中就可以做到心中有数,以不变应万变,即使出现问题,也可以很快找到问题所在,不用直接Google或stackoverflow,我们就抱着学习的目的,把Rertrofit的基本流程实现并测试,并添加了简单的GsonConverterFactoryRxJavaCallAdapter

测试的接口一部分用的GitHub Api 一部分是自己写的简单的php程序(有贴代码,下载个php的集成环境,新建个名称一样的文件,把代码复制进去,丢www目录就ok了,根目录根据集成环境不同可能会不同,也可以自己用nodejs或者java写个简单接口)

贴上GitHub源码,欢迎留言讨论,原创内容,转载请保留

在代码的实现先从简单的开始一点一点完善(实现的多了后面会变的稍微复杂点);

已实现的注解:

  • @Get
  • @Query
  • @Path

暂时没有打算考虑Kotlin和挂起函数,请求只实现了同步方式,力求简单,方法名称尽量和Retrofit保持统一

源码分析

下面分析一下Retrofit的整个流程

Retrofit实例构建

使用Retrofit

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

我们看下简单的build方法

public Retrofit build() {
    //如果没有设置callFactory 则创建一个默认的OkHttpClient
    okhttp3.Call.Factory callFactory = this.callFactory;
    if (callFactory == null) {
        callFactory = new OkHttpClient();
    }
    this.callFactory = callFactory;
    //在集合最后添加一个默认的返回值适配处理工厂
    // 可以处理Call<Object> 或者 Call<? extends Object>类型的适配器
    // Call<Object> 中的Object如果没有添加其他转换器工厂的话默认是okhttp3.ResponseBody
    callAdapterFactories.add(new DefaultCallAdapterFactory());
    //在集合最开始的位置添加内置的类型转换器工厂
    //内置的处类型转换工厂可以返回处理RequestBody->RequestBody和ResponseBody->ResponseBody和Object->String的类型
    //如果不添加内置的工厂,在参数处理时候会找不到转换器抛出异常
    List<Converter.Factory> converterFactories = new ArrayList<>();
    converterFactories.add(new BuiltInConverters());
    converterFactories.addAll(this.converterFactories);
    this.converterFactories = converterFactories;
    return new Retrofit(this);
}

如果没有设置OkHttpClient就设置一个默认的,添加一个默认的适配器工厂处理Call<Object>形式的返回值,添加内置的转换器工厂。

看下CallAdapter代码

/**
 * 返回值适配器
 * 原始的返回值只能是Call<ResponseBody>这种形式(挂起函数啥的先不算,简单的来说就这样)
 * CallAdapter的作用基本可以简单的理解为把Call<ResponseBody>(Call<R>) 适配为T<R> 比如:Observable<ResponseBody>
 * @param <R> 返回值泛型参数类型 ,如果返回值是Observable<ResponseBody> 则R是ResponseBody
 * @param <T> 返回值的类型 Observable<ResponseBody> 则T是Observable
 */
public interface CallAdapter<R, T> {
    /**
     * 返回值的泛型类型
     */
    Type responseType();

    /**
     * 适配
     */
    T adapt(Call<R> call);

    /**
     * 工厂
     */
    interface Factory {
        /**
         * 如果可以处理则返回一个CallAdapter实例,如果处理不了返回null就行
         * @param returnType 返回值类型 即T<R>
         */
        CallAdapter<?, ?> get(Type returnType, Annotation[] animations, Retrofit retrofit);
    }
}

CallAdapter主要用来适配从Call<T>返回值形式到T<R>形式的适配,比如从返回值是Observable<Object>,则需要添加一个可以从Call<ResponseBody>Observable<Object>的适配器工厂来生成对应的适配器,如果返回值可以适配在工厂的get方法返回适配器实例即可,如果不可以处理则返回null即可

看下默认的适配器

class DefaultCallAdapterFactory implements CallAdapter.Factory {
    @Override
    public CallAdapter<?, ?> get(Type returnType, Annotation[] animations, Retrofit retrofit) {
        if (Utils.getRawType(returnType) != Call.class) {
            return null;
        }
        if (!(returnType instanceof ParameterizedType)){
            throw new IllegalArgumentException("返回值必须是泛型参数类型");
        }
        return new CallAdapter<Object, Call<?>>() {
            @Override
            public Type responseType() {
                return Utils.getParameterUpperBound(0,(ParameterizedType) returnType);
            }

            @Override
            public Call<?> adapt(Call<Object> call) {
                //因为返回本身就是Call<Object>这种形式所以直接返回就好了
                return call;
            }
        };
    }
}

默认的适配器工厂生成的适配器只处理返回值是Call的方法,并且必须是带泛型的,如果后期没有添加其他适配器工厂则Api接口返回值只能是Call<Xxx>

看下转换器Converter

public interface Converter<F, T> {

    /**
     * 把F转换为T
     */
    T convert(F value) throws IOException;
    /**
     * 转换器工厂
     */
    interface Factory {
        /**
         * 生产请求转换器,可以把其他类型转换为RequestBody
         */
        Converter<?, RequestBody> requestConverter(
            Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit);
        /**
         * 生产响应转换器,原始返回类型是ResponseBody,从ResponseBody转换为其他类型
         */
        Converter<ResponseBody, ?> responseConverter(Type type,
                                                     Annotation[] annotations, Retrofit retrofit);
        /**
         * 生产字符串转换器
         */
        default Converter<?, String> stringConverter(Type type, Annotation[] annotations,
                                                     Retrofit retrofit) {
            return null;
        }
    }
}

Converter很简单就是把一种类型转换成另一种类型,Converter.Factory可以生成3种适配器转换请求的和转换响应的和转换成字符串的,根据Type判断如果可以处理就返回Converter实例,如果不能处理就返回null

Api接口创建

interface GitHubApi {
    @Get("/repos/{owner}/{repo}/contributors")
    Call<ResponseBody> contributors(
        @Path("owner") String owner,
        @Path("repo") String repo
    );
}
GitHubApi github = retrofit.create(GitHubApi.class);

看下create方法

 public <T> T create(Class<T> tClass) {
     return (T) Proxy.newProxyInstance(tClass.getClassLoader(), new Class<?>[]{tClass}, new InvocationHandler() {
         @Override
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
             return loadService(method).invoke(method, args);
         }
     });
 }

直接就是一个JDK动态代理返回接口的代理对象,这个应该都看得懂

请求

方法调用

Call<ResponseBody> call = github.contributors("square", "retrofit");

调用接口方法,会执行动态代理的invoke方法,解析方法注解并构建ServiceMethod,构建的ServiceMethod实例会加入缓存,如果下次是同一个Method实例则可以从缓存中读取,并且读取缓存的过程是线程安全的,代码类型DCL形式应该都看得懂。

new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return loadService(method).invoke(method, args);
    }
}
private ServiceMethod<?> loadService(Method method) {
    //如果缓存有,则从缓存中读取
    ServiceMethod<?> service = serviceCache.get(method);
    if (service != null) {
        return service;
    }
    synchronized (serviceCache) {
        //缓存中没有则解析方法生成ServiceMethod加入缓存并返回
        service = serviceCache.get(method);
        if (service == null) {
            service = ServiceMethod.parseAnnotations(this, method);
            serviceCache.put(method, service);
        }
    }
    return service;
}
abstract class ServiceMethod<T> {
    /**
     * 执行方法调用
     */
    abstract T invoke(Method method, Object[] args);

    /**
     * 解析注解
     */
    static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
        //解析注解生成RequestFactory
        //RequestFactory 包含已经解析好的url、请求方式、各注解的参数处理器(ParameterHandler)...
        RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
        //这个方法调用其实是解析获取CallAdapter和ResponseBody的Converter
        //如果不考虑kotlin的挂起函数的话,返回的是HttpServiceMethod和子类CallAdapted
        return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
    }
}

ServiceMethod类有2个方法,invoke是在Api方法调用时候执行,parseAnnotations方法是在调用时候先解用来解析注解,主要是把方法的注解,参数注解,需要的CallAdapter等解析获取到,因为有缓存所以再次使用就不用重新解析。

ServiceMethod的解析主要分2步:

  • 方法注解解析和方法参数注解,这一步主要是在RequestFactory中进行的,这些解析的到的东西也是在RequestFactory中持有:
    • 方法注解:确定Url相对地址,请求方法是否可以包含请求体,
    • 参数注解解析:拿到所有参数的ParameterHandler
  • 返回值类型解析:拿到需要的CallAdapter,和需要返回值泛型类型需要的转换器,这一步在ServiceMethod的子类HttpServiceMethod中进行,如果不考虑kotlin和挂起函数我们实际拿到的ServiceMethod是其子类CallAdapted,CallAdapted继承的HttpServiceMethod
方法注解解析和方法参数注解

RequestFactory持有解析拿到的请求地址和参数处理器等,在实际请求时候生成Request,实际的解析也是在RequestFactory中完成的

RequestBuidler是对okhttp3.Request.Builder的封装,只要是在迭代参数处理器时候将参数处理器所拿到的参数应用到okhttp3.Request.Builder

static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
        return new RequestFactory.Builder(retrofit, method).build();
}
RequestFactory build() {
    //解析方法注解
    //@Get 会拿到请求方法 相对的请求地址
    for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
    }
    //解析方法参数,返回相应的参数处理器实例
    int parameterCount = paramsAnnotationsArray.length;
    parameterHandlers = new ParameterHandler[parameterCount];
    for (int index = 0, last = parameterCount - 1; index < parameterCount; index++) {
        parameterHandlers[index] = parseParameter(index, parameterTypes[index], paramsAnnotationsArray[index], index == last);
    }

    return new RequestFactory(this);
}

从截取的部分关键代码可以看出来RequestFactory主要是做2个事情:

  • 解析方法注解,拿到请求方式比如GET,请求地址,等,如果实现了POST等注解还会确定Content-Type
  • 解析参数注解拿到每个参数的参数处理器ParameterHandler

下面是解析方法注解:

 void parseMethodAnnotation(Annotation annotation) {
     if (annotation instanceof Get) {
         //@Get的请求方法就是GET,相当地址是注解的value方法返回值
         parseHttpMethodAndPath("GET", ((Get) annotation).value());
     }
 }
void parseHttpMethodAndPath(String httpMethod, String value) {
    this.httpMethod = httpMethod;
    this.relativeUrl = value;
}

目前只是一个简单的实现所以只看下Get注解,这一步可以确定请求方式是GET,可以拿到Get注解中的相对地址,这要是看不懂就没有办法了。

下面是解析参数注解:

ParameterHandler<?> parseParameter(int index, Type type, Annotation[] annotationArray, boolean allowContinuation) {
    ParameterHandler<?> result = null;
    //方法参数上可能有多个注解,Retrofit只解析自己的
    for (Annotation annotation : annotationArray) {
        ParameterHandler<?> annotationAction = parseParameterAnnotation(index, type, annotationArray, annotation);
        //如果返回null说明不是Retrofit注解,如果找到了则直接返回,没有一个参数有多个Retrofit注解的情况
        if (annotationAction == null) continue;
        result = annotationAction;
    }
    return result;
}
ParameterHandler<?> parseParameterAnnotation(int index, Type type, Annotation[] annotations, Annotation annotation) {
    if (annotation instanceof Query) {
        gotQuery = true;
        //如果是Query注解,则需要拿到参数名 和对应的参数值转换器
        Query query = (Query) annotation;
        String name = query.value();
        boolean encoded = query.encoded();
        //query String 是 ?name=value&name1=value1 所以需要StringConverter
        Converter<?, String> stringConverter = retrofit.stringConverter(type, annotations);
        return new ParameterHandler.Query<>(stringConverter, name, encoded);
    } else if (annotation instanceof Path) {
        if (gotQuery) {
            throw new IllegalArgumentException("Path注解不能在Query注解后面");
        }
        gotPath = true;
        //如果是Path注解,则需要拿到参数名 和对应的参数值转换器
        Path path = (Path) annotation;
        //资源路径是url中所以也是需要StringConverter
        Converter<?, String> stringConverter = retrofit.stringConverter(type, annotations);
        String name = path.value();
        return new ParameterHandler.Path<>(name, stringConverter);
    }
    //到这里说明这个注解不是本框架的参数注解直接返回null就好了
    return null;
}

解析方法参数注解主要是拿到参数处理器,基本每个参数注解,这里有一些需要注意的,因为我只实现了简单的几个注解,所以这里就用Retrofit来说了

  • 不能注解多个Url
  • UrlPath同时使用
  • Query啥的不能在Url注解前面
  • Url注解就不要在方法注解上写相对地址了
  • Path注解不能出现在Query注解后面
  • Field参数注解方法注解必须有FormUrlEncoded
  • Part参数注解方法注解必须有Multipart
  • Part参数注解,value为空的情况下,参数类型必须是Part或者Iterable<Part>的实现类型,或者Part[]
  • Part参数注解,value不为空的情况下,参数类型可以是requestBodyConverter可以处理的其他类型或集合和数组,但不能是Part类型,不推荐在这里写File添加一个file到RequestBody的转换器转换,Header缺少filename有些语言会检测不到上传文件
  • Body参数注解方法注解不能有FormUrlEncodedMultipart,并且只能有一个Body参数注解
  • ...
返回值类型解析

接下来看HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);方法

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
    Retrofit retrofit, Method method, RequestFactory requestFactory) {
    //获取返回值类型Type
    Type returnType = method.getGenericReturnType();
    Annotation[] annotations = method.getAnnotations();
    //获取CallAdapter
    CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, returnType, annotations);
    //获取ResponseBodyConverter
    Type responseType = callAdapter.responseType();
    Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType);
    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    //返回子类实例
    return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
}
private static <ResponseT> Converter<ResponseBody, ResponseT> createResponseConverter(
    Retrofit retrofit, Method method, Type responseType) {
    return retrofit.responseBodyConverter(responseType,method.getAnnotations());
}
private static <ReturnT, ResponseT> CallAdapter<ResponseT, ReturnT> createCallAdapter(
    Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
    return (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType,annotations);
}

主要是解析返回值拿到CallAdapterresponseConverter 并返回其子类实现CallAdapted,需要注意的是如何拿到ConverterCallAdapter,我们看下responseBodyConverter方法

<T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
    return nextResponseBodyConverter(type, annotations);
}

@SuppressWarnings("unchecked")
private <T> Converter<ResponseBody, T> nextResponseBodyConverter(Type type, Annotation[] annotations) {
    //获取ResponseBody->T的转换器,如果找到直接返回,没找到抛异常
    for (int i = 0; i < converterFactories.size(); i++) {
        Converter<ResponseBody, ?> converter = converterFactories.get(i)
            .responseConverter(type, annotations, this);
        //只要工厂get方法返回不是null就当他返回了可以处理的Converter,直接返回,后面的就没有机会了
        if (converter != null) {
            return (Converter<ResponseBody, T>) converter;
        }
    }
    throw new IllegalArgumentException("没有找到可以处理的ResponseBodyConverter");
}

循环之前自定义添加的Converter.Factory和内置的集合循环调用responseConverter方法,如果返回不为null则认为可以处理,直接返回,也就是说和添加顺序是有关系的,如果添加了2个都可以处理同一种类型转换器,那么后面的就没有机会处理了,requestBodyConvertercallAdapter 方法也是同样的流程

到这里为止,方法的解析就完了,主要是拿到ServiceMethod,包含下面的:

  • RequestFactory 各个参数注解的处理器,请求方法,url等
  • CallAdapter 返回值适配器
  • responseConverter 返回值类型转换器

接下来就是调用拿到的ServiceMethod执行invoke方法;这个方法在其子类HttpServiceMethod实现,其子类CallAdapted并没有重写,invoke方法就比较简单了,创建Call的实现类OkHttpCall实例,并调用adapt方法,adapt直接调用了callAdapter.adapt

@Override
ReturnT invoke(Method method, Object[] args) {
    //创建Call的子类实现,目前只有OkHttpCall
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, responseConverter, args, callFactory);
    //适配返回值类型
    return adapt(call, args);
}
abstract ReturnT adapt(Call<ResponseT> call,Object[] args);
@Override
ReturnT adapt(Call<ResponseT> call, Object[] args) {
    //适配返回值类型
    return callAdapter.adapt(call);
}

真实请求

这里根据返回值的不同实现也不同,Rxjava的返回值是在真实订阅时候才会真实请求,也是调用的Call.execute,默认的适配器是返回Call的实例,在我们自己调用execute时候真实请求,但是不过不过什么实现最后的实际请求都是调用的Callexecute方法,再上面的方法调用时候我们也分析了,这里实际调用的是其子类OkHttpCallexecute

触发实际请求:

Response<ResponseBody> response = call.execute();

这个Response是对okhttp3.Response和返回值类型泛型的一个封装

@Override
public Response<T> execute() throws IOException {
    return parseResponse(getRawCall().execute());
}

/**
    * okhttp3.Response -> Response<T>
    */
private Response<T> parseResponse(okhttp3.Response response) throws IOException {
    ResponseBody body = response.body();
    //转换ResponseBody -> T
    T t = responseConverter.convert(body);
    return Response.success(t, response);
}

private okhttp3.Call getRawCall() throws IOException {
    okhttp3.Call call = rawCall;
    if (call != null) return call;
    return rawCall = createRawCall();
}

private okhttp3.Call createRawCall() throws IOException {
    return callFactory.newCall(requestFactory.create(args));
}

实际上简单的代码就是OkHttpClient.newCall(Request),RequestRequestFactorycreate方法创建的,请求回来的ResponseBody会用之前解析拿到的responseConverter转换,并封装成Response<T>

看下create方法:

public Request create(Object[] args) throws IOException {
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl);
    int argumentCount = args.length;
    //循环应用参数处理器
    for (int p = 0; p < argumentCount; p++) {
        handlers[p].apply(requestBuilder, args[p]);
    }
    //构建Request
    return requestBuilder.get().build();
}

这里的create方法很简单,拿到一个RequestBuilder实例,传入请求方法请求地址,循环应用之前解析拿到的参数处理器,最后构建一个RequestRequestBuilder是对okhttp3.Request.Builder的封装,实际还是会应用到okhttp3.Request.Builder上最后调用build拿到Request

我们看下其中一个参数处理器怎么处理的:

static class Query<T> extends ParameterHandler<T> {

    Converter<T,String> stringConverter;
    String name;
    boolean encoded;

    Query(Converter<T, String> stringConverter, String name, boolean encoded) {
        this.stringConverter = stringConverter;
        this.name = name;
        this.encoded = encoded;
    }

    @Override
    void apply(RequestBuilder requestBuilder, T value) throws IOException {
        String queryValue = stringConverter.convert(value);
        requestBuilder.addQueryParam(name,queryValue,encoded);
    }
}

就是直接调用封装的RequestBuilder实例添加一个查询参数,其他处理器也是大同小异,接下来我么看下RequestBuilder

class RequestBuilder {
    private final String method;
    private final String baseUrl;
    private String relativeUrl;
    private HttpUrl.Builder urlBuilder;

    private final Request.Builder requestBuilder;

    RequestBuilder(String method, String baseUrl, String relativeUrl) {
        this.method = method;
        this.baseUrl = baseUrl;
        this.relativeUrl = relativeUrl;
        this.requestBuilder = new Request.Builder();
    }

    /**
     * 添加query参数 (?name=value&name1=value1)
     */
    void addQueryParam(String name, String value, boolean encoded) {
        if (relativeUrl != null) {
            urlBuilder = HttpUrl.parse(baseUrl).newBuilder(relativeUrl);
            relativeUrl = null;
        }
        if (encoded) {
            urlBuilder.addEncodedQueryParameter(name, value);
        } else {
            urlBuilder.addQueryParameter(name, value);
        }
    }

    /**
     * 替换资源路径
     */
    void addPathParam(String name, String value) {
        relativeUrl = relativeUrl.replace("{" + name + "}",value);
    }

    Request.Builder get() {
        HttpUrl url;
        HttpUrl.Builder urlBuilder = this.urlBuilder;
        if (urlBuilder != null) {
            url = urlBuilder.build();
        } else {
            url = HttpUrl.parse(baseUrl).resolve(relativeUrl);
        }
        return requestBuilder
            .url(url)
            .method(method, null);
    }
}s

实际上就是对okhttp3.Request.Builder的封装,没什么好说的.

ResponseBody body = response.body();
String string = body.string();
System.out.println(string);

因为我们没有添加其他转换器工厂,所以默认只能拿到ResponseBody,至此所有流程结束。是否觉得很简单呢?