Android OkHttp常用详解
OkHttp不需要多介绍了,已经是网络框架界的大佬了,很多网络框架都基于OkHttp封装,也有很多涉及到网络的第三方框架都可以支持使用OkHttp替换网络。
OkHttp的4.0.x版本已经全部由java
替换到了Kotlin
,API的一些使用也会有些不同,具体的参考Upgrading to OkHttp 4
由于不熟悉Kotlin代码,本文使用的OkHttp的版本为
3.14.2
,是3.14.x的最后一个版本
接入
OkHttp在3.13.x
以上的版本需要在Android 5.0+ (API level 21+)和Java 1.8的环境开发。
同时还需要再添加Okio的依赖库,而Okio在1.x
版本是基于Java实现的,2.x
则是Kotlin实现的。
dependencies {
//...
//OkHttp
implementation 'com.squareup.okhttp3:okhttp:3.14.2'
implementation 'com.squareup.okio:okio:1.17.4'
}
3.12.x
以及以下的版本支持Android 2.3+ (API level 9+)和Java 1.7的开发环境
Get请求
请求分为同步请求和异步请求,先看看同步请求
public void getSyn(final String url) {
new Thread(new Runnable() {
@Override
public void run() {
try {
//创建OkHttpClient对象
OkHttpClient client = new OkHttpClient();
//创建Request
Request request = new Request.Builder()
.url(url)//访问连接
.get()
.build();
//创建Call对象
Call call = client.newCall(request);
//通过execute()方法获得请求响应的Response对象
Response response = call.execute();
if (response.isSuccessful()) {
//处理网络请求的响应,处理UI需要在UI线程中处理
//...
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
这就是一段同步Get请求的代码,同步网络请求需要在子线程中执行,而处理UI需要回到UI线程中处理。
在看看Get的异步请求,这时就不需要自己创建子线程了,但是处理UI同样需要在UI线程中处理,不能再请求响应的回调方法中处理
public void getAsyn(String url) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//...
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if(response.isSuccessful()){
String result = response.body().string();
//处理UI需要切换到UI线程处理
}
}
});
}
Request.Builder
中默认的使用Get请求,所以可以不调用get()
方法
看了两种不同的Get请求,基本流程都是先创建一个OkHttpClient
对象,然后通过Request.Builder()
创建一个Request
对象,OkHttpClient
对象调用newCall()
并传入Request
对象就能获得一个Call
对象。而同步和异步不同的地方在于execute()
和enqueue()
方法的调用,调用execute()
为同步请求并返回Response
对象;调用enqueue()
方法测试通过callback的形式返回Response
对象。
注意:无论是同步还是异步请求,接收到
Response
对象时均在子线程中,其中通过Response
对象获取请求结果需要在子线程中完成,在得到结果后再切换到UI线程改变UI
Post请求
Post请求与Get请求不同的地方在于Request.Builder
的post()
方法,post()
方法需要一个RequestBody
的对象作为参数
public void post(String url,String key,String value){
OkHttpClient client = new OkHttpClient();
FormBody body = new FormBody.Builder()
.add(key,value)
.build();
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//...
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if(response.isSuccessful()){
String result = response.body().string();
//处理UI需要切换到UI线程处理
}
}
});
}
RequestBody
是一个抽象类,分别有FormBody
和MultipartBody
两个子类,上面这个例子使用的是FormBody
,用于传输表单类型的参数。MultipartBody
则支持多类型的参数传递,例如:在传输表单类型的参数的同时,还是可以传输文件。创建一个MultipartBody
对象再调用post()
方法就OK了。
MultipartBody body = new MultipartBody.Builder()
// 添加表单参数
// .addFormDataPart(key,value)
.addFormDataPart(name, fileName, RequestBody.create(MediaType.get("application/octet-stream"), file))
.build();
RequestBody
前面的代码可以看到使用了RequestBody.create()
方法,改方法的返回值也是一个RequestBody
对象。
其实Post请求就是包含了RequestBody
对象,MultipartBody
则是可以支持多中以及多个RequestBody
对象。
RequestBody
提供了5个重载的create()
静态方法,如下图
如果只需要上传文件,请求就比较简单了,如下:
public void uploadFile(String url, File file) {
OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(MediaType.get("application/octet-stream"), file);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Call call = client.newCall(request);
//call.enqueue();
//...
}
这个MediaType.get()
的传值是"application/octet-stream"
,这是二进制流传输,在不知道文件类型的情况下可以这么操作,具体的传参可以参考Content-Type的类型使用对照表
PS:如果要监听文件的上传进度就没这么简单了
这是生成一个上传JSON的RequestBody
对象的代码
MediaType jsonType = MediaType.parse("application/json; charset=utf-8");
String jsonStr = "{\"username\":\"Sia\"}";//json数据.
RequestBody body = RequestBody.create(jsonType, josnStr);
设置超时时间
OkHttp可以设置调用、连接和读写的超时时间,都是通过OkHttpClient.Builder
设置的。如果不主动设置,OkHttp将使用默认的超时设置。
OkHttpClient mClient = new OkHttpClient.Builder()
.callTimeout(6_000, TimeUnit.MILLISECONDS)
.connectTimeout(6_000, TimeUnit.MILLISECONDS)
.readTimeout(20_000, TimeUnit.MILLISECONDS)
.writeTimeout(20_000, TimeUnit.MILLISECONDS)
.build();
设置请求Header
请求的Header是通过Request.Builder
对象的相关方法来维护的,如下:
- headers(Headers headers)
- header(String name, String value)
- addHeader(String name, String value)
- removeHeader(String name)
addHeader
和removeHeader
方法比较好理解,分别是添加和移除header信息。header(String name, String value)
这是会重新设置指定name
的header信息。
headers(Headers headers)
则是会移除掉原有的所有header信息,将参数headers
的header信息添加到请求中。这是这几个方法的一些差别。
使用的话都是Builder模式的链式调用,举个栗子
Request request = new Request.Builder()
.header("Accept","image/webp")
.addHeader("Charset","UTF-8")
.url(url)
.build();
Cookie也是header信息中的一个字段,通过Header相关方法添加就好了
请求部分的基础使用基本上就这些了,具体的一些用法可以参考官方文档https://square.github.io/okhttp/recipes/
Interceptors(拦截器)
拦截器是OkHttp当中一个比较强大的机制,可以监视、重写和重试调用请求。
这是一个比较简单的Interceptor
的实现,对请求的发送和响应进行了一些信息输出。
class LoggingInterceptor implements Interceptor {
public static final String TAG = "Http_log";
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
Log.i(TAG, String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
Log.i(TAG, String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
需要实现其中intercept(Interceptor.Chain chain)
方法,同时必须调用chain.proceed(request)
代码,也就是网络请求真正发生的地方。
拦截器可以设置多个,并且拦截器的调用是有顺序的。官网举的例子是,同时添加一个压缩拦截器和一个校验拦截器,需要决定数据是先被压缩在校验,还是先校验在压缩。
拦截器还分为应用拦截器(Application Interceptors)和网络拦截器(Network Interceptors)
上图可以看出应用拦截器是处于应用和OkHttp核心之间,而网络拦截器则是在OkHttp核心与网络之间,这里就直接搬运官网的示例,使用LoggingInterceptor
来看一下两种拦截器的差异。
OkHttp自身有5个Interceptor的实现,有兴趣可以阅读源码
- RetryAndFollowUpInterceptor
- BridgeInterceptor
- CacheInterceptor
- ConnectInterceptor
- CallServerInterceptor
Application Interceptors
先看看应用拦截器,通过OkHttpClient.Builder
的addInterceptor
方法添加拦截器
OkHttpClient client = 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();
Response response = client.newCall(request).execute();
response.body().close();
看请求和响应的两个链接是不同的,URL http://www.publicobject.com/helloworld.txt
会重定向到 https://publicobject.com/helloworld.txt
,OkHttp会自动跟随重定向,而应用拦截器只被调用一次,并且chain.proceed()
返回的Response
对象是具有重定向响应对象。
INFO: Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example
INFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
Network Interceptors
再来看看网络拦截器,通过OkHttpClient.Builder
的addNetworkInterceptor
方法添加拦截器
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
Response response = client.newCall(request).execute();
response.body().close();
结果日志:
INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
INFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/html
Content-Length: 193
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt
INFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}
User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
INFO: Received response for https://publicobject.com/helloworld.txt in 80.9ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
从日志来看,拦截器运行了两次,第一次请求了http://www.publicobject.com/helloworld.txt
,第二次则是重定向到https://publicobject.com/helloworld.txt
。同时通过网络拦截能获得更多的header信息。更多的关于Interceptor
的使用以及它们各自的优缺点,请参考OkHttp官方说明文档。
其他
OkHttp已经出来很久了,本文只是为了完成自己的执念吧!还有Events的使用没有写出来,这个东西平常也没用过,后续研究了会补充上。