美文网首页
okhttp架构原理剖析

okhttp架构原理剖析

作者: 嘉伦哥 | 来源:发表于2019-10-29 15:58 被阅读0次

    简单的架构图

    先来看看okhttp 简单的架构,分为6层。每一层都负责各自的任务!下面会对每一层进行简单的剖析!


    image.png

    1.Interface-接口层

    接口层包含:okhttclient、Call、RealCall、AsyncCall、Dispatcher

    • okhttpclient:主要负责网络请求,用户的网络框架的各种设置也是通过okhttpclient设置的!而整个Application中,应该共享一个okhttpclient 实例。

    • Call: 每一个请求的实例,比如登录login 对应一个Call、获取用户信息 对应一个Call。Call本身就是一个接口,用户的每一个Http请求就是一个Call实例,而且每一个Call都对应一个线程。
      Call包含了request()、execute()、enqueue()方法。

    • RealCall:具体的Call接口实现类,代表每一个HTTP请求。每一个RealCall内部有一个AsyncCall final类。

    • AsyncCall:RealCall类的内部final类,实现了NamedRunnable类的execute()。继承于NamedRunnable类,NamedRunnable类实现了Runnable接口,并且有一个execute()抽象方法,这个抽象方法在Runnable的run()里执行。

    • Dispatcher:

      • OkHttp的任务队列,其内部维护了一个线程池,进行线程分发,实现非阻塞,高可用,高并发。
      • 当有接收到一个Call时,Dispatcher负责在线程池中找到空闲的线程并执行其execute方法。
      • Okhttp采用Deque作为缓存队列,按照入队的顺序先进先出。
      • OkHttp最出彩的地方就是在try/finally中调用了finished函数,可以主动控制等待队列的移动,而不是采用 锁或者wait/notify,极大减少了编码复杂性。

    2.Protocal协议层

    Protocol层负责处理协议逻辑,OkHttp支持Http1、Http2、WebSocket协议。

    • Http2 区别于Http1:
      • Http2使用的是二进制传送,HTTP1.X是文本(字符串)传送。
        二进制传送的单位是帧和流。帧组成了流,同时流还有流ID标示。

      • Http2支持多路复用。
        因为有流ID,所以通过同一个http请求实现多个http请求传输变成了可能,可以通过流ID来标示究竟是哪个流从而定位到是哪个http请求。

      • Http2头部压缩。
        HTTP2通过gzip和compress压缩头部然后再发送,同时客户端和服务器端同时维护一张头信息表,所有字段都记录在这张表中,这样后面每次传输只需要传输表里面的索引Id就行,通过索引ID就可以知道表头的值了。

      • Http2支持在客户端未经请求许可的情况下,主动向客户端推送内容。
        HTTP2支持在客户端未经请求许可的情况下,主动向客户端推送内容

    3.Connection-连接层

    在连接层中有一个连接池,统一管理所有的Socket连接,当用户新发起一个网络请求时,OkHttp会首先从连接池中查找是否有符合要求的连接,如果有则直接通过该连接发送网络请求;否则新创建一个连接。RealConnection描述一个物理Socket连接,连接池中维护多个RealConnection实例。由于Http/2支持多路复用,一个RealConnection可以支持多个网络访问请求,所以OkHttp又引入了StreamAllocation来描述一个实际的网络请求

    • RealConnection:
    rivate RealConnection findConnection(。。。){   
         result = new RealConnection(connectionPool, selectedRoute);
        result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
    
    }
    public void connect(。。。) {
         //如果协议不等于null,抛出一个异常
        if (protocol != null) throw new IllegalStateException("already connected");
    
       。。 省略部分代码。。。。
    
        while (true) {//一个while循环
             //如果是https请求并且使用了http代理服务器
            if (route.requiresTunnel()) {
              connectTunnel(...);
            } else {//
                //直接打开socket链接
              connectSocket(connectTimeout, readTimeout);
            }
            //建立协议
            establishProtocol(connectionSpecSelector);
            break;//跳出while循环
            。。省略部分代码。。。
      }
    

    4.Cache-缓存层

    在okhttpClient源码中可以发现,内部缓存使用到时一个Cache类

    OkHttpClient(Builder builder) {
        this.dispatcher = builder.dispatcher;
        this.proxy = builder.proxy;
        this.protocols = builder.protocols;
        this.connectionSpecs = builder.connectionSpecs;
        this.interceptors = Util.immutableList(builder.interceptors);
        this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
        this.eventListenerFactory = builder.eventListenerFactory;
        this.proxySelector = builder.proxySelector;
        this.cookieJar = builder.cookieJar;
        this.cache = builder.cache;
        .....省略代码
        .....省略代码
      }
    

    看一下cache类中的构造方法使用的是DiskLruCache缓存机制

    Cache(File directory, long maxSize, FileSystem fileSystem) {
        this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
      }
    

    DiskLruCache 和LruCache 都知道,分别用于实现内存缓存和硬盘缓存,其核心思想都是LRU算法。

    • DiskLruCache:简单分析一下内部缓存机制。
      核心内部类Entry.class,体现DiskLruCache 存储机制。
    
    private final class Entry {
        final String key;
    
        /** Lengths of this entry's files. */
        final long[] lengths;
        final File[] cleanFiles;
        final File[] dirtyFiles;
    
        /** True if this entry has ever been published. */
        boolean readable;
    
        /** The ongoing edit or null if this entry is not being edited. */
        Editor currentEditor;
    
        /** The sequence number of the most recently committed edit to this entry. */
        long sequenceNumber;
    
        Entry(String key) {
          this.key = key;
    
          lengths = new long[valueCount];
          cleanFiles = new File[valueCount];
          dirtyFiles = new File[valueCount];
    
          // The names are repetitive so re-use the same builder to avoid allocations.
          StringBuilder fileBuilder = new StringBuilder(key).append('.');
          int truncateTo = fileBuilder.length();
          for (int i = 0; i < valueCount; i++) {
            fileBuilder.append(i);
            cleanFiles[i] = new File(directory, fileBuilder.toString());
            fileBuilder.append(".tmp");
            dirtyFiles[i] = new File(directory, fileBuilder.toString());
            fileBuilder.setLength(truncateTo);
          }
        }
    
    
     /**
         * Returns a snapshot of this entry. This opens all streams eagerly to guarantee that we see a
         * single published snapshot. If we opened streams lazily then the streams could come from
         * different edits.
         */
        Snapshot snapshot() {
          if (!Thread.holdsLock(DiskLruCache.this)) throw new AssertionError();
    
          Source[] sources = new Source[valueCount];
          long[] lengths = this.lengths.clone(); // Defensive copy since these can be zeroed out.
          try {
            for (int i = 0; i < valueCount; i++) {
              sources[i] = fileSystem.source(cleanFiles[i]);
            }
            return new Snapshot(key, sequenceNumber, sources, lengths);
          } catch (FileNotFoundException e) {
            // A file must have been deleted manually!
            for (int i = 0; i < valueCount; i++) {
              if (sources[i] != null) {
                Util.closeQuietly(sources[i]);
              } else {
                break;
              }
            }
            // Since the entry is no longer valid, remove it so the metadata is accurate (i.e. the cache
            // size.)
            try {
              removeEntry(this);
            } catch (IOException ignored) {
            }
            return null;
          }
        }
    

    5.IO层

    6.Interceptor-拦截器层

    相关文章

      网友评论

          本文标题:okhttp架构原理剖析

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