Okhttp拦截器Interceptor学习和使用

前言

前年的这个时候我们项目将网络框架替换为okhttp+retrofit ,然后我对 retrofit 源码进行了学习和分享,写了几篇相关的文章同时更新了项目的网络框架。

Android网络之Retrofit2.0使用和解析
Retrofit2.0中注解使用套路
Retrofit2.0+Okhttp不依赖服务端的数据缓存

需求是推动任何事物向前发展的动力,这次我们项目需要对网络接口进行加密了,开发过程涉及到了okhttp的网路层的处理,所以我又将其源码翻了一番。

回顾一下我们曾经学习过的因特网五层协议栈:

  • 网络请求发出时:应用层->传输层->网络层->连接层->物理层
  • 收到响应后:物理层->连接层->网络层->传输层->应用层

这个很像我们这次要讲的 okhttp 中的interceptor 的责任链模式。

Interceptor

Interceptor的wiki

按照以往的惯例我们先上图,然后在对每个步骤进行详细的讲述。

okhttp-interceptors

为什么会有拦截器

我们在进行应用开发的时候都会在请求中增加一些我们应用需要和服务端交互的通用信息,比如在 header 中增加用户的登录态信息等等。或者像 Retrofit2.0+Okhttp不依赖服务端的数据缓存 这篇文章中不依赖服务端的缓存,在请求的过程中我们自己修改一些请求的 requestresponse

这个时候拦截器就是我们的强大的助力。

okhttp中的拦截器

我们从 okhttp 处理一条普通的url请求的代码执行过程中观察 interceptors 的工作。

下边的代码是从 okhttp官网 摘的一段示例代码:

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}

我们跟进 okhttpclient.newcall 方法:

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
    /***部分代码省略***/
    /**
     * Prepares the {@code request} to be executed at some point in the future.
     */
    @Override 
    public Call newCall(Request request) {
        return new RealCall(this, request, false /* for web socket */);
    }
}

获取一个真正用来进行请求的Call RealCall.execute :

final class RealCall implements Call {
    final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;
    RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        final EventListener.Factory eventListenerFactory = client.eventListenerFactory();
        this.client = client;
        this.originalRequest = originalRequest;
        this.forWebSocket = forWebSocket;
        this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
        // TODO(jwilson): this is unsafe publication and not threadsafe.
        this.eventListener = eventListenerFactory.create(this);
    }
    @Override 
    public Response execute() throws IOException {
        synchronized (this) {
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }
        captureCallStackTrace();
        try {
            //进行网络请求
            client.dispatcher().executed(this);
            //经过一层层网络拦截器之后,获取网络请求的返回值
            Response result = getResponseWithInterceptorChain();
            if (result == null) throw new IOException("Canceled");
            return result;
        } finally {
            client.dispatcher().finished(this);
        }
      }
      Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        List<Interceptor> interceptors = new ArrayList<>();
        //Application拦截器
        interceptors.addAll(client.interceptors());
        //重定向和失败后重新请求拦截器
        interceptors.add(retryAndFollowUpInterceptor);
        //网桥拦截器,顾名思义client和Server之前的桥梁
        interceptors.add(new BridgeInterceptor(client.cookieJar()));
        //缓存处理拦截器
        interceptors.add(new CacheInterceptor(client.internalCache()));
        //Socket层的握手链接
        interceptors.add(new ConnectInterceptor(client));
        if (!forWebSocket) {
            //网络烂拦截器
            interceptors.addAll(client.networkInterceptors());
        }
        //client和Server之前的读写操作
        interceptors.add(new CallServerInterceptor(forWebSocket));
        //责任链开始执行
        Interceptor.Chain chain = new RealInterceptorChain(
            interceptors, null, null, null, 0, originalRequest);
        return chain.proceed(originalRequest);
      }
}

Interceptor介绍

我们先看拦截器的接口定义:

public interface Interceptor {
    //拦截处理
    Response intercept(Chain chain) throws IOException;
    interface Chain {
        //获取请求的request
        Request request();
        //处理request获取response
        Response proceed(Request request) throws IOException;
    
        /**
         * Returns the connection the request will be executed on. This is only available in the chains
         * of network interceptors; for application interceptors this is always null.
         */
        @Nullable Connection connection();
    }
}

在我们跟踪源码的执行的过程我们回忆下最开始的时候的流程图,涉及到的拦截器以及他们各自的位置和在网络请求的作用。

Application Interceptor

我们可以自定义设置 Okhttp 的拦截器之一。

从流程图中我们可以看到一次网络请求它只会执行一次拦截,而且它是第一个触发拦截的,这里拦截到的url请求的信息都是最原始的信息。所以我们可以在该拦截器中添加一些我们请求中需要的通用信息,打印一些我们需要的日志。

当然我们可以定义多个这样的拦截器,一个处理 header 信息,一个处理 接口请求的 加解密

NetwrokInterceptor

NetwrokInterceptor 也是我们可以自定义的拦截器之一。

它位于倒数第二层,会经过 RetryAndFollowIntercptor 进行重定向并且也会通过 BridgeInterceptor 进行 request请求头和 响应 resposne 的处理,因此这里可以得到的是更多的信息。在打印结果可以看到它内部重定向操作和失败重试,这里会有比 Application Interceptor 更多的日志。

RetryAndFollowInterceptor

RetryAndFollowUpInterceptor 的作用,看到该拦截器的名称就知道,它就是一个负责失败重连的拦截器。它是 Okhttp 内置的第一个拦截器,通过 while (true) 的死循环来进行对异常结果或者响应结果判断是否要进行重新请求。

BridgeInterceptor

BridgeInterceptor 为用户构建的一个 Request 请求转化为能够进行网络访问的请求,同时将网络请求回来的响应 Response 转化为用户可用的 Response。比如,涉及的网络文件的类型和网页的编码,返回的数据的解压处理等等。

CacheInterceptor

CacheInterceptor 根据 OkHttpClient 对象的配置以及缓存策略对请求值进行缓存。

ConnectInterceptor

ConnectInterceptor 在 OKHTTP 底层是通过 SOCKET 的方式于服务端进行连接的,并且在连接建立之后会通过 OKIO 获取通向 server 端的输入流 Source 和输出流 Sink。

CallServerInterceptor

CallServerInterceptorConnectInterceptor 拦截器的功能就是负责与服务器建立 Socket 连接,并且创建了一个 HttpStream 它包括通向服务器的输入流和输出流。而接下来的 CallServerInterceptor 拦截器的功能使用 HttpStream 与服务器进行数据的读写操作的。

有关每个拦截器的具体代码分析:https://www.jianshu.com/u/8173f323f5bb

拦截器中的骚操作

HttpUrl httpurl = request.url();
//获取request中的原始url地址
String requestUrl = httpurl.url().toString();
/**
 * 获取url中的参数
 * @param httpUrl
 * @return
 */
private Map<String, String> getHttpUrlParams(HttpUrl httpUrl) {
    Set<String> paramsSet = httpUrl.queryParameterNames();
    Map<String, String> paramMap = new HashMap<>();
    if (paramsSet != null && paramsSet.size() > 0) {
        for (String s: paramsSet) {
            paramMap.put(s, httpUrl.queryParameter(s));
        }
    }
    return paramMap;
}
//一般POST请求参数都是放在RequestBody中,使用时需要判断RequestBody是否为子类FormBody的实例
RequestBody requestBody = request.body();
/**
 * 获取请求form中的参数
 * @param formBody
 * @return
 */
private Map<String, String> getHttpUrlParams(FormBody formBody) {
    Map<String, String> paramMap = new HashMap<>();
    if (formBody != null) {
        for (int i = 0; i < formBody.size(); i++) {
            paramMap.put(formBody.name(i), formBody.value(i));
        }
    }
    return paramMap;
}
//构造新的POST表单
FormBody.Builder formBuilder = new FormBody.Builder();
for (String key: map.keySet()) {
    formBuilder.add(key, map.get(key));
}
//构造新的HttpUrl,动态修改请求url地址
HttpUrl httpUrl = HttpUrl.parse(requestUrlTrue);
//构造新的request请求
request = request.newBuilder()
        .method(POST, formBuilder.build())
        .url(httpUrl)
        .build();
//获取相应体对应的请求体,请求和返回一一对应
Request request = response.request()
//获取请求的相应体
ResponseBody responseBody = response.body();
//获取返回值类型
MediaType mediaType = responseBody.contentType();
//获取相应体重的数据流,只能获取一次,在获取之后数据流会关闭,再次获取会有异常抛出
byte[] responseBytes = responseBytes = responseBody.bytes();
//利用修改后的返回值,构造新的相应体
response = response.newBuilder()
        .body(ResponseBody.create(mediaType, responseBytes))
        .build();

文章到这里就全部讲述完啦,若有其他需要交流的可以留言哦

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容