前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android高频面试专题 - 架构篇(二)okhttp面试必知必会

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

作者头像
Android扫地僧
发布2020-03-19 16:14:36
4K0
发布2020-03-19 16:14:36
举报
文章被收录于专栏:Android进阶

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

1、HTTP报文结构

  • 请求报文
  • 响应报文

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使用

代码语言:javascript
复制
//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、同步请求详细源码解读

代码语言:javascript
复制
@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()才开始进行网络请求。

代码语言:javascript
复制
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、异步请求详细源码解读

代码语言:javascript
复制
@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添加到正在运行的异步队里,并通过线程池异步执行,否则就将其丢到等待队列排队。

代码语言:javascript
复制
synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    runningAsyncCalls.add(call);
    executorService().execute(call);
  } else {
    readyAsyncCalls.add(call);
  }
}

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

代码语言:javascript
复制
 @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读取就直接流已关闭的异常了。

代码语言:javascript
复制
  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的线程池中,不限量的线程池实现了对象复用)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-02-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android扫地僧 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档