Retrofit源码解析(二):源码解析

前言

之前我们已经复习了Retrofit的基础用法,Retrofit的源码理解并不复杂,他实现的主要功能就是把接口文件通过注解转化成Okhttp请求,所以我们弄懂了主线,整个Retrofit我们就明白了。

正文

首先复习一下Retrofit的用法:

val retrofit = Retrofit.Builder()
                // 必填项
                .baseUrl("http://www.baidu.com")
                .client(OkHttpClient())
                // 对得到的结果进行转换,常用的有加密解密,json转换等等
                .addConverterFactory(StringConvertFactory())
                // 对返回的结果进行封装,常用的有之间转化成Rxjava对象
                // 这里我们简单的进行包装
                .addCallAdapterFactory(ResponseWrapperCallAdapterFactory())
                .build()

api = retrofit.create(TestApi::class.java)

Retrofit的通过构建者模式创建,这里的重点是:

api = retrofit.create(TestApi::class.java)

我们看看这个create方法是怎么解析接口文件的:

public <T> T create(final Class<T> service) {
    // 检查创建的网络请求的服务接口是否可用
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    // 当调用定义的接口方法的时候,通过动态代理调用自己的逻辑,发起网络请求
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            // 如果参数的类型是Object,直接反射调用
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            // 如果是default方法,根据平台类型直接调用
            // android平台固定返回false
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            // 通过Method,封装成okhttp请求
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            // 发起网络请求
            return serviceMethod.adapt(okHttpCall);
          }
        });
  }

通过create方法,创建了指定接口文件的动态代理,每次我们通过动态代理访问对应的方法的时候,都会进入到invoke方法,通过invoke方法,我们可以获得这个方法的注解信息,然后转换成okhttp请求。

首先检查了接口文件是否合法,判断是否是interface接口并且方法书大于0。这里我们还看到了构建Retrofit时设置的validateEagerly属性,如果是true,会在创建接口代理时,解析所有的方法,所以我们一般不会开启这个设置。eagerlyValidateMethods方法内部调用的是loadServiceMethod方法,loadServiceMethod方法解析调用方法的注解信息,封装成ServiceMethod对象。

ServiceMethod<?, ?> loadServiceMethod(Method method) {
    // 如果缓存中已经有了这个方法,直接返回
    ServiceMethod<?, ?> result = serviceMethodCache.get(method);
    if (result != null) return result;
    // 同步锁
    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        // 把这个方法封装成ServiceMethod对象,保存起来
        result = new ServiceMethod.Builder<>(this, method).build();
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

首先检查缓存,如果缓存了这个方法的请求信息,直接返回,否则会重新解析一次,解析的步骤被封装到ServiceMethod.Builder中,所以我们看一下ServiceMethod.Builder的build方法。

public ServiceMethod build() {
      // 创建callAdapter
      callAdapter = createCallAdapter();
      // 得到返回值的类型
      responseType = callAdapter.responseType();
      if (responseType == Response.class || responseType == okhttp3.Response.class) {
        throw methodError("'"
            + Utils.getRawType(responseType).getName()
            + "' is not a valid response body type. Did you mean ResponseBody?");
      }
      // 创建responseConverter
      responseConverter = createResponseConverter();

      // 解析注解
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }
      // 检查是否设置了请求方式
      if (httpMethod == null) {
        throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
      }

      // 如果没有添加body,要检查请求的方式是否支持无body请求
      if (!hasBody) {
        if (isMultipart) {
          throw methodError(
              "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
        }
        if (isFormEncoded) {
          throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
              + "request body (e.g., @POST).");
        }
      }

      // 遍历参数的注解
      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        Type parameterType = parameterTypes[p];
        // 对注解的参数类型进行检查
        if (Utils.hasUnresolvableType(parameterType)) {
          throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
              parameterType);
        }

        // 每一个参数都要有注解
        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
        if (parameterAnnotations == null) {
          throw parameterError(p, "No Retrofit annotation found.");
        }
        // 解析参数的注解
        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }

      if (relativeUrl == null && !gotUrl) {
        throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
      }
      if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
        throw methodError("Non-body HTTP method cannot contain @Body.");
      }
      if (isFormEncoded && !gotField) {
        throw methodError("Form-encoded method must contain at least one @Field.");
      }
      if (isMultipart && !gotPart) {
        throw methodError("Multipart method must contain at least one @Part.");
      }

      return new ServiceMethod<>(this);
    }

在build方法中,开始了对注解的解析,这个还做了很多的判断,例如是否设置了url的检查,某些注解使用冲突的问题等等。这个方法中主要看parseMethodAnnotation方法和parseParameter方法。

parseMethodAnnotation方法用来解析方法的注解:

private void parseMethodAnnotation(Annotation annotation) {
      if (annotation instanceof DELETE) {
        parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
      } else if (annotation instanceof GET) {
        parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
      } else if (annotation instanceof HEAD) {
        parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
        if (!Void.class.equals(responseType)) {
          throw methodError("HEAD method must use Void as response type.");
        }
      } else if (annotation instanceof PATCH) {
        parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
      } else if (annotation instanceof POST) {
        parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
      } else if (annotation instanceof PUT) {
        parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
      } else if (annotation instanceof OPTIONS) {
        parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
      } else if (annotation instanceof HTTP) {
        HTTP http = (HTTP) annotation;
        parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
      } else if (annotation instanceof retrofit2.http.Headers) {
        String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
        if (headersToParse.length == 0) {
          throw methodError("@Headers annotation is empty.");
        }
        headers = parseHeaders(headersToParse);
      } else if (annotation instanceof Multipart) {
        if (isFormEncoded) {
          throw methodError("Only one encoding annotation is allowed.");
        }
        isMultipart = true;
      } else if (annotation instanceof FormUrlEncoded) {
        if (isMultipart) {
          throw methodError("Only one encoding annotation is allowed.");
        }
        isFormEncoded = true;
      }
    }

这个分别解析了方法的注解,我们看到了熟悉@GET、@POST等注解,具体的实现在parseHttpMethodAndPath中:

/**
     * 解析方法中注解,保存对应的value
     * */
    private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
      if (this.httpMethod != null) {
        throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
            this.httpMethod, httpMethod);
      }
      this.httpMethod = httpMethod;
      this.hasBody = hasBody;

      if (value.isEmpty()) {
        return;
      }

      // Get the relative URL path and existing query string, if present.
      // 判断url链接之后的参数是否合法
      int question = value.indexOf('?');
      if (question != -1 && question < value.length() - 1) {
        // Ensure the query string does not have any named parameters.
        String queryParams = value.substring(question + 1);
        Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
        if (queryParamMatcher.find()) {
          throw methodError("URL query string \"%s\" must not have replace block. "
              + "For dynamic query parameters use @Query.", queryParams);
        }
      }

      // 保存相对地址
      this.relativeUrl = value;
      // 从地址中分离出设置的参数
      this.relativeUrlParamNames = parsePathParameters(value);
    }

parseHttpMethodAndPath先获取注解中网络请求的相对路径,如果设置了相对地址,最后会和baseUrl拼接成完成的请求地址,另外还检查了在相对地址中,是不允许设置参数的,考虑到有些对url链接的地址组成不了解朋友,我们举一个简单的例子:

地址:http://www.baidu.com

如果想要在网址中添加参数,需要在末尾加上?,然后通过 key=value&key=value的方式拼接

拼接参数后:http://www.baidu.com/?wd=111

具体的url了解的格式大家可以自己查看相关资料。看完了解析方法上的直接,接着看解析方法中参数的注解,因为代码实在是太多了,我们就挑其中一个熟悉的注解作为理解的例子:

else if (annotation instanceof Field) {
        if (!isFormEncoded) {
          throw parameterError(p, "@Field parameters can only be used with form encoding.");
        }
        Field field = (Field) annotation;
        String name = field.value();
        boolean encoded = field.encoded();

        gotField = true;

        Class<?> rawParameterType = Utils.getRawType(type);
        // 如果是集合,需要指定泛型是String
        if (Iterable.class.isAssignableFrom(rawParameterType)) {
          if (!(type instanceof ParameterizedType)) {
            throw parameterError(p, rawParameterType.getSimpleName()
                + " must include generic type (e.g., "
                + rawParameterType.getSimpleName()
                + "<String>)");
          }
          ParameterizedType parameterizedType = (ParameterizedType) type;
          Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
          Converter<?, String> converter =
              retrofit.stringConverter(iterableType, annotations);
          return new ParameterHandler.Field<>(name, converter, encoded).iterable();
        } 
        // 数组
        else if (rawParameterType.isArray()) {
          Class<?> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
          Converter<?, String> converter =
              retrofit.stringConverter(arrayComponentType, annotations);
          return new ParameterHandler.Field<>(name, converter, encoded).array();
        } 
        // 其他
        else {
          Converter<?, String> converter =
              retrofit.stringConverter(type, annotations);
          return new ParameterHandler.Field<>(name, converter, encoded);
        }

      } 

我们看到@Field注解必须和@FormUrlEncoded一起使用,把对应的注解的类型的key和设置的参数值加入到表单中去。

现在方法的注解和参数的注解都已经得到了,Retrofit把ServiceMethod和指定的参数封装成OKhttpCall:

OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);

OkhttpCall是ServiceMethod和okhttp请求转换的处理类,他主要是负责成request和处理response,这里贴出几个重要的方法:

生成okhttp的网络请求:

/** 
 * OKhttpCall.toCall实际上调用的ServiceMethod.toCall方法
 * 生成okhttp网络请求
 */
  okhttp3.Call toCall(@Nullable Object... args) throws IOException {
    // 封装网络请求的信息
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);

    //
    @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
    // 判断参数的数量是否和方法的数量相等
    int argumentCount = args != null ? args.length : 0;
    if (argumentCount != handlers.length) {
      throw new IllegalArgumentException("Argument count (" + argumentCount
          + ") doesn't match expected count (" + handlers.length + ")");
    }

    // 把参数添加到requestBuilder中
    for (int p = 0; p < argumentCount; p++) {
      handlers[p].apply(requestBuilder, args[p]);
    }
    // 调用okhttpClient创建Call对象
    return callFactory.newCall(requestBuilder.build());
  }

获取okhttp的request信息:

@Override public synchronized Request request() {
    // 如果已经有okhttp的call请求,返回call的request
    okhttp3.Call call = rawCall;
    if (call != null) {
      return call.request();
    }
    // 判断是否创建请求失败过
    if (creationFailure != null) {
      if (creationFailure instanceof IOException) {
        throw new RuntimeException("Unable to create request.", creationFailure);
      } else if (creationFailure instanceof RuntimeException) {
        throw (RuntimeException) creationFailure;
      } else {
        throw (Error) creationFailure;
      }
    }
    try {
      // 创建okhttp请求
      return (rawCall = createRawCall()).request();
    } catch (RuntimeException | Error e) {
      throwIfFatal(e); // Do not assign a fatal error to creationFailure.
      creationFailure = e;
      throw e;
    } catch (IOException e) {
      creationFailure = e;
      throw new RuntimeException("Unable to create request.", e);
    }
  }

解析okhttp的请求结果:

@Override public Response<T> execute() throws IOException {
    okhttp3.Call call;

    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      if (creationFailure != null) {
        if (creationFailure instanceof IOException) {
          throw (IOException) creationFailure;
        } else if (creationFailure instanceof RuntimeException) {
          throw (RuntimeException) creationFailure;
        } else {
          throw (Error) creationFailure;
        }
      }

      call = rawCall;
      if (call == null) {
        try {
          call = rawCall = createRawCall();
        } catch (IOException | RuntimeException | Error e) {
          throwIfFatal(e); //  Do not assign a fatal error to creationFailure.
          creationFailure = e;
          throw e;
        }
      }
    }

    if (canceled) {
      call.cancel();
    }

    return parseResponse(call.execute());
  }

其中在解析网络请求结果的过程中,调用了我们设置的ConvertFactory,对得到的响应结果进行转换:

/** Builds a method return value from an HTTP response body. */
  /**
   * 对得到的网络请求结果进行转换
   * */
  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }

到此为止Retrofit的发起网络请求的整个流程就结束了。

哎?是不是忘了点什么?没错那就是CallAdapter,其实CallAdapter只是对网络请求结果的包装,也就对ConvertFactory的转换结果的封装,回顾一下我们自定义的CallAdapterFactory:

/**
 * Created by li.zhipeng on 2018/8/29.
 *
 *  把得到网络请求结果String,转换成ResponseWrapper
 */
public class ResponseWrapperCallAdapterFactory extends CallAdapter.Factory  {

    @Override
    public CallAdapter<String, ResponseWrapper> get(@NonNull final Type returnType, @NonNull Annotation[] annotations, @NonNull Retrofit retrofit) {
        return new CallAdapter<String, ResponseWrapper>() {

            @Override
            public Type responseType() {
                return returnType;
            }

            @Override
            public ResponseWrapper adapt(@NonNull Call<String> call) {
                try {
                    return new ResponseWrapper(call.execute().body());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return new ResponseWrapper("error");
            }
        };
    }
}

这个类的重点就是:

@Override
            public ResponseWrapper adapt(@NonNull Call<String> call) {
                try {
                    return new ResponseWrapper(call.execute().body());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return new ResponseWrapper("error");
            }

我们在CallAdapter中开启了网络请求,并且把得到的结果封装成我们需要的对象。在动态代理中

serviceMethod.adapt(okHttpCall);
T adapt(Call<R> call) {
    return callAdapter.adapt(call);
  }

通过ServiceMethod间接调用了我们设置的CallAdapter的adapt方法。

总结

通过我们的对Retrofit的源码的分析,主要需要理解两个类:

ServiceMethod:负责解析方法的注解和方法参数的注解。

OKhttpCall:负责将ServiceMethod和Okhttp的网络请求进行转换,并解析网络请求的结果。

Retrofit的实现的核心就是动态代理,不得不说动态代理在这种场景发挥出了不可思议的作用。

Retrofit的源码分析就到此结束了,有什么不懂的问题欢迎大家留言讨论。

demo下载地址:https://github.com/li504799868/RetrofitDemo

    原文作者:珠穆朗玛小王子
    原文地址: https://www.jianshu.com/p/fd02cf568fb0
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞