HTTP是现代应用常用的一种交换数据和媒体的网络方式,高效地使用HTTP能让资源加载更快,节省带宽。OkHttp是一个高效的HTTP客户端,它有以下默认特性:
支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接
连接池减少请求延时
透明的GZIP压缩减少响应数据的大小
缓存响应内容,避免一些完全重复的请求
当网络出现问题的时候OkHttp依然坚守自己的职责,它会自动恢复一般的连接问题,如果你的服务有多个IP地址,当第一个IP请求失败时,OkHttp会交替尝试你配置的其他IP,OkHttp使用现代TLS技术(SNI, ALPN)初始化新的连接,当握手失败时会回退到TLS 1.0。
note: OkHttp 支持 Android 2.3 及以上版本Android平台, 对于 Java, JDK 1.7及以上.
okhttp的GitHub地址如下: 点击跳转
本文demo中使用的接口为 玩安卓 点击跳转 网站提供.
另外,如果你的targetSdkVersion ≥28.还需要注意以下问题:
由于Android P系统限制了明文流量的网络请求,网络请求url要么使用https. 如果使用的只能是http,有2种处理方式:
方法一:
targetSdkVersion 降级回到 27
方法二:
在res目录下新建xml文件夹,文件夹里面创建network_security_config.xml 文件;
文件内容
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
然后在 AndroidManifest.xml 的application 标签加上
android:networkSecurityConfig="@xml/network_security_config"
如果不进行上述操作,在使用okhttp3请求时,会报如下错误:
CLEARTEXT communication to * not permitted by network 。
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
从4.0.0开始,okhttp使用kotlin编码, 因后面涉及okhttp的源码分析,本系列文章若无特殊说明均使用 3.10.0 版本.
<uses-permission android:name="android.permission.INTERNET" />
最简单常用的网络权限.
如果使用 DiskLruCache
,那还得声明写外存的权限.
new OkHttpClient;
构造Request对象;
通过前两步中的对象构建Call对象;
通过Call#enqueue(Callback)方法来提交异步请求;
String url = "https://www.wanandroid.com/article/list/1/json";
OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url(url)
.get()//默认就是GET请求,可以不写
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "onFailure: ");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e(TAG, "onResponse: " + response.body().string());
}
});
前面几个步骤和异步方式一样,只是最后一部是通过 Call#execute()
来提交请求,注意这种方式会阻塞调用线程,所以在Android中应放在子线程中执行,否则有可能引起ANR异常。
String url = "https://www.wanandroid.com/article/list/1/json";
OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url(url)
.build();
final Call call = okHttpClient.newCall(request);
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = call.execute();
Log.e(TAG, "run: " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody requestBody = new FormBody.Builder()
.add("username", "weibuzudao")
.add("password", "123456")
.build();
Request request = new Request.Builder()
.url("https://www.wanandroid.com/user/login")
.post(requestBody)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "onFailure: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e(TAG, "onResponse: " + response.body().string());
}
});
提交表单时,使用 RequestBody
的实现类FormBody
来描述请求体,它可以携带一些经过编码的 key-value
请求体,键值对存储在下面两个集合中:
private final List<String> encodedNames;
private final List<String> encodedValues;
OkHttp的拦截器链可谓是其整个框架的精髓,用户可传入的 interceptor
分为两类:
一类是全局的 interceptor
,该类 interceptor
在整个拦截器链中最早被调用,通过 OkHttpClient.Builder#addInterceptor(Interceptor)
传入;
另外一类是非网页请求的 interceptor
,这类拦截器只会在非网页请求中被调用,并且是在组装完请求之后,真正发起网络请求前被调用,所有的 interceptor
被保存在 List
集合中,按照添加顺序来逐个调用,具体可参考 RealCall#getResponseWithInterceptorChain()
方法。通过 OkHttpClient.Builder#addNetworkInterceptor(Interceptor)
传入;
这里举一个简单的例子,例如有这样一个需求,我要监控App通过 OkHttp 发出的所有原始请求,以及整个请求所耗费的时间,针对这样的需求就可以使用第一类全局的 interceptor 在拦截器链头去做。
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
ResponseBody body = response.body();
if (body != null) {
Log.d(TAG, "onResponse: " + response.body().string());
body.close();
}
}
});
public class LoggingInterceptor implements Interceptor {
private static final String TAG = "LoggingInterceptor";
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long startTime = System.nanoTime();
Log.d(TAG, String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long endTime = System.nanoTime();
Log.d(TAG, String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (endTime - startTime) / 1e6d, response.headers()));
return response;
}
}
针对这个请求,打印出来的结果
Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example
Received response for https://publicobject.com/helloworld.txt in 1265.9ms
Server: nginx/1.10.0 (Ubuntu)
Date: Wed, 28 Mar 2018 08:19:48 GMT
Content-Type: text/plain
Content-Length: 1759
Last-Modified: Tue, 27 May 2014 02:35:47 GMT
Connection: keep-alive
ETag: "5383fa03-6df"
Accept-Ranges: bytes
注意到一点是这个请求做了重定向,原始的 request url 是 http://www.publicobject.com/helloworld.tx,而响应的 request url 是 https://publicobject.com/helloworld.txt,这说明一定发生了重定向,但是做了几次重定向其实我们这里是不知道的,要知道这些的话,可以使用 addNetworkInterceptor()去做。
推荐让 OkHttpClient
保持单例,用同一个 OkHttpClient
实例来执行你的所有请求,因为每一个 OkHttpClient
实例都拥有自己的连接池和线程池,重用这些资源可以减少延时和节省资源,如果为每个请求创建一个 OkHttpClient
实例,显然就是一种资源的浪费。当然,也可以使用如下的方式来创建一个新的 OkHttpClient
实例,它们共享连接池、线程池和配置信息。
OkHttpClient eagerClient = client.newBuilder()
.readTimeout(500, TimeUnit.MILLISECONDS)
.build();
Response response = eagerClient.newCall(request).execute();
每一个Call
(其实现是RealCall
)只能执行一次,否则会报异常,具体参见 RealCall#execute()