精华内容
下载资源
问答
  • rxjava,okhttp 合并请求

    千次阅读 2017-05-22 17:05:05
    项目中我们往往会有这样的需求,一个页面,可能存在多个请求,并且每个请求返回的是不同的数据,当用户...我们现在用到了rxjava 那么实现这个请求合并就很简单了具体实现:guithub项目地址https://github.com/wj576...

    项目中我们往往会有这样的需求,一个页面,可能存在多个请求,并且每个请求返回的是不同的数据,当用户进入页面的时候,我们需要向服务器请求两次,当第一个请求成功之后,我们紧接着去请求第二个,并且两个请求同时成功之后,请求才真正成功,有一个请求失败,就提示用户请求失败。我们现在用到了rxjava 那么实现这个请求合并就很简单了


    具体实现:

    guithub项目地址https://github.com/wj576038874/mvp-rxjava-retrofit-okhttp

    Observable<List<Topic>> observable1 = OkHttpUtils.getRetrofit().create(ApiService.class).loadTopicList(1);
            Observable<List<New>> observable2 = OkHttpUtils.getRetrofit().create(ApiService.class).loadNewsList(1);
    
            Observable<TopicsAndNews> observable = Observable.zip(observable1, observable2, new Func2<List<Topic>, List<New>, TopicsAndNews>() {
                @Override
                public TopicsAndNews call(List<Topic> topics, List<New> news) {
                    TopicsAndNews topicsAndNews = new TopicsAndNews();
                    topicsAndNews.setNews(news);
                    topicsAndNews.setTopics(topics);
                    return topicsAndNews;
                }
            });
    
            Subscriber<TopicsAndNews> subscriber = new DialogSubscriber<TopicsAndNews>(dialog) {
                @Override
                protected void onSuccess(TopicsAndNews topicsAndNews) {
                    onLoadDatasListener.onSuccess(topicsAndNews);
                }
    
                @Override
                protected void onFailure(String msg) {
                    onLoadDatasListener.onFailure(msg);
                }
            };
    
            observable.subscribeOn(Schedulers.io())
                    .unsubscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(subscriber);

    展开全文
  • okhttp 同步请求和异步请求

    万次阅读 2017-03-31 22:14:52
    OkHttp发送请求后,可以通过同步或异步地方式获取响应。下面就同步和异步两种方式进行介绍。 1.1、同步方式 发送请求后,就会进入阻塞状态,知道收到响应。下面看一个下载百度首页的例子: OkHttpClient client...

    一、使用OkHttp

    OkHttp发送请求后,可以通过同步或异步地方式获取响应。下面就同步和异步两种方式进行介绍。

    1.1、同步方式

    发送请求后,就会进入阻塞状态,知道收到响应。下面看一个下载百度首页的例子:

    OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
            Request request = new Request.Builder().url("http://www.baidu.com")
                    .get().build();
            Call call = client.newCall(request);
            try {
                Response response = call.execute();
                System.out.println(response.body().string());
            } catch (IOException e) {
                e.printStackTrace();
            }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    上面的代码先创建OkHttpClient和Request对象,两者均使用了Builder模式;然后将Request封装成Call对象,然后调用Call的execute()同步发送请求,最后打印响应。

    1.2、异步方式

    异步方式是在回调中处理响应的,同样看下载百度首页的例子:

     OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
            Request request = new Request.Builder().url("http://www.baidu.com")
                    .get().build();
            Call call = client.newCall(request);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    System.out.println("Fail");
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
    
                    System.out.println(response.body().string());
    
                }
            });
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    同样是创建OkHttpClient、Request和Call,只是调用了enqueue方法并在回调中处理响应。 
    上面介绍了同步、异步获取请求的步骤,都是比较简单的。

    1.3、Request、Response、Call

    上面的代码中涉及到几个常用的类:Request、Response和Call。下面分别介绍: 
    Request 
    每一个HTTP请求包含一个URL、一个方法(GET或POST或其他)、一些HTTP头。请求还可能包含一个特定内容类型的数据类的主体部分。 
    Response 
    响应是对请求的回复,包含状态码、HTTP头和主体部分。 
    重写请求 
    当将Request提交给OkHttp后,出于正确性和效率的考虑,OkHttp在传输请求之前会重写请求。 
    OkHttp可能会在请求中添加缺少的请求头,包括”Content-Length”,”Transfer-Encoding”,”User-Agent”,”HOST”,”Connection”和”Content-Type”等。 
    有些请求可能有缓存的响应。当缓存响应过时时,OkHttp可以做一个额外的GET请求获取最新的响应。这要求”If-Modified-Since”和”If-None-Match”头被添加。 
    重写响应 
    如果使用了透明压缩,OkHttp会丢弃”Content-Encoding”和”Content-Length”头,因为和解压后的响应主体不匹配。 
    如果一个额外的GET请求成功了,那么网络和缓存中的响应将会合并。 
    请求重定向 
    当请求的URL移动了,web服务器会返回一个302的状态码并指明文件的新地址。OkHttp将会重定向获取最终的响应。 
    请求重试 
    有时连接会失败,那么OkHttp会重试别的路由。 
    Call 
    当重写、重定向等时,一个请求可能会产生多个请求和响应。OkHttp使用Call抽象出一个满足请求的模型,尽管中间可能会有多个请求或响应。执行Call有两种方式,同步或异步,这在上面已经介绍过了。 
    Call可以在任何线程被取消。

    二、拦截器

    拦截器是一个监视、重写、重试请求的强有力机制。拦截器可以串联。 
    拦截器原理 
    从图中可以看出,拦截器分为应用拦截器和网络拦截器两种。应用拦截器是在发送请求之前和获取到响应之后进行操作的,网络拦截器是在进行网络获取前进行操作的。

    2.1、应用拦截器

    下面定义一个应用拦截器,用于在请求发送前打印URL以及接受到响应后打印内容。

    public class LogInterceptor implements Interceptor {
    
        @Override
        public Response intercept(Chain chain) throws IOException {
    
            Request request = chain.request();
    
            System.out.println(request.toString());
    
            Response response = chain.proceed(request);
    
            System.out.println(response);
    
            return response;
        }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    上面的代码中,LogInterceptor实现了Interceptor接口。首先从chain中得到请求,然后打印请求;然后调用proceed方法处理请求得到响应,然后打印响应。调用代码如下:

     OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new LogInterceptor()).build();
            Request request = new Request.Builder().url("http://www.baidu.com")
                    .get().build();
            Call call = okHttpClient.newCall(request);
            try {
                call.execute();
            } catch (IOException e) {
                e.printStackTrace();
            }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    可以看到通过调用addInterceptor方法添加应用拦截器。

    2.2、网络拦截器

    网络拦截器的使用和应用拦截器类似,只是调用OkHttpClient的addNetworkInterceptor方法即可。

    OkHttpClient okHttpClient = new OkHttpClient.Builder().addNetworkInterceptor(new LogInterceptor()).build();
            Request request = new Request.Builder().url("http://www.taobao.com")
                    .get().build();
            Call call = okHttpClient.newCall(request);
            try {
                call.execute();
            } catch (IOException e) {
                e.printStackTrace();
            }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    下面是运行结果:

    Request{method=GET, url=http://www.taobao.com/, tag=Request{method=GET, url=http://www.taobao.com/, tag=null}}
    Response{protocol=http/1.1, code=302, message=Found, url=http://www.taobao.com/}
    Request{method=GET, url=https://www.taobao.com/, tag=Request{method=GET, url=http://www.taobao.com/, tag=null}}
    Response{protocol=http/1.1, code=200, message=OK, url=https://www.taobao.com/}
     
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4

    可以发现,拦截器运行了两次。一次是初始请求”http://www.taobao.com“,一次是请求重定向”https://www.taobao.com“。

    2.3、应用拦截器和网络拦截器的比较

    每个拦截器由它各自的优势。 
    应用拦截器 
    - 不需要考虑中间状态的响应,比如重定向或者重试。 
    - 只会被调用一次,甚至于HTTP响应保存在缓存中。 
    - 观察应用程序的原意。 
    - 允许短路,可以不调用Chain.proceed()方法 
    - 允许重试和发送多条请求,调用Chain.proceed()方法 
    网络拦截器 
    - 可以操作中间状态的响应,比如重定向和重试 
    - 不调用缓存的响应 
    - 可以观察整个网络上传输的数据 
    - 获得携带请求的Connection

    2.4、重写请求

    拦截器可以添加、移除或者替换请求的头信息,也可以改变传输的主体部分。下面的一个拦截器对请求主体进行Gzip压缩。

    final class GzipRequestInterceptor implements Interceptor {
      @Override public Response intercept(Interceptor.Chain chain) throws IOException {
        Request originalRequest = chain.request();
        if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
          return chain.proceed(originalRequest);
        }
    
        Request compressedRequest = originalRequest.newBuilder()
            .header("Content-Encoding", "gzip")
            .method(originalRequest.method(), gzip(originalRequest.body()))
            .build();
        return chain.proceed(compressedRequest);
      }
    
      private RequestBody gzip(final RequestBody body) {
        return new RequestBody() {
          @Override public MediaType contentType() {
            return body.contentType();
          }
    
          @Override public long contentLength() {
            return -1; // We don't know the compressed length in advance!
          }
    
          @Override public void writeTo(BufferedSink sink) throws IOException {
            BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
            body.writeTo(gzipSink);
            gzipSink.close();
          }
        };
      }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    2.5、重写响应

    同样地,拦截器可以重写响应的头部以及主体部分。但是

    /** Dangerous interceptor that rewrites the server's cache-control header. */
    private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
      @Override public Response intercept(Interceptor.Chain chain) throws IOException {
        Response originalResponse = chain.proceed(chain.request());
        return originalResponse.newBuilder()
            .header("Cache-Control", "max-age=60")
            .build();
      }
    };
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    备注:        

    备注:
    • 同步交互:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程;

    • 异步交互:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。 

    • 区别:一个需要等待,一个不需要等待,在部分情况下,我们的项目开发中都会优先选择不需要等待的异步交互方式。

    • 哪些情况建议使用同步交互呢?比如银行的转账系统,对数据库的保存操作等等,都会使用同步交互操作,其余情况都优先使用异步交互。

    三、总结

    本篇文章主要介绍了OkHttp进行GET的同步、异步请求,对于HTTP其他方法,比如POST等都是可以进行的,这儿就不过多介绍了,想了解的朋友可以到OkHttp Github地址查看.

    展开全文
  • OkHttp网络请求流程小结 文章目录OkHttp网络请求流程小结前言简单使用源码分析创建Call对象并加入线程池处理通过拦截器发送请求缓存策略HTTP的缓存OkHttp的缓存策略失败重连请求流程图参考资料 前言 本文基于OkHttp ...

    OkHttp网络请求流程小结

    前言

    本文基于OkHttp 4.7.2,下面的源码都是kotlin,我看了看OkHttp的github,发现最新4.8.0版本kotlin和java五五开,所以还不会kotlin的Android程序猿还不赶快学起来~

    简单使用

    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder()
          .url(url)
          .build();
    Response response = client.newCall(request).enqueue(
          object : Callback {
              override fun onFailure(call: Call, e: IOException) {
                  ...
              }
    
              override fun onResponse(call: Call, response: Response) {
                  ...
              }
          })
    

    关键就是这一行:

    client.newCall(request).enqueue()
    

    其实就是两个方法:

    OkHttpClient的newCall() 和 RealCall的execute()/enqueue()方法(execute()/enqueue()分别为同步和异步请求)

    源码分析

    以下源码主要分析异步请求,因为它的步骤比同步请求多了创建并加入线程池这一步,但两者之后的步骤都是一样的,所以不再分析同步请求的源码。

    创建Call对象并加入线程池处理

    OkHttpClient.newCall()

    可以看到,newCall方法直接创建了一个RealCall对象

    override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
    

    RealCall.enqueue()

    override fun enqueue(responseCallback: Callback) {
        synchronized(this) {
            check(!executed) { "Already Executed" }	//如果executed为true则抛出异常
            executed = true
        }
        callStart()
        client.dispatcher.enqueue(AsyncCall(responseCallback))
    }
    

    在创建OkHttpClient对象时,会通过它的内部类Builder来创建,其中创建了Dispatcher对象。因此这里可以通过client.dispatcher直接获取它持有的dispatcher对象。

    Dispatcher任务调度类

    Dispatcher用于控制并发请求,下面是其中一些主要的属性(省略了set和get方法)

    ArrayDeque即双端队列,它可以从两端对元素进行增删,其中存储元素的数据结构为数组。

    var maxRequests = 64			//最大并发请求数
    
    var maxRequestsPerHost = 5		//每个主机的最大请求数
    
    val executorService: ExecutorService	//消费者线程池
    
    private val readyAsyncCalls = ArrayDeque<AsyncCall>()	//将要运行的异步请求队列
    
    private val runningAsyncCalls = ArrayDeque<AsyncCall>()	//正在运行的异步请求队列
    
    private val runningSyncCalls = ArrayDeque<RealCall>()	//正在运行的同步请求队列
    

    构造方法

    class Dispatcher constructor()		//默认构造,会在网络请求前创建默认线程池
    
    constructor(executorService: ExecutorService) : this() {	//有参构造方法需要传入一个线程池对象
        this.executorServiceOrNull = executorService
    }
    

    RealCall.enqueue(responseCallback: Callback)

    又调用回dispatcher的enqueue方法

    override fun enqueue(responseCallback: Callback) {
        synchronized(this) {
            check(!executed) { "Already Executed" }
            executed = true
        }
        callStart()
        client.dispatcher.enqueue(AsyncCall(responseCallback))
    }
    

    Dispatcher.enqueue(call: AsyncCall)

    AsyncCall是RealCall的内部类,它是一个Runnable对象

    internal fun enqueue(call: AsyncCall) {
      synchronized(this) {
        readyAsyncCalls.add(call)	//直接将任务加入待运行异步任务队列末尾
    
        //下面是为了共享同一主机正在运行的调用的AtomicInteger
        if (!call.call.forWebSocket) {
          val existingCall = findExistingCallWithHost(call.host)
          if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
        }
      }
      promoteAndExecute()	//这个方法会将待运行队列中符合要求的任务跃迁至正在运行队列,并将它们加入线程池运行
    }
    

    Dispatcher.promoteAndExecute()

    这个函数将“readyAsyncCalls队列中符合要求的任务取出并加入到runningAsyncCalls队列”这个操作称为“跃迁”(promote)

    private fun promoteAndExecute(): Boolean {
        this.assertThreadDoesntHoldLock()
    
        val executableCalls = mutableListOf<AsyncCall>()
        val isRunning: Boolean
        synchronized(this) {
            val i = readyAsyncCalls.iterator()
            //while循环遍历待执行异步队列
            while (i.hasNext()) {
                val asyncCall = i.next()
    
                if (runningAsyncCalls.size >= this.maxRequests) break //当前并发的请求数量大于最大并发量64时,就跳出循环,停止跃迁,将之前跃迁的任务放入线程池执行
                if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue //主机最大并数量大于主机最大并发量5,会跳过当前异步调用,继续遍历待执行异步队列
    
                i.remove()		//通过迭代器移除刚才迭代器返回的待执行队列中的任务
                asyncCall.callsPerHost.incrementAndGet()  //更新每个请求的主机并发数量
                executableCalls.add(asyncCall)		//将跃迁的任务加入executableCalls列表
                runningAsyncCalls.add(asyncCall)	//将跃迁的任务加入runningAsyncCalls队列
            }
            isRunning = runningCallsCount() > 0
        }
    
        //遍历上面跃迁的所有任务,并加入线程池执行
        for (i in 0 until executableCalls.size) {
            val asyncCall = executableCalls[i]
            asyncCall.executeOn(executorService)
        }
    
        return isRunning
    }
    

    AsyncCall.executeOn(executorService: ExecutorService)

    fun executeOn(executorService: ExecutorService) {
      client.dispatcher.assertThreadDoesntHoldLock()
    
      var success = false
      try {
        executorService.execute(this)	//在线程池中执行AsyncCall的run方法,这个线程池类似于CachedExecutorService,只有“无限”多个非核心线程
        success = true
      } catch (e: RejectedExecutionException) {
        ...
      } finally {
        if (!success) {
          client.dispatcher.finished(this) //最终总会调用Dispatcher的finish方法
        }
      }
    }
    

    dispatcher.finished(this)

    internal fun finished(call: AsyncCall) {
        call.callsPerHost.decrementAndGet()
        finished(runningAsyncCalls, call)
    }
    
    private fun <T> finished(calls: Deque<T>, call: T) {
        val idleCallback: Runnable?
        synchronized(this) {
            //将执行完的任务移除runningAsyncCalls队列
            if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
            idleCallback = this.idleCallback
        }
    
        val isRunning = promoteAndExecute()		//执行完继续调用promoteAndExecute方法取出并执行后面的待执行请求
    
        if (!isRunning && idleCallback != null) {
            idleCallback.run()
        }
    }
    

    AsyncCall.run()

    上面AsyncCall的executeOn方法中将AsyncCall对象加入到线程池并执行了下面的run方法:

    override fun run() {
        threadName("OkHttp ${redactedUrl()}") {
            var signalledCallback = false
            timeout.enter()
            try {
                val response = getResponseWithInterceptorChain()	//请求网络,获取数据
                signalledCallback = true
                responseCallback.onResponse(this@RealCall, response)	//回调接口的onResponse方法,并将response作为参数传入
            } catch (e: IOException) {
                ...
            } catch (t: Throwable) {
                ...
            } finally {
                client.dispatcher.finished(this)
            }
        }
    }
    

    通过拦截器发送请求

    RealCall.getResponseWithInterceptorChain()

    internal fun getResponseWithInterceptorChain(): Response {
        //构建一个完整的拦截器栈
        val interceptors = mutableListOf<Interceptor>()
        interceptors += client.interceptors
        interceptors += RetryAndFollowUpInterceptor(client)
        interceptors += BridgeInterceptor(client.cookieJar)
        interceptors += CacheInterceptor(client.cache)
        interceptors += ConnectInterceptor
        if (!forWebSocket) {
            interceptors += client.networkInterceptors
        }
        interceptors += CallServerInterceptor(forWebSocket)
    
        //创建RealInterceptorChain拦截器链,其中封装了一些请求的基本信息
        val chain = RealInterceptorChain(
            call = this,
            interceptors = interceptors,	//将上面创建的interceptors列表传入
            index = 0,
            exchange = null,
            request = originalRequest,
            connectTimeoutMillis = client.connectTimeoutMillis,
            readTimeoutMillis = client.readTimeoutMillis,
            writeTimeoutMillis = client.writeTimeoutMillis
        )
    
        var calledNoMoreExchanges = false
        try {
            val response = chain.proceed(originalRequest)	//调用proceed方法获取数据
            if (isCanceled()) {
                response.closeQuietly()
                throw IOException("Canceled")
            }
            return response
        } catch (e: IOException) {
            calledNoMoreExchanges = true
            throw noMoreExchanges(e) as Throwable
        } finally {
            if (!calledNoMoreExchanges) {
                noMoreExchanges(null)
            }
        }
    }
    

    RealInterceptorChain.proceed(request: Request)

    override fun proceed(request: Request): Response {
        check(index < interceptors.size)
    
        calls++
        
    	...
    
        //实例化拦截器链中下一个拦截器,其实就是又创建了一个RealInterceptorChain对象,但更改了两个参数
        val next = copy(index = index + 1, request = request)
        //获取列表中当前的拦截器
        val interceptor = interceptors[index]	//interceptors就是上面getResponseWithInterceptorChain函数中创建RealInterceptorChain时传入的列表
    
        @Suppress("USELESS_ELVIS")
        val response = interceptor.intercept(next) ?: throw NullPointerException(
            "interceptor $interceptor returned null")	//看这里,当前拦截器的intercept方法中传入的是下一个拦截器next
    	...
    
        check(response.body != null) { "interceptor $interceptor returned a response with no body" }
    
        return response
    }
    

    上面 val response = interceptor.intercept(next) 这行代码intercept方法中又会调用传入的下一个拦截器next,这就实现了一个调用链:

    在这里插入图片描述

    它们各有各的作用,职责分明。它们的作用分别如下:

    1. RetryAndFollowUpInterceptor:取消、失败重试、重定向
    2. BridgeInterceptor:把用户请求转换为 HTTP 请求;把 HTTP 响应转换为用户友好的响应
    3. CacheInterceptor:读写缓存、根据策略决定是否使用
    4. ConnectInterceptor:实现和服务器建立连接
    5. CallServerInterceptor:实现读写数据

    最终的网络请求是在CallServerInterceptor的intercept方法中的,其中通过Exchange完成网络请求,它的实现类之一是Http1ExchangeCodec,请求和获取数据是通过I/O流向Socket对象写入或读取实现的。

    缓存策略

    HTTP的缓存

    HTTP协议的缓存

    OkHttp的缓存策略

    上面请求流程中,中间会经过CacheInterceptor进行拦截。

    OkHttp的缓存策略可以从它的CacheInterceptor的intercept方法看出:

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val call = chain.call()
        val cacheCandidate = cache?.get(chain.request())	//获取待使用缓存,是一个DiskLruCache缓存,
    
        val now = System.currentTimeMillis()
    
        val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()	//获取缓存策略
        val networkRequest = strategy.networkRequest
        val cacheResponse = strategy.cacheResponse
    
        cache?.trackResponse(strategy)
        val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE
    
        //缓存不可用,直接关闭
        if (cacheCandidate != null && cacheResponse == null) {
            // The cache candidate wasn't applicable. Close it.
            cacheCandidate.body?.closeQuietly()
        }
    
        //不进行网络请求,并且没有缓存或缓存已过期,则返回504 Unsatisfiable Request错误
        if (networkRequest == null && cacheResponse == null) {
            return Response.Builder()
            .request(chain.request())
            .protocol(Protocol.HTTP_1_1)
            .code(HTTP_GATEWAY_TIMEOUT)
            .message("Unsatisfiable Request (only-if-cached)")
            .body(EMPTY_RESPONSE)
            .sentRequestAtMillis(-1L)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build().also {
                listener.satisfactionFailure(call, it)
            }
        }
    
        //不进行网络请求而缓存可用,则使用缓存
        if (networkRequest == null) {
            return cacheResponse!!.newBuilder()
            .cacheResponse(stripBody(cacheResponse))
            .build().also {
                listener.cacheHit(call, it)
            }
        }
    
        if (cacheResponse != null) {
            listener.cacheConditionalHit(call, cacheResponse)
        } else if (cache != null) {
            listener.cacheMiss(call)
        }
    
        var networkResponse: Response? = null
        try {
            networkResponse = chain.proceed(networkRequest)	//进行网络请求
        } finally {
            // If we're crashing on I/O or otherwise, don't leak the cache body.
            if (networkResponse == null && cacheCandidate != null) {
                cacheCandidate.body?.closeQuietly()
            }
        }
    
        //有缓存,并且响应码为304: Not Modified,则将缓存和请求来的响应进行合并
        if (cacheResponse != null) {
            if (networkResponse?.code == HTTP_NOT_MODIFIED) {
                val 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()
    
                // Update the cache after combining headers but before stripping the
                // Content-Encoding header (as performed by initContentStream()).
                cache!!.trackConditionalCacheHit()
                cache.update(cacheResponse, response)
                return response.also {
                    listener.cacheHit(call, it)
                }
            } else {
                cacheResponse.body?.closeQuietly()
            }
        }
    
        val response = networkResponse!!.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build()
    
        //缓存策略允许缓存并且响应体不为空,就将请求来的数据存入缓存
        if (cache != null) {
            if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
                // Offer this request to the cache.
                val cacheRequest = cache.put(response)
                return cacheWritingResponse(cacheRequest, response).also {
                    if (cacheResponse != null) {
                        // This will log a conditional cache miss only.
                        listener.cacheMiss(call)
                    }
                }
            }
    
            if (HttpMethod.invalidatesCache(networkRequest.method)) {
                try {
                    cache.remove(networkRequest)
                } catch (_: IOException) {
                    // The cache cannot be written.
                }
            }
        }
    
        return response
    }
    

    CacheInterceptor执行流程小结

    1. 如果网络不可用并且无可用的有效缓存,则返回504错误
    2. 如果禁止了网络请求,则直接使用缓存
    3. 如果没有缓存且网络请求可用,则进行网络请求
    4. 如果此时有缓存,并且网络请求返回的响应码为 304: Not Modified,说明缓存还是有效的,则合并网络响应和缓存结果,并更新缓存
    5. 如果没有缓存,则将请求回来的结果写入新的缓存中;

    CacheStrategy.Factory(…).compute()

    我们从 cache 拿到缓存响应后,还需要做这几件事:

    1. 看当前调用方允不允许使用缓存(判断 request.cacheControl() 的值)
    2. 允许使用缓存的话,验证这个缓存还有没有效

    这个compute方法就是来完成这些事情的。
    获取到缓存策略后,在CacheInterceptor的intercept方法中就会根据networkRequest和CacheResponse的情况来做出不同的处理。

    /**
    	上面CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute() 通过这个compute方法来获取缓存策略
    */
    fun compute(): CacheStrategy {
        val candidate = computeCandidate()
    
        // We're forbidden from using the network and the cache is insufficient.
        //禁止使用网络并且缓存无效
        if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
            return CacheStrategy(null, null)
        }
    
        return candidate
    }
    
    
    /** Returns a strategy to use assuming the request can use the network. */
    private fun computeCandidate(): CacheStrategy {
        //没有缓存的响应
        if (cacheResponse == null) {
            return CacheStrategy(request, null)
        }
    
        // Drop the cached response if it's missing a required handshake.
        //HTTPS请求没有进行握手
        if (request.isHttps && cacheResponse.handshake == null) {
            return CacheStrategy(request, null)
        }
    
        // If this response shouldn't have been stored, it should never be used as a response source.
        // This check should be redundant as long as the persistence store is well-behaved and the
        // rules are constant.
        if (!isCacheable(cacheResponse, request)) {
            return CacheStrategy(request, null)
        }
    
        //请求头Cache-Control为no-cache或者请求头包含If-Modified-Since或者If-None-Match,则本地缓存过期,需要服务器验证本地缓存是不是还能继续使
        val requestCaching = request.cacheControl
        if (requestCaching.noCache || hasConditions(request)) {
            return CacheStrategy(request, null)
        }
    
        val responseCaching = cacheResponse.cacheControl
    
        val ageMillis = cacheResponseAge()
        var freshMillis = computeFreshnessLifetime()
    
        if (requestCaching.maxAgeSeconds != -1) {
            freshMillis = minOf(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds.toLong()))
        }
    
        var minFreshMillis: Long = 0
        if (requestCaching.minFreshSeconds != -1) {
            minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
        }
    
        var maxStaleMillis: Long = 0
        if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
            maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
        }
    	//缓存过期了,但仍然可用,给相应头中添加了Warning,使用缓存
        if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
            val builder = cacheResponse.newBuilder()
            if (ageMillis + minFreshMillis >= freshMillis) {
                builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"")
            }
            val oneDayMillis = 24 * 60 * 60 * 1000L
            if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
                builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"")
            }
            return CacheStrategy(null, builder.build())
        }
    
        // Find a condition to add to the request. If the condition is satisfied, the response body
        // will not be transmitted.
        val conditionName: String
        val conditionValue: String?
        
        //缓存已经过期,满足相应的条件则给请求头添加相应的参数,来验证资源是否改变
        when {
            etag != null -> {
                conditionName = "If-None-Match"
                conditionValue = etag
            }
    
            lastModified != null -> {
                conditionName = "If-Modified-Since"
                conditionValue = lastModifiedString
            }
    
            servedDate != null -> {
                conditionName = "If-Modified-Since"
                conditionValue = servedDateString
            }
    
            else -> return CacheStrategy(request, null) // No condition! Make a regular request.
        }
    
        val conditionalRequestHeaders = request.headers.newBuilder()
        conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)
    
        val conditionalRequest = request.newBuilder()
        .headers(conditionalRequestHeaders.build())
        .build()
        return CacheStrategy(conditionalRequest, cacheResponse)
    }
    

    失败重连

    OkHttp的失败重连是通过拦截器链中的RetryAndFollowUpInterceptor来实现的,它的intercept方法如下:

    override fun intercept(chain: Interceptor.Chain): Response {
        val realChain = chain as RealInterceptorChain
        var request = chain.request
        val call = realChain.call
        var followUpCount = 0
        var priorResponse: Response? = null
        var newExchangeFinder = true
        var recoveredFailures = listOf<IOException>()
        while (true) {
            call.enterNetworkInterceptorExchange(request, newExchangeFinder)
    
            var response: Response
            var closeActiveExchange = true
            try {
                if (call.isCanceled()) {
                    throw IOException("Canceled")
                }
    
                try {
                    response = realChain.proceed(request)	//调用下一个拦截器
                    newExchangeFinder = true
                } catch (e: RouteException) {
                    if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {	//调用了recover方法
                        throw e.firstConnectException.withSuppressed(recoveredFailures)
                    } else {
                        recoveredFailures += e.firstConnectException
                    }
                    newExchangeFinder = false
                    continue	//重新进行请求
                } catch (e: IOException) {
                    if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {	//调用了recover方法
                        throw e.withSuppressed(recoveredFailures)
                    } else {
                        recoveredFailures += e
                    }
                    newExchangeFinder = false
                    continue	//重新进行请求
                }
    			...
            } finally {
                call.exitNetworkInterceptorExchange(closeActiveExchange)
            }
        }
    }
    

    可以看到,在请求出现异常时会调用recover方法尝试重连:

    private fun recover(
        e: IOException,
        call: RealCall,
        userRequest: Request,
        requestSendStarted: Boolean
    ): Boolean {
        //app层面禁止重连
        if (!client.retryOnConnectionFailure) return false
    
        //不能再发送请求体了
        if (requestSendStarted && requestIsOneShot(e, userRequest)) return false
    
        //致命异常
        if (!isRecoverable(e, requestSendStarted)) return false
    
        //没有更多尝试的途径了
        if (!call.retryAfterFailure()) return false
    
        // For failure recovery, use the same route selector with a new connection.
        //对于失败重连,使用相同的route selector和一个新的连接
        return true
    }
    

    也就是说,只要不满足上面四个if语句的条件,就会返回true,并在intercept方法中调用下一个拦截器重新请求。

    请求流程图

    img

    参考资料

    《Android进阶之光》

    OkHttp 源码解析(Kotlin版)

    揭秘网络框架第三篇: OkHttp 核心机制深入学习(彻底理解五个拦截器)

    展开全文
  • java okhttp 网络请求

    2015-05-27 12:55:55
    用来交换数据和内容, 有效的使用HTTP可以使你的APP 变的更快和减少流量的使用OkHttp 是一个很棒HTTP客户端:支持SPDY, 可以合并多个到同一个主机的请求 使用连接池技术减少请求的延迟(如果SP

    http://square.github.io/okhttp/

    示例下载

    翻译如下: 转自http://hao.jobbole.com/okhttp/

    http是现在主流应用使用的网络请求方式, 用来交换数据和内容, 有效的使用HTTP可以使你的APP 变的更快和减少流量的使用

    OkHttp 是一个很棒HTTP客户端:

    支持SPDY, 可以合并多个到同一个主机的请求
    使用连接池技术减少请求的延迟(如果SPDY是可用的话)
    使用GZIP压缩减少传输的数据量
    缓存响应避免重复的网络请求
    当你的网络出现拥挤的时候,就是OKHttp 大显身手的时候, 它可以避免常见的网络问题,如果你的服务是部署在不同的IP上面的,如果第一个连接失败, OkHTtp会尝试其他的连接. 这个对现在IPv4+IPv6 中常见的把服务冗余部署在不同的数据中心上. OkHttp 将使用现在TLS特性(SNI ALPN) 来初始化新的连接. 如果握手失败, 将切换到SLLv3

    使用OkHttp很容易, 同时支持 异步阻塞请求和回调.

    如果你使用OkHttp ,你不用重写你的代码, okhttp-urlconnection模块实现了 java.net.HttpURLConnection 中的API, okhttp-apache模块实现了HttpClient中的API

    例子

    请求一个URL

    这里例子请求一个URL,并以字符串的格式打印内容,全部代码参考这里 Full source.

    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();
    }

    向服务器POST请求

    向服务器发送POST请求,全部代码参照这里 Full source.

    public static final MediaType JSON
        = MediaType.parse("application/json; charset=utf-8");
    
    OkHttpClient client = new OkHttpClient();
    
    String post(String url, String json) throws IOException {
      RequestBody body = RequestBody.create(JSON, json);
      Request request = new Request.Builder()
          .url(url)
          .post(body)
          .build();
      Response response = client.newCall(request).execute();
      return response.body().string();
    }

    下载

    Latest JAR

    你还需要下载 Okio, OKhttp使用这个库用来快速的I/O处理 在这里下载 latest JAR.

    Maven方式下载

    <dependency>
      <groupId>com.squareup.okhttp</groupId>
      <artifactId>okhttp</artifactId>
      <version>(insert latest version)</version>
    </dependency>

    gradle

    compile 'com.squareup.okhttp:okhttp:2.0.0'

    github地址 https://github.com/square/okhttp

    展开全文
  • 导入OkHttp依赖库 implementation 'com.squareup.okhttp3:okhttp:3.4.1’ OkHttp.java public class OkHttp { private static final MediaType JSON = MediaType.parse("application/json;charset=utf-8");...
  • OkHttp也提供了一个网络拦截器okhttp-logging-interceptor,通过它能拦截okhttp网络请求和响应所有相关信息(请求行、请求头、请求体、响应行、响应行、响应头、响应体)。 使用okhttp网络日志拦截器: comp
  • OkHttp也提供了一个网络拦截器okhttp-logging-interceptor,通过它能拦截okhttp网络请求和响应所有相关信息(请求行、请求头、请求体、响应行、响应行、响应头、响应体)。 使用okhttp网络日志拦截器需添加以下...
  • okhttp请求

    2017-10-13 07:58:00
    支持SPDY, 可以合并多个到同一个主机的请,使用连接池技术减少请求的延迟(如果SPDY是可用的话), SPDY协议是Google提出的基于传输控制协议(TCP)的应用层协议,通过压缩、多路复用和优先级来缩短加载时间。该协议是...
  • 转载学习 ------------------------------//------...网络请求工具是Retrofit,底层封装的是OkHttp,通常调试网络接口时都会将网络请求和响应相关数据通过日志的形式打印出来。OkHttp也提供了一个网络拦截器okhttp...
  • OkHttp3-请求器(Calls) OkHttp客户端负责接收应用程序发出的请求,并且从服务器获取响应返回给应用程序。理论听起来十分简单,但是在实践中往往会出现很多意想不到的因素。 请求 (Request) 每一个Http请求都包含一...
  • import android.os.Bundle; import android.util.Log; import java.io.IOException; ...import androidx.appcompat.app.AppCompatActivity;...import okhttp3.Call;...import okhttp3.Callback;...import okhttp3
  • 哪位大佬springboot,怎么用okhttp3,重复请求同一个外部服务接口,并合并返回结果,有一个请求错误就停止,支持htp和https
  • OkHttp

    2016-07-04 20:50:10
    OkHttp
  • Android之网络请求7————OkHttp源码4:网络操作 一.目录 二.前言 关于OkHttp的源码已经写了3篇了,Android之网络请求4————OkHttp源码1:框架这一篇主要分析了,OkHttp的整体框架。在Android之...
  • OKHTTP

    2017-10-13 20:08:24
    权限:        compile 'com.squareup.okio:okio:1.5.0' ... compile 'com.squareup.okhttp3:okhttp:3.2.0'  compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'  compile 'com.goo
  • 用来交换数据和内容, 有效的使用HTTP可以使你的APP 变的更快和减少流量的使用OkHttp 是一个很棒HTTP客户端(GitHub主页:https://github.com/square/okhttp):支持SPDY, 可以合并多个到同一个主机的请求使用连接池...
  • .subscribe(...) But only if the retrofit builder is configured with Either with the async adapter (calls will be queued in the okhttp internal executor). I personally think this is not a good idea, ...
  • 支持SPDY, 可以合并多个到同一个主机的请求 使用连接池技术减少请求的延迟(如果SPDY是可用的话) 使用GZIP压缩减少传输的数据量 缓存响应避免重复的网络请求 当你的网络出现拥挤的时候,就是OKHttp 大显身手的时候...
  • OKHttp详解

    千次阅读 2020-12-28 17:45:22
    对于 Android App 来说,OkHttp 现在几乎已经占据了所有的网络请求操作,RetroFit + OkHttp 实现网络请求似乎成了一种标配。因此它也是每一个 Android 开发工程师的必备技能,了解其内部实现原理可以更好地进行功能...
  • OkHttp源码分析

    2021-04-10 18:00:38
    本文基于OkHttp 4.9.0分析 OkHttp是什么? 众所周知,OkHttp是一个客户端用来发送HTTP消息并对服务器的响应做出处理的应用层框架。而且现在流行的Retrofit的底层同样也是基于Okhttp的。那么OkHttp有什么优点呢?...
  • 一个功能强悍的网络请求库,使用RxJava2 + Retrofit2 + OKHttp组合进行封装。 关于我 特征 支持默认、全局、局部三个层次的配置功能。 支持动态配置和自定义底层框架Okhttpclient、Retrofit. 加入基础Api...
  • OkHttp的使用

    2018-08-02 09:30:58
    2.OkHttp的基本使用 3.Call模型 一.Http网络请求响应基本知识 简介 从客户端到服务器端的请求消息及响应,包括在消息首行中,对资源的请求方法、资源的标识符及使用的协议。 Http进行请求和响应,即一次...
  • OKHttp源码分析

    2021-05-29 18:00:18
    OKHttp作为常用的网络框架,Okhttp是由square公司开发。掌握OKHttp的用法以及它的内部工作原理还是很有必要的。
  • OkHttp4.9.0源码分析

    2021-08-09 10:58:01
    通过前面的学习,我们已经对OKHttp有了简单的认识,并对使用有了详细的了解,下面我们将以一个同步Get请求为例进行OKHttp源码分析。 一、基础同步Get请求 private val mUrl = "https://www.baidu.com" // 1、...
  • 在本系列的上一篇文章中,我们分析了OkHttp拦截器链中的前三个拦截器:RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor,它们在请求建立连接之前做了一些预处理。详细可参考你想要的系列:网络...
  • okhttp教程

    千次阅读 2015-07-05 10:05:36
    OkHttp简介 ... 支持SPDY, 可以合并多个到同一个主机的请求 使用连接池技术减少请求的延迟(如果SPDY是可用的话) 使用GZIP压缩减少传输的数据量 缓存响应避免重复的网络请求 当你的网络出现拥
  • Android之网络请求6————OkHttp源码3:获取响应(拦截器链) 一.目录 Android之网络请求6————OkHttp源码3:获取响应(拦截器链) 一.目录 二.目的 三.getResponseWithInterceptorChain方法 四....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,130
精华内容 852
关键字:

okhttp合并请求