Android高频面试专题 – 架构篇(二)okhttp面试必知必会

okhttp的火热程度,不用多说,已经被谷歌爸爸加入到Android源码中,也是面试高频的问题之一,如果只是满足于API工程师,那么面试还是有一点难度的。

1、HTTP报文结构

  • 请求报文

image.png

  • 响应报文

image

2、HTTP发展历史

  • HTTP/0.9
    只有一个命令GET
    没有HEADER等描述数据的信息
    服务器发送完毕,就关闭TCP连接

  • HTTP/1.0
    增加了很多命令
    增加status code和header
    多字符集支持、多部分发送、权限、缓存等

  • HTTP/1.1
    持久连接
    pipeline
    增加host和其他一些命令

  • HTTP2
    所有数据以二进制传输
    同一个连接里面发送多个请求不再需要按照顺序来
    头信息压缩以及推送等提高效率的功能

注意:目前使用最多的仍然是HTTP1.1,可以抓包看。

3、okhttp有哪些优势

1)支持http2,对一台机器的所有请求共享同一个socket

2)内置连接池,支持连接复用,减少延迟

3)支持透明的gzip压缩响应体

4)通过缓存避免重复的请求

5)请求失败时自动重试主机的其他ip,自动重定向

6)丰富的API,可扩展性好

4、okhttp使用

//1.创建OkHttpClient
OkHttpClient client = new OkHttpClient();
//2.创建Request,并填入url信息
String run(String url) throws IOException {
Request request = new Request.Builder()
    .url(url)
    .build();
//3.同步请求
Response response = client.newCall(request).execute();
//4.异步请求
client.newCall(request).enqueue(responseCallback)

**5、看过okhttp源码吗?**简单介绍一下

根据以上使用代码,不管同步还是异步请求,都是通过client.newCall(request)来进行执行,这个newCall其实是创建了一个RealCall对象,所以的请求处理,都是由RealCall来完成,RealCall进进行请求前,会坚持是否已经执行过,如果已执行会抛出异常,也就是说,一个Call对象只能处理一次请求。真正进行网络请求的是getResponseWithInterceptorChain()方法,该方法内部将一系列的拦截器构成拦截链,然后链式执行proceed()方法完成网络请求。

6、同步请求详细源码解读

@Override 
public Response execute() throws IOException {
    synchronized (this) {
      //1.如果该请求正在运行抛出异常,否则将运行标志位置为true,防止重复请求
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    //2.捕获调用堆栈的跟踪
    captureCallStackTrace();
    //告知eventlisten请求开始
    eventListener.callStart(this);
    try {
      //3.通过dispatcher的executed来实际执行
      client.dispatcher().executed(this);
      //4.经过一系列"拦截"操作后获取结果
      Response result = getResponseWithInterceptorChain();
      //如果result为空抛出异常
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      //告知eventlisten请求失败
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      //通知dispatcher执行完毕
      client.dispatcher().finished(this);
    }
}

③中client.dispatcher().executed(this) 仅仅是将call加入同步请求队列,并没有真正开始进行网络请求,Dispatcher 内部维护着三个队列:同步请求队列 runningSyncCalls、异步请求队列 runningAsyncCalls、异步缓存队列 readyAsyncCalls,和一个线程池 executorService,来维护、管理、执行OKHttp的请求。④中getResponseWithInterceptorChain()才开始进行网络请求。

Response getResponseWithInterceptorChain() throws IOException {
    //创建一个拦截器链表用于存放各种拦截器
    List<Interceptor> interceptors = new ArrayList<>();
    //向链表中添加用户自定义的拦截器
    interceptors.addAll(client.interceptors());
    //1.向链表中添加retryAndFollowUpInterceptor用于失败重试和重定向 
    interceptors.add(retryAndFollowUpInterceptor);
    //2.向链表中添加BridgeInterceptor用于把用户构造的请求转换为发送给服务器的请求,把服务器返回的响应转换为对用户友好的响应。
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //3.向链表中添加CacheInterceptor用于读取缓存以及更新缓存
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //4.向链表中添加ConnectInterceptor用于与服务器建立连接
    interceptors.add(new ConnectInterceptor(client));
    //如果不是webSocket添加networkInterceptors
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    //5.向链表中添加CallServerInterceptor用于从服务器读取响应的数据
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //根据上述的拦截器链表生成一个拦截链
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    //通过拦截链的proceed方法开始整个拦截链事件的传递
    return chain.proceed(originalRequest);
  }

主要列举一下默认已经实现的几个拦截器的作用:

RetryAndFollowUpInterceptor:重试和失败重定向拦截器

BridgeInterceptor:桥接拦截器,处理一些必须的请求头信息的拦截器

CacheInterceptor:缓存拦截器,用于处理缓存

ConnectInterceptor:连接拦截器,建立可用的连接,是CallServerInterceptor的基本

CallServerInterceptor:请求服务器拦截器将 http 请求写进 IO 流当中,并且从 IO 流中读取响应 Response

具体细节,请阅读源码,这里不再进行细节描述。

https://yq.aliyun.com/articles/78105

7、异步请求详细源码解读

@Override 
public void enqueue(Callback responseCallback) {
  synchronized (this) {
    //1.如果该请求正在运行抛出异常,否则将运行标志位置为true,防止重复请求
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  //2.捕获调用堆栈的跟踪
  captureCallStackTrace();
  eventListener.callStart(this);
  //3.通过dispatcher的enqueue将此次请求添加到异步队列
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

enqueue实际上是new了一个RealCall的内部类AsyncCall扔进了dispatcher中,如果当前正在运行的异步请求数小于阈值maxRequests (默认Dispatcher中为64)并且同host下运行的请求小于阈值maxRequestsPerHost(默认Dispatcher中为5),就将AsyncCall添加到正在运行的异步队里,并通过线程池异步执行,否则就将其丢到等待队列排队。

synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    runningAsyncCalls.add(call);
    executorService().execute(call);
  } else {
    readyAsyncCalls.add(call);
  }
}

AsyncCall实际上是一个Runnable,我们看一下进入线程池后真正执行的代码:

@Override 
 protected void execute() {
      boolean signalledCallback = false;
      try {
        //还是通过链式调用实现
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }

可以看到,内部还是跟同步请求一样,通过getResponseWithInterceptorChain()完成请求,然后通过传入的callBack回调请求结果,最后在finally中通知Dispatcher此次请求已完成,Dispatcher会在finish中检查当前请求数是否已低于阈值,若低于就去readyAsyncCalls等待队列中取出下一个请求。

8、okhttp实现网络请求的方法

OkHttp3的最底层是Socket,而不是URLConnection,它通过Platform的Class.forName()反射获得当前Runtime使用的socket库

socket发起网络请求的流程一般是:

(1). 创建socket对象;

(2). 连接到目标网络;

(3). 进行输入输出流操作。

(1)(2)的实现,封装在connection接口中,具体的实现类是RealConnection。(3)是通过stream接口来实现,根据不同的网络协议,有Http1xStream和Http2xStream两个实现类,由于创建网络连接的时间较久(如果是HTTP的话,需要进行三次握手),而请求经常是频繁的碎片化的,所以为了提高网络连接的效率,OKHttp3实现了网络连接复用。

9、okhttp实现带进度上传下载

OkHttp把请求和响应分别封装成了RequestBody和ResponseBody,下载进度自定义ResponseBody,重写source()方法,上传进度自定义RequestBody,重写writeTo()方法。

下载 https://www.jianshu.com/p/df7d4945f007

上传 https://blog.csdn.net/u011247387/article/details/83027254

10、为什么response.body().string() 只能调用一次

我们可能习惯在获取到Response对象后,先response.body().string()打印一遍log,再进行数据解析,却发现第二次直接抛异常,其实直接跟源码进去看就发现,通过source拿到字节流以后,直接给closeQuietly悄悄关闭了,这样第二次再去通过source读取就直接流已关闭的异常了。

public final String string() throws IOException {
    BufferedSource source = source();
    try {
      Charset charset = Util.bomAwareCharset(source, charset());
      return source.readString(charset);
    } finally {
      //这里讲resource给悄悄close了
      Util.closeQuietly(source);
    }
  }

解决方案:1.内存缓存一份response.body().string();2.自定义拦截器处理log。

11、okhttp运用的设计模式

  • 构造者模式(OkhttpClient,Request等各种对象的创建)

  • 工厂模式(在Call接口中,有一个内部工厂Factory接口。)

  • 单例模式(Platform类,已经使用Okhttp时使用单例)

  • 策略模式(在CacheInterceptor中,在响应数据的选择中使用了策略模式,选择缓存数据还是选择网络访问。)

  • 责任链模式(拦截器的链式调用)

  • 享元模式(Dispatcher的线程池中,不限量的线程池实现了对象复用)


更多面试专题,进阶知识,尽在“Android扫地僧”
在这里插入图片描述