简单的架构图
先来看看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;
}
}
网友评论