美文网首页Android进阶之路Android开发Android开发经验谈
Android高频面试专题 - 架构篇(二)okhttp面试必知

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

作者: Android扫地僧 | 来源:发表于2020-02-28 19:51 被阅读0次

    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学习PDF+架构视频+面试文档+源码笔记,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这些都是我闲暇还会反复翻阅的精品资料。在脑图中,每个知识点专题都配有相对应的实战项目,可以有效的帮助大家掌握知识点

    总之也是在这里帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习

    image.png

    关注微信公众号“Android扫地僧”(微信->添加朋友->公众号->输入“Android扫地僧”)
    自动回复,即可获取下载地址

    扫码领取资源.png

    相关文章

      网友评论

        本文标题:Android高频面试专题 - 架构篇(二)okhttp面试必知

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