谈谈对Retrofit的理解

作者: dylanhuang88 | 来源:发表于2017-04-15 13:40 被阅读0次

概述

App的开发过程中,少不了和网络打交道。从最开始用的Android Async Http到Volley再到现在的Retrofit,Retrofit的使用方式给了我很深的印象,于是就看了它的源码实现,加深对它的学习和理解。

Retrofit介绍

retrofit其实不是一个网络请求库,算是一个请求库上层的一个使用封装,发请求的实现还是用的OKHttp。官方文档中有这么一句话:Use annotations to describe the HTTP request。即使用注解的方式去描述Http请求。Retrofit的实际作用就是让开发者可以通过注解很方便的产生请求并发起请求,不需要自己去构建Request。

Retrofit使用及源码分析

每个请求都要有API,Retrofit的API定义方式是通过接口的形式,如下代码:

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

很多人可能会觉得困惑,没看到API地址吖,这里只有一个相对的"users/{user}/repos",别急,我们再往下看使用。

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

GitHubService service = retrofit.create(GitHubService.class);

首先是在创建Retrofit时指定了baseUrl,这个就是用此Retrofit创建的请求都是以这个baseUrl为基础的。

然后通过retrofit.create(GitHubService.class);来创建一个GitHubService 的实例,这里使用的是动态代理,来看看这里面的实现吧。

  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, Object... args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod serviceMethod = loadServiceMethod(method);
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

Utils.validateServiceInterface(service);是对传入的service进行校验,Retrofit要求接口定义必需是interface,并且不能extend其它的interface。

Proxy.newProxyInstance这个就是动态代理的实现了。Java动态代理就是给了程序员一种可能:当你要调用某个Class的方法前或后,插入你想要执行的代码。Retrofit实现动态代理就是要在调用接口的方法时,通过反射去解析注解,解析参数,动态去生成一个Request请求,这也就印证了官方的Use annotations to describe the HTTP request这句话了。

具体的转换在 ServiceMethod serviceMethod = loadServiceMethod(method);里,来看下loadServiceMethod的实现:

ServiceMethod loadServiceMethod(Method method) {
    ServiceMethod result;
    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = new ServiceMethod.Builder(this, method).build();
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

可以看到,这里做了一个缓存,避免多次生成ServiceMethod,可以提高性能。我们再去看下具体的ServiceMethod生成逻辑:

public ServiceMethod build() {
      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 = createResponseConverter();

      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }

      if (httpMethod == null) {
        throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
      }

      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);
    }

这里初始化了callAdapter、responseType、responseConverter这些变量,用于发起请求和解析response的。这里有一行很关键的代码就是:

for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
}

这里对方法的注解进行了分析,来看下里面的逻辑吧:

 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、PUT都有呢,原来就是在这里进行解析的。

回过头来继续看create函数:

ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);

Retrofit默认使用OkHttp来发起请求,这里还做了一个适配,通过callAdapter把请求转成想要的类型,比如RxJava2CallAdapter,就会把请求传成Observable<?>类型。

源码分析就先到这里了,相信也有助于大家去理解Retrofit的使用了原理了。

总结

Retrofit并不是一个网络请求库,是一个网络请求的上层封装,提供友好的接口让使用者自定义请求。源码里使用了大量的工厂模式,适配器模式,设计感很强,方便扩展和自定义。官方提供了RxJava的支持,和RxJava结合起来用还是很爽的。如果服务器的接口是用Restful的,那是再合适不过了!!

相关文章

  • 谈谈对Retrofit的理解

    概述 App的开发过程中,少不了和网络打交道。从最开始用的Android Async Http到Volley再到现...

  • 第一次

    ... 谈谈现在使用的项目的技术, retrofit 动态代理 怎么理解 注解 算法: 链表反转 m n 中间的反转

  • Java进阶struts & Hibernate面试题(8)

    【71】谈谈你对Struts的理解。 【72】谈谈你对Hibernate的理解。 【72】谈谈你对Spring的理...

  • 面试积累之框架(五)

    谈谈你对AOP的理解: 谈谈对sprin的理解 SpringMVC的工作机制

  • 谈谈对this的理解

    this的指向 this表示当前对象,this的指向是根据调用的上下文来决定的,默认指向window对象,指向wi...

  • IT欧伟_常见sem面试题-谈谈你对sem的理解

    IT欧伟_常见sem面试题-谈谈你对sem的理解 IT欧伟_常见sem面试题-谈谈你对sem的理解: 1、谈谈你对...

  • Retrofit源码学习笔记

    最近看了Retrofit(2.5.0)的部分源码,借此博客记录下自己对Retrofit的理解,功力善浅,如有错误欢...

  • 谈谈对Dubbo的理解

    一、Dubbo的前世今生: 起初是阿里巴巴开源项目,中间有一段时间不维护了,后来被当当网给捡起来维护了,...

  • 谈谈对ThreadLocal的理解

    ThreadLocal是什么? ThreadLocal从字面上的理解是本地线程的意思,然而事实上它是共享变量的一份...

  • 谈谈对诗的理解

    1今天的故事是一首啊傻自己写的小诗,外加一些微不足道却特别想道的啊傻个人对诗的见解,啊傻希望不要写超过800字,毕...

网友评论

    本文标题:谈谈对Retrofit的理解

    本文链接:https://www.haomeiwen.com/subject/nsknattx.html