Retrofit和OkHttp原理剖析

1,237 阅读7分钟

Retrofit原理:

要理解Retrofit的原理,我们首先要知道 注解动态代理

1.注解(annotation)

我们最常见的注解是java内置的@Override注解:

image.png

加上此注解表示该方法是重写父类的方法,方法签名错误时,编译器会发出错误提示。

这说明@Override注解在编译期就起了作用,按住ctrl,鼠标左键点进@Override:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Target是作用于注解上的注解,表示该注解作用的目标(一个方法或是一个域)

@Retention表明该注解的作用域:

1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被编译器遗弃; 2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期; 3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在,因此可以通过反射机制读取注解的信息;

生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。

我们来看Retrofit给出的,写接口的例子:

public interface GitHub {
  @GET("/repos/{owner}/{repo}/contributors")
  Call<List<Contributor>> contributors(@Path("owner") String owner, @Path("repo") String repo);
}

使用过Retrofit的人知道,该接口方法会被转换成调用网络请求的方法。

看看@GET:

/** Make a GET request. */
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
  /**
   * A relative or absolute path, or full URL of the endpoint. This value is optional if the first
   * parameter of the method is annotated with {@link Url @Url}.
   *
   * <p>See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how
   * this is resolved against a base URL to create the full endpoint URL.
   */
  String value() default "";
}

说明该注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。

2.动态代理

静态代理:

public interface IProxy {
    void hello();
    void bye();
}
public class Greet implements IProxy{
​
    @Override
    public void hello() {
        System.out.print("hello");
    }
​
    @Override
    public void bye() {
        System.out.print("bye");
    }
}
public class GreetToWorld implements IProxy{
    public GreetToWorld(IProxy greet) {
        this.greet = greet;
    }
​
    IProxy greet;
​
    @Override
    public void hello() {
        greet.hello();
        System.out.println(",world");
    }
​
    @Override
    public void bye() {
        greet.bye();
        System.out.println(",world");
    }
}
public class Test {
    public static void main(String[] args) {
        Greet greet = new Greet();
        //静态代理
        GreetToWorld greetToWorld = new GreetToWorld(greet);
        greetToWorld.hello();
        greetToWorld.bye();
    }
}

输出:

image-20210828152439382.png 静态代理的缺点是每一个被代理的类都需要新建一个代理类,尽管这个代理类只是都只是增加了相同的逻辑。

我们需要生成一个代理对象,调用这个对象的方法A,就等于调用被代理对象的方法A,然后加上自己的逻辑B。我们需要代理对象的ClassLoader,和代理对象需要被代理的方法(用接口表示),然后自定义自己的逻辑B。

动态代理:

public class Test {
    public static void main(String[] args) {
        Greet greet = new Greet();
        //动态代理hello
        IProxy dynamicProxy = (IProxy) Proxy.newProxyInstance(Greet.class.getClassLoader(), new Class<?>[]{IProxy.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object res = method.invoke(greet, args);//在这里调用被代理对象的方法A
                System.out.print("world! ");//在这里加上自己的逻辑B
                return res;
            }
        });
        dynamicProxy.hello();
        dynamicProxy.bye();
    }
}

输出:

image-20210828154446878.png 再看Retrofit:

public interface GitHub {
  @GET("/repos/{owner}/{repo}/contributors")
  Call<List<Contributor>> contributors(@Path("owner") String owner, @Path("repo") String repo);
}

Retrofit将这个GitHub接口中的方法,转换成网络请求,需要先新建一个Retrofit实例(后面讲如何配置),然后调用GitHub github = retrofit.create(GitHub.class)

    // Create a very simple REST adapter which points the GitHub API.
    Retrofit retrofit =
        new Retrofit.Builder()
            .baseUrl(API_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build();
​
    // Create an instance of our GitHub API interface.
    GitHub github = retrofit.create(GitHub.class);

需要进行网络请求时,使用:

    // Create a call instance for looking up Retrofit contributors.
    Call<List<Contributor>> call = github.contributors("square", "retrofit");
​
    // Fetch and print a list of the contributors to the library.
    List<Contributor> contributors = call.execute().body();

第一步传入参数,新建一个Call对象:Call用来发送网络请求,每个Call产生一对自己的<Request, Response>,可以用clone方法新建一个参数和请求地址都相同的Call来实现轮询(poll)或者重传(retry)。用execute()发送一个同步请求,用enqueue()发送一个异步请求。

github.contributors("square", "retrofit")调用的肯定不是GitHub接口中的contributors方法,接口方法无法调用。此时调用的是被代理的GitHub接口中的contributors方法,所有的接口都被动态代理用同一个模板代理为网络请求,代理过程在GitHub github = retrofit.create(GitHub.class)中实现:

public <T> T create(final Class<T> service) {
  validateServiceInterface(service);
  return (T)
      Proxy.newProxyInstance(
          service.getClassLoader(),
          new Class<?>[] {service},
          new InvocationHandler() {
            private final Platform platform = Platform.get();
            private final Object[] emptyArgs = new Object[0];
​
            @Override
            public @Nullable Object invoke(Object proxy, Method method, @Nullable 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);
              }
              args = args != null ? args : emptyArgs;
              return platform.isDefaultMethod(method)
                  ? platform.invokeDefaultMethod(method, service, proxy, args)
                  : loadServiceMethod(method).invoke(args);
            }
          });
}

首先用validateServiceInterface(service)验证传入的service是一个接口,然后返回一个代理对象,这个代理对象把所有请求变成loadServiceMethod(method).invoke(args)。

ServiceMethod<?> loadServiceMethod(Method method) {
  ServiceMethod<?> result = serviceMethodCache.get(method);
  if (result != null) return result;
​
  synchronized (serviceMethodCache) {
    result = serviceMethodCache.get(method);
    if (result == null) {
      result = ServiceMethod.parseAnnotations(this, method);
      serviceMethodCache.put(method, result);
    }
  }
  return result;
}

这里有一个缓存,没缓存的话就调用ServiceMethod.parseAnnotations(this, method)解析接口的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);
  } 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(method, "@Headers annotation is empty.");
    }
    headers = parseHeaders(headersToParse);
  } else if (annotation instanceof Multipart) {
    if (isFormEncoded) {
      throw methodError(method, "Only one encoding annotation is allowed.");
    }
    isMultipart = true;
  } else if (annotation instanceof FormUrlEncoded) {
    if (isMultipart) {
      throw methodError(method, "Only one encoding annotation is allowed.");
    }
    isFormEncoded = true;
  }
}

在这里把注解解析为请求的方法。

Retrofit只负责生产对象,生产能做网络请求的工作对象,他有点像一个工厂,只提供产品,工厂本身不处理网络请求,产品才能处理网络请求。 img Retrofit使用OkHttpClient来实现网络请求,这个OkHttpClient虽然不能替换为其他的网络执行框架比如Volley,但是Retrofit允许我们使用自己扩展OkHttpClient,一般最常扩展的就是Interceptor拦截器了。

扩展的是对返回的数据类型的自动转换,把一种数据对象转换为另一种数据对象。 在上述场景中,GsonConverterFactory可以把Http访问得到的json字符串转换为Java数据对象BizEntity。

扩展的是对网络工作对象callWorker的自动转换,把Retrofit中执行网络请求的Call对象,转换为接口中定义的Call对象,例如Rxjava的Observable对象。

image.png OKHTTP的特点:

  • 支持 Http2.0,允许同一主机的所有请求共享套接字。

    • Http2.0:安全是因为http2.0建立在https协议的基础上,高效是因为它是通过二进制分帧来进行数据传输
  • 使用连接池减少请求延迟(如果HTTP / 2不可用)。

  • 透明GZIP缩小下载大小。

  • 响应缓存可以避免重复请求的网络。

    • OKHttp 提供了缓存机制以将我们的的 HTTP 和 HTTPS 请求的响应缓存到文件系统中
    • 构造 OkHttpClient 时配置 Cache,设置缓存路径已经缓存大小
    • 构造 Request 时配置 CacheControl,配置缓存相关属性
  • 内部实现任务队列,提高并发访问的效率。

  • 实现拦截器链(InterceptorChain),实现request与response的分层处理

  • OkHttp 也提供了对 WebSocket 的支持。

    • HTTP 协议有一个缺陷:通信只能由客户端发起
    • WebSocket : 服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于[“服务器推送技术”]的一种
    • 设置pingInterval会定时的向服务器发送一个消息来保持长连接

同步请求:

Response response = mOkHttpClient.newCall(okRequest).execute();

异步请求:WebSocke长连接WebSocketTCP连接建立后,还要通过Http进行一次握手,也就是通过Http发送一条GET请求消息给服务器,告诉服务器我要建立WebSocket连接了,你准备好哦,具体做法就是在头部信息中添加相关参数。然后服务器响应我知道了,并且将连接协议改成WebSocket,开始建立长连接。

  • URL一般是以ws或者wss开头,ws对应Websocket协议,wss对应在TLS之上的WebSocket。类似于HttpHttps的关系,如wss://192.168.1.16。

OKHTTP的响应头

final class RealCall implements Call {
    final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;
//...
    @Override 
    public Response execute() throws IOException {
        synchronized (this) {
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }
        captureCallStackTrace();
        try {
            //进行网络请求
            client.dispatcher().executed(this);
            //经过一层层网络拦截器之后,获取网络请求的返回值
            Response result = getResponseWithInterceptorChain();
            if (result == null) throw new IOException("Canceled");
            return result;
        } finally {
            client.dispatcher().finished(this);
        }
      }
      Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        List<Interceptor> interceptors = new ArrayList<>();
        //Application拦截器
        interceptors.addAll(client.interceptors());
        //重定向和失败后重新请求拦截器
        interceptors.add(retryAndFollowUpInterceptor);
        //网桥拦截器,顾名思义client和Server之前的桥梁
        interceptors.add(new BridgeInterceptor(client.cookieJar()));
        //缓存处理拦截器
        interceptors.add(new CacheInterceptor(client.internalCache()));
        //Socket层的握手链接
        interceptors.add(new ConnectInterceptor(client));
        if (!forWebSocket) {
            //网络拦截器,我们可以自定义这个拦截器,比放在前面获得更多信息
            interceptors.addAll(client.networkInterceptors());
        }
        //client和Server之前的读写操作
        interceptors.add(new CallServerInterceptor(forWebSocket));
        //责任链开始执行
        Interceptor.Chain chain = new RealInterceptorChain(
            interceptors, null, null, null, 0, originalRequest);
        return chain.proceed(originalRequest);
      }
}

\