不忘初心 砥砺前行, Tomorrow Is Another Day !
相关文章
本文概要:
- 基本使用
- 基本原理
一. 基本使用
okhttp的请求和响应大多数采用建造者模式设计.
1. GET同步请求
public void syncGetRequest() { String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" + "&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json"; final OkHttpClient okHttpClient = new OkHttpClient(); final Request request = new Request.Builder() .get() .url(url) .build(); new Thread(new Runnable() { @Override public void run() { try { Response response = okHttpClient.newCall(request).execute(); Log.d(TAG, "syncGetRequest: " + response.body().string()); } catch (IOException e) { e.printStackTrace(); } } }).star复制代码
2. GET异步请求
public void asyncGetRequest() { String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" + "&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json"; final OkHttpClient okHttpClient = new OkHttpClient(); Request request = new Request.Builder() .get() .url(url) .build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { Log.d(TAG, "Callback-Thread: " + Thread.currentThread()); String result = response.body().string(); Log.d(TAG, "asyncGetRequest: " + result); //通过handler或者runOnUiThread方式切换线程. runOnUiThread(new Runnable() { @Override public void run() { idtv.setText("jasonhww"); } }); /* *如果获取的是文件/图片 */ //方式一:通过获取流 //InputStream inputStream = response.body().byteStream(); //方式二:通过获取字节数组 //byte[] bytes = response.body().bytes(); } }); }复制代码
注意事项:
- Callback的回调方法是在子线程执行的,如需更新UI,可通过runjOnUiThread或者handler切换到主线程.
3. POST提交表单
可以使用FormBody.Builder构建一个表单请求体
public void asyncPostForm(){ String url = "http://api.k780.com/"; final OkHttpClient okHttpClient = new OkHttpClient(); //构建表单请求体 RequestBody requestBody = new FormBody.Builder() .add("app","weather.future") .add("weaid","1") .add("appkey","10003") .add("sign","b59bc3ef6191eb9f747dd4e83c99f2a4") .add("format","json") .build(); //创建POST请求 Request request = new Request.Builder() .url(url) .post(requestBody) .build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { Log.d(TAG, "asyncPostForm: "+response.body().string()); } }); }复制代码
4. POST提交JSON字符串
指定请求体的媒体类型为"application/json"
public void asyncJson() { String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" + "&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json"; String json = "{code:1,result:null}"; OkHttpClient okHttpClient = new OkHttpClient(); //指定媒体类型 MediaType mediaType = MediaType.parse("application/json,charset=utf-8"); RequestBody requestBody = RequestBody.create(mediaType, json); Request request = new Request.Builder() .url(url) .post(requestBody) .build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { Log.d(TAG, "asyncJson: " + response.body().string()); } }); }复制代码
5. 缓存
缓存使用相对比较简单,只需指定一下缓存目录及大小即可.
//伪代码 Cache cache = new Cache(cacheDirectory,cacheSize); OkHttpClient okHttpClient = new OkHttpClient.Builder() .cache(cache) .build(); 复制代码
6. 拦截器
通过拦截器我们可以很方便的去修改请求和响应的相关信息,如修改请求头,请求体等.
- 应用拦截器 拦截应用层与okhttp之间的请求和响应
- 网络拦截器 拦截okhttp与网络层之间的请求和响应,此时网络连接已经建立
public class LoggingInterceptor implements Interceptor { private static final String TAG = "LoggingInterceptor"; @Override public Response intercept(Chain chain) throws IOException { //获取请求 Request request = chain.request(); //打印url,连接状态,请求头 Log.d(TAG, "LoggingInterceptor: url = " + request.url() + "\nconnectionStatus = " + chain.connection() + "\nrequestHeaderInfo = " + request.headers()); //执行请求 Response response = chain.proceed(request); //打印url,响应头 Log.d(TAG, "LoggingInterceptor: url = " + response.request() + "\nresponseHeaderInfo = " + response.headers()); return response; }}public class NetInterceptor implements Interceptor { private static final String TAG = "LoggingInterceptor"; @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Response response = chain.proceed(request); //演示设置响应头 CacheControl.Builder builder = new CacheControl.Builder() .maxAge(10,TimeUnit.MINUTES); return response.newBuilder() .header("Cache-Control",builder.build().toString) .build(); }}复制代码
public void asyncCacheInterceptor() { String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" + "&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json"; OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new LoggingInterceptor())//应用拦截 .addNetworkInterceptor(new NetInterceptor())//网络拦截器 .build(); //通过拦截器修改请求头 Request request = new Request.Builder() .get() .header("user-Agent","Interceptor example") .url(url) .build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { Log.d(TAG, "asyncCacheInterceptor: " + response.body().string()); } }); } 复制代码
二. 基本原理
采用OkHttp源码版本为3.8.1
1. OkHttpClient的创建
OkHttpClient作为okhttp的入口,一般将OkHttpClient采用单例模式.
初始化两种方式- 采用直接"new"的方式
- 采用建造者模式
对应源码
//内部也是通过建造者进行初始化OkHttpClient okhttpClient = new OkHttpClient();OkHttpClient okhttpClient = new OkHttpClient.Builder() .build();复制代码
通过初始化okhttpclient,完成对许多成员变量的初始化工作.
对应源码
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; //Cookie瓶 this.cookieJar = builder.cookieJar; //磁盘缓存 this.cache = builder.cache; this.internalCache = builder.internalCache; this.socketFactory = builder.socketFactory; //HTTPS相关成员变量初始化 ...... }复制代码
2. Call的创建
当okhttpclient初始化后,再通过okHttpClient.newCall(request),创建一个Call对象,最终实际返回了一个RealCall对象.
对应源码
@Override public Call newCall(Request request) { return new RealCall(this, request, false /* for web socket */); }复制代码
2.1 同步请求时
调用RealCall的execute方法
对应源码
/** * 1. 首先看RealCall的execute方法. */@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); try { //实际调用了分发器的executed方法,传入RealCall对象. client.dispatcher().executed(this); //最终通过拦截器链获取响应. //最终通过拦截器链获取响应. //最终通过拦截器链获取响应. //重要的话说三遍. Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } finally { client.dispatcher().finished(this); } } /** * 2. 接着看dispatcher的executed方法 */synchronized void executed(RealCall call) { //标识已经执行了请求 runningSyncCalls.add(call); }复制代码
2.1 异步请求时
同样调用RealCall的enqueue方法
对应源码
/** * 1.首先看RealCall的enqueue方法. */@Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); //实际调用了分发器的enqueue方法,传入AsyncCall对象 client.dispatcher().enqueue(new AsyncCall(responseCallback)); } /** * 2.接着看dispatcher对象的enqueue方法. */ synchronized void enqueue(AsyncCall call) { //检测请求是否超过最大请求数和一个host对应最大的请求数 if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { //标识已经执行了请求(这里和同步方法一样操作,可以对比上一点方法) runningAsyncCalls.add(call); //使用线程池执行请求 executorService().execute(call); } else { readyAsyncCalls.add(call); } } /** * 3.最后我们看AsyncCall的实现. */ //AsyncCall是RealCall的内部类,继承自NamedRunnable //NamedRunnable的实现比较简单就是修改线程名.提供一个execute方法在run用调用,这里就不再晒出具体源码了. //AsyncCall的实现主是获取响应,进行回调. final class AsyncCall extends NamedRunnable { private final Callback responseCallback; //....省略部分代码 @Override protected void execute() { boolean signalledCallback = false; 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) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); } else { //回调失败 responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); }复制代码
最后将Realcall对同步与异步请求处理流程做个小结:
同步时.
- 调用RealCall的execute,实际调用了分发器的executed方法,传入RealCall对象.
- 分发器的executed方法,仅仅标识已经执行了请求.
- 最终通过拦截器链获取响应.
异步时.
- 调用RealCall的enqueue,实际调用了分发器的enqueue方法,传入AsyncCall对象.
- 分发器的executed方法,先检查请求,满足则标识已经执行了请求,最终使用线程池执行请求.不满足则进人等待队列
- AsyncCall的实现
- AsyncCall是一个NamedRunnable子类
- 通过拦截器链获取响应,回调成功与失败.
3.拦截器链的构建与启动
整个okhttp的核心设计之处
- 遵循单一原则,每个拦截器只做一项处理.
- 拦截器之间通过拦截器链(RealInterceptorChain)进行连接,构建出处理请求和响应的流水链.
请求通过层层拦截器处理,最后发送出去,反之响应.
对应源码
//通过拦截器链获取响应 Response getResponseWithInterceptorChain() throws IOException { /** * 初始化拦截器集合 */ Listinterceptors = new ArrayList<>(); //添加7种类型拦截器 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); //启动拦截器链 return chain.proceed(originalRequest); } /** * 拦截器链的启动 */ public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { //......省略部分代码 // 创建一个新的拦截器链,拦截器位置指向将index+1.即下一个. RealInterceptorChain next = new RealInterceptorChain( interceptors, streamAllocation, httpCodec, connection, index + 1, request); //通过下标index获取当前要执行的拦截器 Interceptor interceptor = interceptors.get(index); //执行拦截器intercept方法 //如果当前拦截器执行完后,则调用传入的next(新链对象)的proceed方法,执行下一个拦截器.依次类推. Response response = interceptor.intercept(next); if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) { throw new IllegalStateException("network interceptor " + interceptor + " must call proceed() exactly once"); } if (response == null) { throw new NullPointerException("interceptor " + interceptor + " returned null"); } return response; }复制代码
这7种拦截器执行顺序为:
- 应用拦截器
- retryAndFollowUpInterceptor
- BridgeInterceptor
- CacheInterceptor
- ConnectInterceptor
- 网络拦截器
- CallServerInterceptor
其中带下划线的拦截器,就是我们之前自定义的拦截器.接下来就依次讲解7种拦截器的作用.
3.1 应用拦截器
不再讲述
3.2 retryAndFollowUpInterceptor
处理错误重试和重定向
对应源码
@Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); while (true) { if (canceled) { streamAllocation.release(); throw new IOException("Canceled"); } Response response = null; boolean releaseConnection = true; try { //执行下一个拦截器,获取响应 response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null); releaseConnection = false; } catch (RouteException e) { // The attempt to connect via a route failed. The request will not have been sent. if (!recover(e.getLastConnectException(), false, request)) { throw e.getLastConnectException(); } releaseConnection = false; //满足条件,则重试 continue; } catch (IOException e) { // An attempt to communicate with a server failed. The request may have been sent. boolean requestSendStarted = !(e instanceof ConnectionShutdownException); if (!recover(e, requestSendStarted, request)) throw e; releaseConnection = false; //满足条件,则重试 continue; } finally { //出现异常释放资源,continue在finally语句块执行后才执行 // We're throwing an unchecked exception. Release any resources. if (releaseConnection) { streamAllocation.streamFailed(null); streamAllocation.release(); } } //检测是否满足定向的要求代码 ...... //重新赋值为重定向的请求 request = followUp; priorResponse = response; } }复制代码
3.3 BridgeInterceptor
桥接应用层和网络层
A. 将请求进行深加工,使其成为真正请求
- 对一些请求头的设置
B. 并且也对网络响应做响应处理.
- 解压缩,去掉不必要的响应头
对应源码
@Override public Response intercept(Chain chain) throws IOException { //获取请求 Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); //对一些请求头的设置. RequestBody body = userRequest.body(); if (body != null) { MediaType contentType = body.contentType(); if (contentType != null) { requestBuilder.header("Content-Type", contentType.toString()); } long contentLength = body.contentLength(); if (contentLength != -1) { requestBuilder.header("Content-Length", Long.toString(contentLength)); requestBuilder.removeHeader("Transfer-Encoding"); } else { requestBuilder.header("Transfer-Encoding", "chunked"); requestBuilder.removeHeader("Content-Length"); } } if (userRequest.header("Host") == null) { requestBuilder.header("Host", hostHeader(userRequest.url(), false)); } if (userRequest.header("Connection") == null) { requestBuilder.header("Connection", "Keep-Alive"); } // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing // the transfer stream. boolean transparentGzip = false; if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) { transparentGzip = true; //如果没有配置,默认配置gzip.获取响应时需要解压缩. requestBuilder.header("Accept-Encoding", "gzip"); } Listcookies = cookieJar.loadForRequest(userRequest.url()); if (!cookies.isEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies)); } if (userRequest.header("User-Agent") == null) { requestBuilder.header("User-Agent", Version.userAgent()); } //执行下一个拦截器获取响应 Response networkResponse = chain.proceed(requestBuilder.build()); HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) { //用于解压缩 GzipSource responseBody = new GzipSource(networkResponse.body().source()); //返回应用层去掉不需要的响应头 Headers strippedHeaders = networkResponse.headers().newBuilder() .removeAll("Content-Encoding") .removeAll("Content-Length") .build(); responseBuilder.headers(strippedHeaders); responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody))); } return responseBuilder.build(); }复制代码
3.4 CacheInterceptor
承担缓存的查找和保存职责
A. 不需要网络请求,缓存可用,直接返回
B. 缓存不可用,执行下一个拦截器获取响应;
如果用户配置了需要缓存,将响应写入缓存,并返回.对应源码
@Override public Response intercept(Chain chain) throws IOException { Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis(); //检查缓存策略 CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); //networkRequest不为空,需要发送请求 Request networkRequest = strategy.networkRequest; //cacheResponse不为空,则缓存可用 Response cacheResponse = strategy.cacheResponse; //......省略部分代码 // If we don't need the network, we're done. //不需要网络请求,缓存可用,直接返回 if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); } Response networkResponse = null; try { //缓存不可用,执行下一个拦截器获取响应 networkResponse = chain.proceed(networkRequest); } finally { //......省略部分代码 } //......省略部分代码 //包装网络响应 Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); //用户配置了缓存 if (cache != null) { //判断条件 if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { // Offer this request to the cache. CacheRequest cacheRequest = cache.put(response); //网络响应写入缓存,并返回 return cacheWritingResponse(cacheRequest, response); } if (HttpMethod.invalidatesCache(networkRequest.method())) { try { cache.remove(networkRequest); } catch (IOException ignored) { // The cache cannot be written. } } } return response; }复制代码
3.5 ConnectInterceptor
给请求提供一个连接
- TCP的连接,HTTPS的连接全部在此完成.
对应源码
@Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); StreamAllocation streamAllocation = realChain.streamAllocation(); // We need the network to satisfy this request. Possibly for validating a conditional GET. boolean doExtensiveHealthChecks = !request.method().equals("GET"); //获取HTTP编码解码器 HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks); //获取一个链接 RealConnection connection = streamAllocation.connection(); //执行下一个拦截器 return realChain.proceed(request, streamAllocation, httpCodec, connection); }复制代码
3.6 网络拦截器
不再讲述
3.7 CallServerInterceptor
将请求发送出去
对应源码
@Override public Response intercept(Chain chain) throws IOException { //.......省略部分代码 //发送网络请求,httpCodec则是在上一个ConnectInterceptor拦截器获取到的 httpCodec.finishRequest(); if (responseBuilder == null) { responseBuilder = httpCodec.readResponseHeaders(false); } //构建网络响应对象 Response response = responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); int code = response.code(); if (forWebSocket && code == 101) { // Connection is upgrading, but we need to ensure interceptors see a non-null response body. response = response.newBuilder() .body(Util.EMPTY_RESPONSE) .build(); } else { //写入响应体 response = response.newBuilder() .body(httpCodec.openResponseBody(response)) .build(); } if ("close".equalsIgnoreCase(response.request().header("Connection")) || "close".equalsIgnoreCase(response.header("Connection"))) { streamAllocation.noNewStreams(); } if ((code == 204 || code == 205) && response.body().contentLength() > 0) { throw new ProtocolException( "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength()); } return response; }复制代码
由于本人技术有限,如有错误的地方,麻烦大家给我提出来,本人不胜感激,大家一起学习进步.