OkHttp拦截器链源码解读

同步异步源码解读的传送门https://www.jianshu.com/p/baf47f6f0e11
OkHttp网络请求的重中之重拦截器链,在官方文档中是这样解释的:
拦截器是OkHttp中提供的一种强大机制,它可以实现网络监听、请求以及响应重写、请求失败重试等功能。

PS:拦截器是不区分同步和异步的
下面这是官网的图:

image.png

一种是系统应用的拦截器,一种是网络拦截器,这里我们只谈OkHttp内部系统的拦截器,总共分为5种。如下图所示:


image.png

我们再回到RealCall这个类中,看看同步和异步最终执行的方法:
同步:

@Override public Response execute() throws IOException {
        synchronized (this) { // 加同步锁
            // 同一个http请求只能执行一次,执行完就设置为你true
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }
        captureCallStackTrace();
        timeout.enter();
        eventListener.callStart(this);
        try {
            client.dispatcher().executed(this);
            Response result = getResponseWithInterceptorChain();
            if (result == null) throw new IOException("Canceled");
            return result;
        } catch (IOException e) {
            e = timeoutExit(e);
            eventListener.callFailed(this, e);
            throw e;
        } finally {
            client.dispatcher().finished(this);
        }
    }

异步:

@Override public void enqueue(Callback responseCallback) {
        synchronized (this) {
            // 和同步一样判断是否请求过这个链接,已经请求过就直接抛出异常
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true; // 请求过就设置为true
        }
        captureCallStackTrace();
        eventListener.callStart(this);
        client.dispatcher().enqueue(new AsyncCall(responseCallback)); // 关键代码
    }

异步线程真实执行的方法:
// 这个方法才是线程真正实现的方法,在父类NamedRunnable中是一个抽象的方法,在线程的run方法中实现的
        @Override protected void execute() { // 这个方法是在子线程中操作的
            boolean signalledCallback = false;
            timeout.enter();
            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) {
                e = timeoutExit(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 {
                /**
                 * 这个finish做的事情
                 * 1.把这个请求从正在请求的队列中删除
                 * 2.调整我们整个异步请求的队列,因为这个队列是非线程安全的
                 * 3.重新调整异步请求的数量
                 */
                client.dispatcher().finished(this);
            }
        }
    }

从上面看出,不管是同步还是异步都使用了拦截器链,返回Response 对象

下面我们来看看getResponseWithInterceptorChain()这个方法:

// 所使用的拦截器链 在官网上分为两个拦截器 Application 应用拦截器和NetWork网络拦截器
    Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        List<Interceptor> interceptors = new ArrayList<>();
        interceptors.addAll(client.interceptors()); // 添加一个用户自定义的拦截器
        interceptors.add(retryAndFollowUpInterceptor);
        interceptors.add(new BridgeInterceptor(client.cookieJar()));
        interceptors.add(new CacheInterceptor(client.internalCache()));
        interceptors.add(new ConnectInterceptor(client));
        if (!forWebSocket) {
            interceptors.addAll(client.networkInterceptors());
        }
        interceptors.add(new CallServerInterceptor(forWebSocket));

        Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
                originalRequest, this, eventListener, client.connectTimeoutMillis(),
                client.readTimeoutMillis(), client.writeTimeoutMillis());

        return chain.proceed(originalRequest); // 拦截后调用proceed方法来执行相应请求
    }

从代码中可以看出,是一个List集合依次添加用户自定义的拦截器和OkHttp内部的一系列拦截器,到最后通过RealInterceptorChain对象创建出一个拦截器链Interceptor.Chain来,拦截器链执行chain.proceed方法。我们跟着这个方法走进去到RealInterceptorChain类中,对应的方法:

@Override 
public Response proceed(Request request) throws IOException {
        return proceed(request, streamAllocation, httpCodec, connection);
    }

最终到这个方法中:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
                            RealConnection connection) throws IOException {
        if (index >= interceptors.size()) throw new AssertionError();

        calls++;

        // If we already have a stream, confirm that the incoming request will use it.
        if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
            throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
                    + " must retain the same host and port");
        }

        // If we already have a stream, confirm that this is the only call to chain.proceed().
        if (this.httpCodec != null && calls > 1) {
            throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
                    + " must call proceed() exactly once");
        }

        // Call the next interceptor in the chain. 核心方法实现 就是创建下一个拦截器链
        /**
         * 创建了一个拦截器的链,这个链和刚才前面讲的链的区别 ,传入的参数是index + 1,意思就是如果我们下面
         * 要访问的话,只能从下一个拦截器访问,不能从当前拦截器访问,这就形成了一个拦截器链
         */
        RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
                connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
                writeTimeout);
        Interceptor interceptor = interceptors.get(index); // 根据索引获取拦截器
        Response response = interceptor.intercept(next);  // 再把上面获取到的拦截器链,当做参数传进去,这样就形成了所有拦截器的链条

        // Confirm that the next interceptor made its required call to chain.proceed().
        if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
            throw new IllegalStateException("network interceptor " + interceptor
                    + " must call proceed() exactly once");
        }

        // Confirm that the intercepted response isn't null.
        if (response == null) {
            throw new NullPointerException("interceptor " + interceptor + " returned null");
        }

        if (response.body() == null) {
            throw new IllegalStateException(
                    "interceptor " + interceptor + " returned a response with no body");
        }

        return response;
    }

这里面的核心在上面代码中已经写明,这里就不在阐述了。
所有的拦截器都实现了Interceptor这个接口:

/**
 * Observes, modifies, and potentially short-circuits requests going out and the corresponding
 * responses coming back in. Typically interceptors add, remove, or transform headers on the request
 * or response.
 */
public interface Interceptor {
    Response intercept(Chain chain) throws IOException;

    // 也就是说我们可以通过实现Interceptor,定义一个拦截器对象,然后拿到请求和Response对象,对Request和Response进行修改
    interface Chain {
        // 实现chain接口对象的request方法可以拿到Request对象
        okhttp3.Request request();

        // 实现chain接口对象的proceed方法可以拿到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();

        Call call();

        int connectTimeoutMillis();

        Chain withConnectTimeout(int timeout, TimeUnit unit);

        int readTimeoutMillis();

        Chain withReadTimeout(int timeout, TimeUnit unit);

        int writeTimeoutMillis();

        Chain withWriteTimeout(int timeout, TimeUnit unit);
    }
}

其实整个OkHttpde 请求就是经过这一个个的拦截器链的chain.proceed方法完成的。

我们来简单做个总结:
1.创建一系列拦截器,并将其放入一个拦截器list中。
2.创建一个拦截器链RealInterceptorChain,并执行拦截器链的proceed方法。
3.在发起请求前对request进行处理。
4.调用下一个拦截器,获取Response。
5.对Response进行处理,返回给上一个拦截器。
我们这里完全可以自定义拦截器,实现Interceptor,生成一个拦截器对象,然后拿到请求Request对象和响应Response对象,对Request和Response进行修改。

整个流程就是OkHttp通过定义许多拦截器一步一步地对Request进行拦截处理(从头至尾),直到请求返回网络数据,后面又倒过来,一步一步地对Response进行拦截处理,最后拦截的结果就是回调给调用者的最终Response。(从尾至头)
上一张图很清晰的理清拦截器的操作:


image.png

这篇先到这里,下一篇我们把OkHttp中的这5个拦截器一一细细分析。
感谢观看,如有错误,欢迎指正。

OkHttp的重定向及重试拦截器的源码解读:https://www.jianshu.com/p/d70f07288afc

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

推荐阅读更多精彩内容