OkHttp
官网
1.可以共享一个Socket
2.连接池
3.Gzip格式传输,减少大小
4.缓存可以避免重复请求
默认重试,多个ip地址的,会挨个尝试
request/reponse 链式调用。
同步 blocking call
异步 callback
支持Android 2.3及以上。
Java要求至少1.7
示例:
Http状态码
100~199:指示信息,表示请求已接收,继续处理
200~299:请求成功,表示请求已被成功接收、理解、接受
300~399:重定向,要完成请求必须进行更进一步的操作
400~499:客户端错误,请求有语法错误或请求无法实现
500~599:服务器端错误,服务器未能实现合法的请求
常见状态吗
200 OK:客户端请求成功
400 Bad Request:客户端请求有语法错误,不能被服务器所理解
401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden:服务器收到请求,但是拒绝提供服务
500 Internal Server Error:服务器发生不可预期的错误
503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常
HTTP的消息报头
消息报头分为通用报头、请求报头、响应报头、实体报头等。消息头由键值对组成,每行一对,关键字和值用英文冒号“:”分隔。
通用报头
既可以出现在请求报头,也可以出现在响应报头中
Date:表示消息产生的日期和时间
Connection:允许发送指定连接的选项,例如指定连接是连续的,或者指定“close”选项,通知服务器,在响应完成后,关闭连接
Cache-Control:用于指定缓存指令,缓存指令是单向的(响应中出现的缓存指令在请求中未必会出现),且是独立的(一个消息的缓存指令不会影响另一个消息处理的缓存机制)
请求报头
请求报头通知服务器关于客户端求求的信息,典型的请求头有:
Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机
User-Agent:发送请求的浏览器类型、操作系统等信息
Accept:客户端可识别的内容类型列表,用于指定客户端接收那些类型的信息
Accept-Encoding:客户端可识别的数据编码
Accept-Language:表示浏览器所支持的语言类型
Connection:允许客户端和服务器指定与请求/响应连接有关的选项,例如这是Keep-Alive则表示保持连接。
Transfer-Encoding:告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式。
响应报头
用于服务器传递自身信息的响应,常见的响应报头:
Location:用于重定向接受者到一个新的位置,常用在更换域名的时候
Server:包含可服务器用来处理请求的系统信息,与User-Agent请求报头是相对应的
实体报头
实体报头用来定于被传送资源的信息,既可以用于请求也可用于响应。请求和响应消息都可以传送一个实体,常见的实体报头为:
Content-Type:发送给接收者的实体正文的媒体类型
Content-Lenght:实体正文的长度
Content-Language:描述资源所用的自然语言,没有设置则该选项则认为实体内容将提供给所有的语言阅读
Content-Encoding:实体报头被用作媒体类型的修饰符,它的值指示了已经被应用到实体正文的附加内容的编码,因而要获得Content-Type报头域中所引用的媒体类型,必须采用相应的解码机制。
Last-Modified:实体报头用于指示资源的最后修改日期和时间
Expires:实体报头给出响应过期的日期和时间
Fiddler 网络数据 抓包 Fiddler
HttpClient与HttpURLConnection
Android SDK中包含了HttpClient,在Android6.0版本直接删除了HttpClient类库
HttpURLConnection
Android 2.2版本之前,HttpURLConnection一直存在着一些令人厌烦的bug。比如说对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能:
private void disableConnectionReuseIfNecessary() {
// 这是一个2.2版本之前的bug
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
System.setProperty("http.keepAlive", "false");
}
}
所以在Android 2.2版本以及之前的版本使用HttpClient是较好的选择,而在Android 2.3版本及以后,HttpURLConnection则是最佳的选择,它的API简单,体积较小,因而非常适用于Android项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。另外在Android 6.0版本中,HttpClient库被移除了,HttpURLConnection则是以后我们唯一的选择。
OkHttp2.x用法全解析 点击打开链接
OkHttp, 它处理了很多网络疑难杂症:会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP,此外OkHttp还处理了代理服务器问题和SSL握手失败问题。
compile 'com.squareup.okhttp:okhttp:2.7.5'
compile 'com.squareup.okio:okio:1.7.0'
异步GET请求 Async client.enqueue
同步GET请求 Sync client.execute
异步POST请求
重点操控细节来了!!!
1、请求缓存设置
首先我们设置缓存路径和大小并设置给OkHttpClient:
第一次是 network ---
第二次是 cache---
------是 cache---
当然也有种情况是有的请求每次都需要最新的数据,则在创建Request,来设置cacheControl为“CacheControl.FORCE_NETWORK”,用来表示请求会一直请求网络得到数据:
第一次是 network ---
第二次是 network---
------是 network---
2.设置超时时间
使用call.cancel()可以立即停止掉一个正在执行的call。
如果一个线程正在写请求或者读响应,将会引发IOException。
当用户离开一个应用时或者跳到其他界面时,使用Call.cancel()可以节约网络资源,另外不管同步还是异步的call都可以取消。
也可以通过tags来同时取消多个请求。
当你构建一请求时,使用RequestBuilder.tag(tag)来分配一个标签。
之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call。
"是否取消成功"+call.isCanceled()
关于封装
铺垫完成,主角登场
OkHttp3用法全解析
表单/文件/表单和文件混合
OkHttp3异步POST请求和OkHttp2.x对比没有FormEncodingBuilder这个类,
替代它的是功能更加强大的 FormBody
和OkHttp2.x有 区别的是 不能通过OkHttpClient直接设置超时时间和缓存了, 而是通过OkHttpClient.Builder来设置,
所以我们通常不会调用new OkHttpClient()来得到OkHttpClient,而是通过builder.build():
=====okhttp的运行原理======
client.newCall -->RealCall --->Client.dispatcher().equeue(AsyncCall)
这个AsyncCall添加进来的时候,先判断正在运行的异步请求队列是否已满额,如果满额加入等待队列。
这个AsyncCall是RealCall的一个内部类。内部也实现了execute方法。
execute方法调用中,会调用一个 getResponseWithInterceptorChain方法来得到Response
在这个方法中,会创建一个 ApplicationInterceptorChain,一个拦截器链,这个类也是RealCall的内部类,
接下来就执行它的 proceed方法。
proceed方法每次从拦截器列表中取出拦截器,
当存在多个拦截器时都会阻塞,并等待下一个拦截器的调用返回。
拦截器可以修改请求和响应的头部信息。比如在请求头中添加host属性,也可以添加一些公共参数,比如设备id,版本号等。
如果没有拦截器,就会直接调用getResponse方法,执行网络请求。
getReponse里创建 HttpEngine
engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);
engine.sendRequest();
engine.readResponse();
sendRequest方法里
//网络请求
networkRequest = cacheStrategy.networkRequest;
//缓存的响应
cacheResponse = cacheStrategy.cacheResponse;
最主要的是做了缓存的策略。
cacheCandidate是上次与服务器交互缓存的Response,这里的缓存都是基于Map,key是请求中url的md5,value是在文件中查询到的缓存,页面置换基于LRU算法,我们现在只需要知道它是一个可以读取缓存Header的Response即可。
根据cacheStrategy的处理得到了networkRequest和cacheResponse这两个值,
根据这两个值的数据是否为null来进行进一步的处理,
当networkRequest和cacheResponse都为null的情况也就是不进行网络请求并且缓存不存在或者过期,这时候则返回504错误;
当networkRequest 为null时也就是不进行网络请求,而且缓存可以使用时则直接返回缓存;
其他的情况则请求网络。
readResponse方法里
这个方法发起刷新请求头部和请求体,解析HTTP响应头部。如果有缓存并且可用则用缓存的数据并更新缓存,否则就用网络请求返回的数据。
如缓存果过期或者强制放弃缓存,在此情况下,缓存策略全部交给服务器判断,客户端只用发送条件get请求即可,如果缓存是有效的,则返回304 Not Modifiled,否则直接返回body。
条件get请求有两种方式一种是Last-Modified-Date,一种是 ETag。
这里采用了Last-Modified-Date,通过缓存和网络请求响应中的Last-Modified来计算是否是最新数据,如果是则缓存有效。
失败重连
再回到RealCall的getResponse方法:
当发生IOException或者RouteException时会执行HttpEngine的recover方法:
重新创建了HttpEngine并返回,用来完成重连。
====复用连接池====
说到连接池,就得要了解下HTTP连接相关的三次握手和四次分手。
打开连接(3次握手后)-写数据-读数据-关闭连接(4次分手后)
通过客户端和服务端之间 发送报文段 相互确认并不断更改状态直至建立连接
keepalive connection
当然大量的连接每次连接关闭都要三次握手四次分手的很显然会造成性能低下,
因此http有一种叫做keepalive connections的机制,它可以在传输数据后仍然保持连接,当客户端需要再次获取数据时,直接使用刚刚空闲下来的连接而不需要再次握手。
连接池(ConnectionPool)
在okhttp中,在高层代码的调用中,反复执行aquire与release操作,
这两个函数其实是在改变RealConnection中的List<Reference<StreamAllocation>> 的大小。(StreamAllocation.java)
连接池的类位于okhttp3.ConnectionPool:
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
/** The maximum number of idle connections for each address. */
//空闲的socket最大连接数
private final int maxIdleConnections;
//socket的keepAlive时间
private final long keepAliveDurationNs;
// 双向队列
private final Deque<RealConnection> connections = new ArrayDeque<>();
final RouteDatabase routeDatabase = new RouteDatabase();
boolean cleanupRunning;
主要的变量有必要说明一下:
executor线程池,类似于CachedThreadPool,需要注意的是这种线程池的工作队列采用了没有容量的SynchronousQueue
Deque<RealConnection>,双向队列,双端队列同时具有队列和栈性质,经常在缓存中被使用,里面维护了RealConnection也就是socket物理连接的包装。
RouteDatabase,它用来记录连接失败的Route的黑名单,当连接失败的时候就会把失败的线路加进去。
ConnectionPool构造方法
public ConnectionPool() {
//默认空闲的socket最大连接数为5个,socket的keepAlive时间为5分钟
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
// Put a floor on the keep alive duration, otherwise cleanup will spin loop.
if (keepAliveDuration <= 0) {
throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
}
}
通过构造函数可以看出 ConnectionPool默认的空闲的socket最大连接数为5个,socket的keepAlive时间为5分钟。
这里再次记录下Http状态码
304 (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。
403 (禁止) 服务器拒绝请求。
404 (未找到) 服务器找不到请求的网页。
502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。
504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。
Dispatcher 任务调度
它维护了几个主要变量
/** 最大并发请求数*/
private int maxRequests = 64;
/** 每个主机最大请求数*/
private int maxRequestsPerHost = 5;
/** 消费者线程池 */
private ExecutorService executorService;
/** 将要运行的异步请求队列 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**正在运行的异步请求队列 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** 正在运行的同步请求队列 */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
=====
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
当正在运行的异步请求队列中的数量小于64并且正在运行的请求主机数小于5时
则把请求加载到runningAsyncCalls中并在线程池中执行,
否则就再入到readyAsyncCalls中进行缓存等待。
private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
return chain.proceed(originalRequest);
}
1.可以共享一个Socket
2.连接池
3.Gzip格式传输,减少大小
4.缓存可以避免重复请求
默认重试,多个ip地址的,会挨个尝试
request/reponse 链式调用。
同步 blocking call
异步 callback
支持Android 2.3及以上。
Java要求至少1.7
示例:
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();
}
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();
}
compile 'com.squareup.okhttp3:okhttp:(insert latest version)'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'(2018.5.16)
Http状态码
100~199:指示信息,表示请求已接收,继续处理
200~299:请求成功,表示请求已被成功接收、理解、接受
300~399:重定向,要完成请求必须进行更进一步的操作
400~499:客户端错误,请求有语法错误或请求无法实现
500~599:服务器端错误,服务器未能实现合法的请求
常见状态吗
200 OK:客户端请求成功
400 Bad Request:客户端请求有语法错误,不能被服务器所理解
401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden:服务器收到请求,但是拒绝提供服务
500 Internal Server Error:服务器发生不可预期的错误
503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常
HTTP的消息报头
消息报头分为通用报头、请求报头、响应报头、实体报头等。消息头由键值对组成,每行一对,关键字和值用英文冒号“:”分隔。
通用报头
既可以出现在请求报头,也可以出现在响应报头中
Date:表示消息产生的日期和时间
Connection:允许发送指定连接的选项,例如指定连接是连续的,或者指定“close”选项,通知服务器,在响应完成后,关闭连接
Cache-Control:用于指定缓存指令,缓存指令是单向的(响应中出现的缓存指令在请求中未必会出现),且是独立的(一个消息的缓存指令不会影响另一个消息处理的缓存机制)
请求报头
请求报头通知服务器关于客户端求求的信息,典型的请求头有:
Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机
User-Agent:发送请求的浏览器类型、操作系统等信息
Accept:客户端可识别的内容类型列表,用于指定客户端接收那些类型的信息
Accept-Encoding:客户端可识别的数据编码
Accept-Language:表示浏览器所支持的语言类型
Connection:允许客户端和服务器指定与请求/响应连接有关的选项,例如这是Keep-Alive则表示保持连接。
Transfer-Encoding:告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式。
响应报头
用于服务器传递自身信息的响应,常见的响应报头:
Location:用于重定向接受者到一个新的位置,常用在更换域名的时候
Server:包含可服务器用来处理请求的系统信息,与User-Agent请求报头是相对应的
实体报头
实体报头用来定于被传送资源的信息,既可以用于请求也可用于响应。请求和响应消息都可以传送一个实体,常见的实体报头为:
Content-Type:发送给接收者的实体正文的媒体类型
Content-Lenght:实体正文的长度
Content-Language:描述资源所用的自然语言,没有设置则该选项则认为实体内容将提供给所有的语言阅读
Content-Encoding:实体报头被用作媒体类型的修饰符,它的值指示了已经被应用到实体正文的附加内容的编码,因而要获得Content-Type报头域中所引用的媒体类型,必须采用相应的解码机制。
Last-Modified:实体报头用于指示资源的最后修改日期和时间
Expires:实体报头给出响应过期的日期和时间
Fiddler 网络数据 抓包 Fiddler
HttpClient与HttpURLConnection
Android SDK中包含了HttpClient,在Android6.0版本直接删除了HttpClient类库
HttpURLConnection
Android 2.2版本之前,HttpURLConnection一直存在着一些令人厌烦的bug。比如说对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能:
private void disableConnectionReuseIfNecessary() {
// 这是一个2.2版本之前的bug
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
System.setProperty("http.keepAlive", "false");
}
}
所以在Android 2.2版本以及之前的版本使用HttpClient是较好的选择,而在Android 2.3版本及以后,HttpURLConnection则是最佳的选择,它的API简单,体积较小,因而非常适用于Android项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。另外在Android 6.0版本中,HttpClient库被移除了,HttpURLConnection则是以后我们唯一的选择。
因为会了HttpURLConnection的POST请求那GET请求也就会了,所以我这里只举出POST的例子
刘望舒的网络编程2--HttpUrlConncetion\HttpClient
OkHttp2.x用法全解析 点击打开链接
OkHttp, 它处理了很多网络疑难杂症:会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP,此外OkHttp还处理了代理服务器问题和SSL握手失败问题。
compile 'com.squareup.okhttp:okhttp:2.7.5'
compile 'com.squareup.okio:okio:1.7.0'
异步GET请求 Async client.enqueue
private void getAsynHttp() {
//创建okHttpClient对象
OkHttpClient mOkHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(final Response response) throws IOException {
String str = response.body().string();
Log.i("wangshu", str);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplication(), "请求成功", Toast.LENGTH_SHORT).show();
}
});
}
});
}
注:onResponse回调并不是在UI线程。
同步GET请求 Sync client.execute
private String getSyncHttp() throws IOException{
OkHttpClient mOkHttpClient = new OkHttpClient();
//创建请求Request
final Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
Call call = mOkHttpClient.newCall(request);
Response mResponse=call.execute();
if (mResponse.isSuccessful()) {
return mResponse.body().string();
} else {
throw new IOException("Unexpected code " + mResponse);
}
}
异步POST请求
private void postAsynHttp() {
OkHttpClient mOkHttpClient = new OkHttpClient();
RequestBody formBody = new FormEncodingBuilder()
.add("size", "10")
.build();
Request request = new Request.Builder()
.url("http://api.1-blog.com/biz/bizserver/article/list.do")
.post(formBody)
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(Response response) throws IOException {
String str = response.body().string();
Log.i("wangshu", str);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
}
});
}
});
}
post与get不同的就是要要创建RequestBody并传进Request中
重点操控细节来了!!!
1、请求缓存设置
首先我们设置缓存路径和大小并设置给OkHttpClient:
mOkHttpClient = new OkHttpClient();
File sdcache = getExternalCacheDir();
int cacheSize = 10 * 1024 * 1024;
mOkHttpClient.setCache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
然后get请求,并判断缓存中是否有数据,有则取之,无则网络获取
private void getAsynHttp() {
//创建请求Request
final Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(final Response response) throws IOException {
if (null != response.cacheResponse()) {
String str = response.cacheResponse().toString();
Log.i("wangshu", "cache---" + str);
} else {
response.body().string();
String str=response.networkResponse().toString();
Log.i("wangshu", "network---" + str);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
}
});
}
});
}
日志显示:
第一次是 network ---
第二次是 cache---
------是 cache---
当然也有种情况是有的请求每次都需要最新的数据,则在创建Request,来设置cacheControl为“CacheControl.FORCE_NETWORK”,用来表示请求会一直请求网络得到数据:
final Request request = new Request.Builder()
.url("http://www.baidu.com")
.cacheControl(CacheControl.FORCE_NETWORK)
.build();
日志显示:
第一次是 network ---
第二次是 network---
------是 network---
2.设置超时时间
mOkHttpClient = new OkHttpClient();
mOkHttpClient.setConnectTimeout(15, TimeUnit.SECONDS);
mOkHttpClient.setWriteTimeout(20, TimeUnit.SECONDS);
mOkHttpClient.setReadTimeout(20, TimeUnit.SECONDS);
3.取消请求
使用call.cancel()可以立即停止掉一个正在执行的call。
如果一个线程正在写请求或者读响应,将会引发IOException。
当用户离开一个应用时或者跳到其他界面时,使用Call.cancel()可以节约网络资源,另外不管同步还是异步的call都可以取消。
也可以通过tags来同时取消多个请求。
当你构建一请求时,使用RequestBuilder.tag(tag)来分配一个标签。
之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call。
"是否取消成功"+call.isCanceled()
关于封装
public abstract class ResultCallback<T>
{
public abstract void onError(Request request, Exception e);
public abstract void onResponse(Response response);
}
public class OkHttpEngine {
private volatile static OkHttpEngine mInstance;
private OkHttpClient mOkHttpClient;
private Handler mHandler;
public static OkHttpEngine getInstance() {
if (mInstance == null) {
synchronized (OkHttpEngine.class) {
if (mInstance == null) {
mInstance = new OkHttpEngine();
}
}
}
return mInstance;
}
private OkHttpEngine() {
mOkHttpClient = new OkHttpClient();
mOkHttpClient.setConnectTimeout(15, TimeUnit.SECONDS);
mOkHttpClient.setWriteTimeout(20, TimeUnit.SECONDS);
mOkHttpClient.setReadTimeout(20, TimeUnit.SECONDS);
mHandler = new Handler();
}
public OkHttpEngine setCache(Context mContext) {
File sdcache = mContext.getExternalCacheDir();
int cacheSize = 10 * 1024 * 1024;
mOkHttpClient.setCache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
return mInstance;
}
/**
* 异步get请求
* @param url
* @param callback
*/
public void getAsynHttp(String url, ResultCallback callback) {
final Request request = new Request.Builder()
.url(url)
.build();
Call call = mOkHttpClient.newCall(request);
dealResult(call, callback);
}
private void dealResult(Call call, final ResultCallback callback) {
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
sendFailedCallback(request, e, callback);
}
@Override
public void onResponse(final Response response) throws IOException {
sendSuccessCallback(response, callback);
}
private void sendSuccessCallback(final Response object, final ResultCallback callback) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onResponse(object);
}
}
});
}
private void sendFailedCallback(final Request request, final Exception e, final ResultCallback callback) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (callback != null)
callback.onError(request, e);
}
});
}
});
}
}
OkHttpEngine.getInstance().getAsynHttp("http://www.baidu.com", new ResultCallback() {
@Override
public void onError(Request request, Exception e) {
}
@Override
public void onResponse(Response response) {
String str = response.networkResponse().toString();
Log.i("wangshu", str);
Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
}
});
铺垫完成,主角登场
OkHttp3用法全解析
compile 'com.squareup.okhttp3:okhttp:3.2.0'
compile 'com.squareup.okio:okio:1.7.0'
异步GET请求
private void getAsynHttp() {
mOkHttpClient=new OkHttpClient();
Request.Builder requestBuilder = new Request.Builder().url("http://www.baidu.com");
//可以省略,默认是GET请求
requestBuilder.method("GET",null);
Request request = requestBuilder.build();
Call mcall= mOkHttpClient.newCall(request);
mcall.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (null != response.cacheResponse()) {
String str = response.cacheResponse().toString();
Log.i("wangshu", "cache---" + str);
} else {
response.body().string();
String str = response.networkResponse().toString();
Log.i("wangshu", "network---" + str);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
}
});
}
});
}
异步POST请求表单/文件/表单和文件混合
OkHttp3异步POST请求和OkHttp2.x对比没有FormEncodingBuilder这个类,
替代它的是功能更加强大的 FormBody
private void postAsynHttp() {
mOkHttpClient=new OkHttpClient();
RequestBody formBody = new FormBody.Builder()
.add("size", "10")
.build();
Request request = new Request.Builder()
.url("http://api.1-blog.com/biz/bizserver/article/list.do")
.post(formBody)
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String str = response.body().string();
Log.i("wangshu", str);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
}
});
}
});
}
异步上传文件
//首先定义上传文件类型:
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse("text/x-markdown; charset=utf-8");
File file = new File("/sdcard/wangshu.txt");
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
.build();
异步下载文件
private void downAsynFile() {
mOkHttpClient = new OkHttpClient();
String url = "https://img-my.csdn.net/uploads/201603/26/1458988468_5804.jpg";
Request request = new Request.Builder().url(url).build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) {
InputStream inputStream = response.body().byteStream();
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(new File("/sdcard/wangshu.jpg"));
byte[] buffer = new byte[2048];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, len);
}
fileOutputStream.flush();
} catch (IOException e) {
Log.i("wangshu", "IOException");
e.printStackTrace();
}
Log.d("wangshu", "文件下载成功");
}
});
}
异步上传Multipart文件
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
mOkHttpClient = new OkHttpClient();
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "wangshu")
.addFormDataPart("image", "wangshu.jpg",
RequestBody.create(MEDIA_TYPE_PNG, new File("/sdcard/wangshu.jpg")))
.build();
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + "...")
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build();
设置超时时间和缓存
和OkHttp2.x有 区别的是 不能通过OkHttpClient直接设置超时时间和缓存了, 而是通过OkHttpClient.Builder来设置,
所以我们通常不会调用new OkHttpClient()来得到OkHttpClient,而是通过builder.build():
File sdcache = getExternalCacheDir();
int cacheSize = 10 * 1024 * 1024;
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
OkHttpClient mOkHttpClient=builder.build();
取消请求和2.x一样
=====okhttp的运行原理======
client.newCall -->RealCall --->Client.dispatcher().equeue(AsyncCall)
这个AsyncCall添加进来的时候,先判断正在运行的异步请求队列是否已满额,如果满额加入等待队列。
这个AsyncCall是RealCall的一个内部类。内部也实现了execute方法。
execute方法调用中,会调用一个 getResponseWithInterceptorChain方法来得到Response
在这个方法中,会创建一个 ApplicationInterceptorChain,一个拦截器链,这个类也是RealCall的内部类,
接下来就执行它的 proceed方法。
proceed方法每次从拦截器列表中取出拦截器,
当存在多个拦截器时都会阻塞,并等待下一个拦截器的调用返回。
拦截器可以修改请求和响应的头部信息。比如在请求头中添加host属性,也可以添加一些公共参数,比如设备id,版本号等。
如果没有拦截器,就会直接调用getResponse方法,执行网络请求。
getReponse里创建 HttpEngine
engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);
engine.sendRequest();
engine.readResponse();
sendRequest方法里
//网络请求
networkRequest = cacheStrategy.networkRequest;
//缓存的响应
cacheResponse = cacheStrategy.cacheResponse;
最主要的是做了缓存的策略。
cacheCandidate是上次与服务器交互缓存的Response,这里的缓存都是基于Map,key是请求中url的md5,value是在文件中查询到的缓存,页面置换基于LRU算法,我们现在只需要知道它是一个可以读取缓存Header的Response即可。
根据cacheStrategy的处理得到了networkRequest和cacheResponse这两个值,
根据这两个值的数据是否为null来进行进一步的处理,
当networkRequest和cacheResponse都为null的情况也就是不进行网络请求并且缓存不存在或者过期,这时候则返回504错误;
当networkRequest 为null时也就是不进行网络请求,而且缓存可以使用时则直接返回缓存;
其他的情况则请求网络。
readResponse方法里
这个方法发起刷新请求头部和请求体,解析HTTP响应头部。如果有缓存并且可用则用缓存的数据并更新缓存,否则就用网络请求返回的数据。
如缓存果过期或者强制放弃缓存,在此情况下,缓存策略全部交给服务器判断,客户端只用发送条件get请求即可,如果缓存是有效的,则返回304 Not Modifiled,否则直接返回body。
条件get请求有两种方式一种是Last-Modified-Date,一种是 ETag。
这里采用了Last-Modified-Date,通过缓存和网络请求响应中的Last-Modified来计算是否是最新数据,如果是则缓存有效。
失败重连
再回到RealCall的getResponse方法:
当发生IOException或者RouteException时会执行HttpEngine的recover方法:
重新创建了HttpEngine并返回,用来完成重连。
====复用连接池====
说到连接池,就得要了解下HTTP连接相关的三次握手和四次分手。
打开连接(3次握手后)-写数据-读数据-关闭连接(4次分手后)
通过客户端和服务端之间 发送报文段 相互确认并不断更改状态直至建立连接
keepalive connection
当然大量的连接每次连接关闭都要三次握手四次分手的很显然会造成性能低下,
因此http有一种叫做keepalive connections的机制,它可以在传输数据后仍然保持连接,当客户端需要再次获取数据时,直接使用刚刚空闲下来的连接而不需要再次握手。
连接池(ConnectionPool)
在okhttp中,在高层代码的调用中,反复执行aquire与release操作,
这两个函数其实是在改变RealConnection中的List<Reference<StreamAllocation>> 的大小。(StreamAllocation.java)
连接池的类位于okhttp3.ConnectionPool:
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
/** The maximum number of idle connections for each address. */
//空闲的socket最大连接数
private final int maxIdleConnections;
//socket的keepAlive时间
private final long keepAliveDurationNs;
// 双向队列
private final Deque<RealConnection> connections = new ArrayDeque<>();
final RouteDatabase routeDatabase = new RouteDatabase();
boolean cleanupRunning;
主要的变量有必要说明一下:
executor线程池,类似于CachedThreadPool,需要注意的是这种线程池的工作队列采用了没有容量的SynchronousQueue
Deque<RealConnection>,双向队列,双端队列同时具有队列和栈性质,经常在缓存中被使用,里面维护了RealConnection也就是socket物理连接的包装。
RouteDatabase,它用来记录连接失败的Route的黑名单,当连接失败的时候就会把失败的线路加进去。
ConnectionPool构造方法
public ConnectionPool() {
//默认空闲的socket最大连接数为5个,socket的keepAlive时间为5分钟
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
// Put a floor on the keep alive duration, otherwise cleanup will spin loop.
if (keepAliveDuration <= 0) {
throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
}
}
通过构造函数可以看出 ConnectionPool默认的空闲的socket最大连接数为5个,socket的keepAlive时间为5分钟。
这里再次记录下Http状态码
304 (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。
403 (禁止) 服务器拒绝请求。
404 (未找到) 服务器找不到请求的网页。
502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。
504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。
Dispatcher 任务调度
它维护了几个主要变量
/** 最大并发请求数*/
private int maxRequests = 64;
/** 每个主机最大请求数*/
private int maxRequestsPerHost = 5;
/** 消费者线程池 */
private ExecutorService executorService;
/** 将要运行的异步请求队列 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**正在运行的异步请求队列 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** 正在运行的同步请求队列 */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
=====
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
当正在运行的异步请求队列中的数量小于64并且正在运行的请求主机数小于5时
则把请求加载到runningAsyncCalls中并在线程池中执行,
否则就再入到readyAsyncCalls中进行缓存等待。
private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
return chain.proceed(originalRequest);
}