Android登录拦截器结构思路,Android小知识-剖析OkHttp中的五个拦截器(中篇)

本平台的文章更新会有延迟,大家可以关注微信公众号-顾林海,包括年底前会更新kotlin由浅入深系列教程,目前计划在微信公众号进行首发,如果大家想获取最新教程,请关注微信公众号,谢谢

在上一小节介绍了重试重定向拦截器RetryAndFollowUpInterceptor和桥接适配拦截器BridgeInterceptor,这节分析缓存拦截器CacheInterceptor。

缓存策略mHttpClient = new OkHttpClient.Builder()

.cache(new Cache(new File("cache"),30*1024*1024))//使用缓存策略

.build();

在OkHttp中使用缓存可以通过OkHttpClient的静态内部类Builder的cache方法进行配置,cache方法传入一个Cache对象。public Cache(File directory, long maxSize) {    this(directory, maxSize, FileSystem.SYSTEM);

}

创建Cache对象时需要传入两个参数,第一个参数directory代表的是缓存目录,第二个参数maxSize代表的是缓存大小。

在Chache有一个很重要的接口:public final class Cache implements Closeable, Flushable {        final InternalCache internalCache = new InternalCache() {            @Override

public Response get(Request request) throws IOException {                return Cache.this.get(request);

}            @Override

public CacheRequest put(Response response) throws IOException {                return Cache.this.put(response);

}            @Override

public void remove(Request request) throws IOException {

Cache.this.remove(request);

}            @Override

public void update(Response cached, Response network) {

Cache.this.update(cached, network);

}            @Override

public void trackConditionalCacheHit() {

Cache.this.trackConditionalCacheHit();

}            @Override

public void trackResponse(CacheStrategy cacheStrategy) {

Cache.this.trackResponse(cacheStrategy);

}

};

...

}

通过InternalCache这个接口实现了缓存的一系列操作,接着我们一步步看它是如何实现的,接下来分析缓存的get和put操作。

先看InternalCache的put方法,也就是存储缓存:public final class Cache implements Closeable, Flushable {        final InternalCache internalCache = new InternalCache() {

...            @Override

public CacheRequest put(Response response) throws IOException {                return Cache.this.put(response);

}

...

};

...

}

InternalCache的put方法调用的是Cache的put方法,往下看:@Nullable CacheRequest put(Response response) {        //标记1:获取请求方法

String requestMethod = response.request().method();        //标记2:判断缓存是否有效

if (HttpMethod.invalidatesCache(response.request().method())) {            try {

remove(response.request());

} catch (IOException ignored) {

}            return null;

}        //标记3:非GET请求不使用缓存

if (!requestMethod.equals("GET")) {            return null;

}        if (HttpHeaders.hasVaryAll(response)) {            return null;

}        //标记4:创建缓存体类

Cache.Entry entry = new Cache.Entry(response);        //标记5:使用DiskLruCache缓存策略

DiskLruCache.Editor editor = null;        try {

editor = cache.edit(key(response.request().url()));            if (editor == null) {                return null;

}

entry.writeTo(editor);            return new Cache.CacheRequestImpl(editor);

} catch (IOException e) {

abortQuietly(editor);            return null;

}

}

首先在标记1处获取我们的请求方式,接着在标记2处根据请求方式判断缓存是否有效,通过HttpMethod的静态方法invalidatesCache。public static boolean invalidatesCache(String method) {    return method.equals("POST")

|| method.equals("PATCH")

|| method.equals("PUT")

|| method.equals("DELETE")

|| method.equals("MOVE");     // WebDAV

}

通过invalidatesCache方法,如果请求方式是POST、PATCH、PUT、DELETE以及MOVE中一个,就会将当前请求的缓存移除。

在标记3处会判断如果当前请求不是GET请求,就不会进行缓存。

在标记4处创建Entry对象,Entry的构造器如下:Entry(Response response) {      this.url = response.request().url().toString();      this.varyHeaders = HttpHeaders.varyHeaders(response);      this.requestMethod = response.request().method();      this.protocol = response.protocol();      this.code = response.code();      this.message = response.message();      this.responseHeaders = response.headers();      this.handshake = response.handshake();      this.sentRequestMillis = response.sentRequestAtMillis();      this.receivedResponseMillis = response.receivedResponseAtMillis();

}

创建的Entry对象在内部会保存我们的请求url、头部、请求方式、协议、响应码等一系列参数。

在标记5处可以看到原来OkHttp的缓存策略使用的是DiskLruCache,DiskLruCache是用于磁盘缓存的一套解决框架,OkHttp对DiskLruCache稍微做了点修改,并且OkHttp内部维护着清理内存的线程池,通过这个线程池完成缓存的自动清理和管理工作,这里不做过多介绍。

拿到DiskLruCache的Editor对象后,通过它的edit方法创建缓存文件,edit方法传入的是缓存的文件名,通过key方法将请求url进行MD5加密并获取它的十六进制表示形式。

接着执行Entry对象的writeTo方法并传入Editor对象,writeTo方法的目的是将我们的缓存信息存储在本地。

点进writeTo方法:public void writeTo(DiskLruCache.Editor editor) throws IOException {

BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));        //缓存URL

sink.writeUtf8(url)

.writeByte('\n');        //缓存请求方式

sink.writeUtf8(requestMethod)

.writeByte('\n');        //缓存头部

sink.writeDecimalLong(varyHeaders.size())

.writeByte('\n');        for (int i = 0, size = varyHeaders.size(); i 

sink.writeUtf8(varyHeaders.name(i))

.writeUtf8(": ")

.writeUtf8(varyHeaders.value(i))

.writeByte('\n');

}        //缓存协议,响应码,消息

sink.writeUtf8(new StatusLine(protocol, code, message).toString())

.writeByte('\n');

sink.writeDecimalLong(responseHeaders.size() + 2)

.writeByte('\n');        for (int i = 0, size = responseHeaders.size(); i 

sink.writeUtf8(responseHeaders.name(i))

.writeUtf8(": ")

.writeUtf8(responseHeaders.value(i))

.writeByte('\n');

}        //缓存时间

sink.writeUtf8(SENT_MILLIS)

.writeUtf8(": ")

.writeDecimalLong(sentRequestMillis)

.writeByte('\n');

sink.writeUtf8(RECEIVED_MILLIS)

.writeUtf8(": ")

.writeDecimalLong(receivedResponseMillis)

.writeByte('\n');        //判断https

if (isHttps()) {

sink.writeByte('\n');

sink.writeUtf8(handshake.cipherSuite().javaName())

.writeByte('\n');

writeCertList(sink, handshake.peerCertificates());

writeCertList(sink, handshake.localCertificates());

sink.writeUtf8(handshake.tlsVersion().javaName()).writeByte('\n');

}

sink.close();

}

writeTo方法内部对Response的相关信息进行缓存,并判断是否是https请求并缓存Https相关信息,从上面的writeTo方法中发现,返回的响应主体body并没有在这里进行缓存,最后返回一个CacheRequestImpl对象。private final class CacheRequestImpl implements CacheRequest {    private final DiskLruCache.Editor editor;    private Sink cacheOut;    private Sink body;    boolean done;

CacheRequestImpl(final DiskLruCache.Editor editor) {      this.editor = editor;      this.cacheOut = editor.newSink(ENTRY_BODY);      this.body = new ForwardingSink(cacheOut) {        @Override public void close() throws IOException {          synchronized (Cache.this) {            if (done) {              return;

}

done = true;

writeSuccessCount++;

}          super.close();

editor.commit();

}

};

}

}

在CacheRequestImpl类中有一个body对象,这个就是我们的响应主体。CacheRequestImpl实现了CacheRequest接口,用于暴露给缓存拦截器,这样的话缓存拦截器就可以直接通过这个类来更新或写入缓存数据。

看完了put方法,继续看get方法:public final class Cache implements Closeable, Flushable {        final InternalCache internalCache = new InternalCache() {

...            @Override public Response get(Request request) throws IOException {               return Cache.this.get(request);

}

...

};

...

}

查看Cache的get方法:@Nullable Response get(Request request) {        //获取缓存的key

String key = key(request.url());        //创建快照

DiskLruCache.Snapshot snapshot;

Cache.Entry entry;        try {            //更加key从缓存获取

snapshot = cache.get(key);            if (snapshot == null) {                return null;

}

} catch (IOException e) {            return null;

}        try {            //从快照中获取缓存

entry = new Cache.Entry(snapshot.getSource(ENTRY_METADATA));

} catch (IOException e) {

Util.closeQuietly(snapshot);            return null;

}

Response response = entry.response(snapshot);        if (!entry.matches(request, response)) {            //响应和请求不是成对出现

Util.closeQuietly(response.body());            return null;

}        return response;

}

get方法比较简单,先是根据请求的url获取缓存key,创建snapshot目标缓存中的快照,根据key获取快照,当目标缓存中没有这个key对应的快照,说明没有缓存返回null;如果目标缓存中有这个key对应的快照,那么根据快照创建缓存Entry对象,再从Entry中取出Response。

Entry的response方法:public Response response(DiskLruCache.Snapshot snapshot) {

String contentType = responseHeaders.get("Content-Type");

String contentLength = responseHeaders.get("Content-Length");        //根据头部信息创建缓存请求

Request cacheRequest = new Request.Builder()

.url(url)

.method(requestMethod, null)

.headers(varyHeaders)

.build();        //创建Response

return new Response.Builder()

.request(cacheRequest)

.protocol(protocol)

.code(code)

.message(message)

.headers(responseHeaders)

.body(new Cache.CacheResponseBody(snapshot, contentType, contentLength))

.handshake(handshake)

.sentRequestAtMillis(sentRequestMillis)

.receivedResponseAtMillis(receivedResponseMillis)

.build();

}

Entry的response方法中会根据头部信息创建缓存请求,然后创建Response对象并返回。

回到get方法,接着判断响应和请求是否成对出现,如果不是成对出现,关闭流并返回null,否则返回Response。

到这里缓存的get和put方法的整体流程已经介绍完毕,接下来介绍缓存拦截器。

CacheInterceptor

进入CacheInterceptor的intercept方法,下面贴出部分重要的代码。@Override public Response intercept(Interceptor.Chain chain) throws IOException {

Response cacheCandidate = cache != null

? cache.get(chain.request())

: null;

...

}

第一步先尝试从缓存中获取Response,这里分两种情况,要么获取缓存Response,要么cacheCandidate为null。@Override public Response intercept(Interceptor.Chain chain) throws IOException {

...

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

Request networkRequest = strategy.networkRequest;

Response cacheResponse = strategy.cacheResponse;

...

}

第二步,获取缓存策略CacheStrategy对象,CacheStrategy内部维护了一个Request和一个Response,也就是说CacheStrategy能指定到底是通过网络还是缓存,亦或是两者同时使用获取Response。CacheStrategy内部工厂类Factory的get方法如下:    public CacheStrategy get() {

CacheStrategy candidate = getCandidate();      if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {        // We're forbidden from using the network and the cache is insufficient.

return new CacheStrategy(null, null);

}      return candidate;

}

方法中通过getCandidate方法获取CacheStrategy对象,继续点进去:private CacheStrategy getCandidate() {        //标记1:没有缓存Response

if (cacheResponse == null) {            return new CacheStrategy(request, null);

}        //标记2

if (request.isHttps() && cacheResponse.handshake() == null) {            return new CacheStrategy(request, null);

}

...

CacheControl requestCaching = request.cacheControl();        //标记3

if (requestCaching.noCache() || hasConditions(request)) {            return new CacheStrategy(request, null);

}

CacheControl responseCaching = cacheResponse.cacheControl();        //标记4

if (responseCaching.immutable()) {            return new CacheStrategy(null, cacheResponse);

}

...        //标记5

if (!responseCaching.noCache() && ageMillis + minFreshMillis 

Response.Builder builder = cacheResponse.newBuilder();            if (ageMillis + minFreshMillis >= freshMillis) {

builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");

}            long oneDayMillis = 24 * 60 * 60 * 1000L;            if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {

builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");

}            return new CacheStrategy(null, builder.build());

}

...

}

标记1处,可以看到先对cacheResponse进行判断,如果为空,说明没有缓存对象,这时创建CacheStrategy对象并且第二个参数Response传入null。

标记2处,判断请求是否是https请求,如果是https请求但没有经过握手操作 ,创建CacheStrategy对象并且第二个参数Response传入null。

标记3处,判断如果不使用缓存或者服务端资源改变,亦或者验证服务端发过来的最后修改的时间戳,同样创建CacheStrategy对象并且第二个参数Response传入null。

标记4处,判断缓存是否受影响,如果不受影响,创建CacheStrategy对象时,第一个参数Request为null,第二个参数Response直接使用cacheResponse。

标记5处,根据一些信息添加头部信息 ,最后创建CacheStrategy对象。

回到CacheInterceptor的intercept方法:@Override public Response intercept(Chain chain) throws IOException {

...    if (networkRequest == null && cacheResponse == null) {      return new Response.Builder()

.request(chain.request())

.protocol(Protocol.HTTP_1_1)

.code(504)

.message("Unsatisfiable Request (only-if-cached)")

.body(Util.EMPTY_RESPONSE)

.sentRequestAtMillis(-1L)

.receivedResponseAtMillis(System.currentTimeMillis())

.build();

}

...

}

第三步,判断当前如果不能使用网络同时又没有找到缓存,这时会创建一个Response对象,code为504的错误。@Override public Response intercept(Chain chain) throws IOException {

...      if (networkRequest == null) {      return cacheResponse.newBuilder()

.cacheResponse(stripBody(cacheResponse))

.build();

}

...

}

第四步,如果当前不能使用网络,就直接返回缓存结果。@Override public Response intercept(Chain chain) throws IOException {

...

Response networkResponse = null;    try {

networkResponse = chain.proceed(networkRequest);

} finally {

...

}

...

}

第五步,调用下一个拦截器进行网络请求。@Override public Response intercept(Chain chain) throws IOException {

...    if (cacheResponse != null) {      if (networkResponse.code() == HTTP_NOT_MODIFIED) {

Response response = cacheResponse.newBuilder()

.headers(combine(cacheResponse.headers(), networkResponse.headers()))

.sentRequestAtMillis(networkResponse.sentRequestAtMillis())

.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())

.cacheResponse(stripBody(cacheResponse))

.networkResponse(stripBody(networkResponse))

.build();

networkResponse.body().close();

cache.trackConditionalCacheHit();

cache.update(cacheResponse, response);        return response;

} else {

closeQuietly(cacheResponse.body());

}

}

...

}

第六步,当通过下个拦截器获取Response后,判断当前如果有缓存Response,并且网络返回的Response的响应码为304,代表从缓存中获取。@Override public Response intercept(Chain chain) throws IOException {

...    if (cache != null) {      //标记1

if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {

CacheRequest cacheRequest = cache.put(response);        return cacheWritingResponse(cacheRequest, response);

}      //标记2

if (HttpMethod.invalidatesCache(networkRequest.method())) {        try {

cache.remove(networkRequest);

} catch (IOException ignored) {

}

}

}    return response;

}

第七步,标记1判断http头部有没有响应体,并且缓存策略可以被缓存的,满足这两个条件后,网络获取的Response通过cache的put方法写入到缓存中,这样下次取的时候就可以从缓存中获取;标记2处判断请求方法是否是无效的请求方法,如果是的话,从缓存池中删除这个Request。最后返回Response给上一个拦截器。

作者:顾林海

链接:https://www.jianshu.com/p/37b3badd7033

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值