引用
compile 'com.squareup.okhttp3:okhttp:3.3.1'
核心类
OkHttpClient
OkHttpClient表示了HTTP请求的客户端类,在绝大多数的App中,我们只应该执行一次new OkHttpClient(),将其作为全局的实例进行保存,从而在App的各处都只使用这一个实例对象,这样所有的HTTP请求都可以共用Response缓存、共用线程池以及共用连接池。
//生成默认对象
OkHttpClient client = new OkHttpClient();
//通过OkHttpClient.Builder()初始化参数,包括超时时间、缓存目录、代理、Authenticator等
OkHttpClient client = new OkHttpClient.Builder().
readTimeout(30, TimeUnit.SECONDS).
cache(cache).
proxy(proxy).
authenticator(authenticator).
build();
//对之前生成的client对象重新初始化超时时间,其他属性不变,只有超时时间覆盖而已
OkHttpClient clientWith60sTimeout = client.newBuilder().
readTimeout(60, TimeUnit.SECONDS).
build();
Request
Request类封装了请求报文信息:请求的Url地址、请求的方法(如GET、POST等)、各种请求头(如Content-Type、Cookie)以及可选的请求体。一般通过内部类Request.Builder
的链式调用生成Request对象。
Call
Call代表了一个实际的HTTP请求,它是连接Request和Response的桥梁,通过Request对象的newCall()
方法可以得到一个Call对象。Call对象既支持同步获取数据,也可以异步获取数据。
- 执行Call对象的
execute()
方法,会阻塞当前线程去获取数据,该方法返回一个Response对象。 - 执行Call对象的enqueue()方法,不会阻塞当前线程,该方法接收一个Callback对象,当异步获取到数据之后,会回调执行Callback对象的相应方法。如果请求成功,则执行Callback对象的onResponse方法,并将Response对象传入该方法中;如果请求失败,则执行Callback对象的onFailure方法。
Response
Response类封装了响应报文信息:状态吗(200、404等)、响应头(Content-Type、Server等)以及可选的响应体。可以通过Call对象的execute()方法获得Response对象,异步回调执行Callback对象的onResponse方法时也可以获取Response对象
请求
String url = "http://www.baidu.com";
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), "test content"))
.build();
Call call = okHttpClient.newCall(request);
//同步请求
try {
Response response=call.execute();
} catch (IOException e) {
e.printStackTrace();
}
//异步请求
call.enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.body().string());
}
});
添加请求头
Request request = new Request.Builder()
.url(url)
.addHeader("name","123")
.build();
添加请求体
Request request = new Request.Builder()
.url(url)
.post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), "test content"))
.build();
Request.post接受的是一个RequestBody对象,一般可以通过create函数得到RequestBody对象,create()对象的第一个参数MediaType
类型,第二个参数可以是String、File、byte[]或okio.ByteString
MediaType指的是要传递的数据的MIME类型,MediaType
对象包含了三种信息:type、subtype以及CharSet,一般将这些信息传入parse()方法中,这样就能解析出MediaType
对象,比如在上例中application/json; charset=utf-8,type值是application
/后面的json
是subtype,如果不知道某种类型数据的MIME类型,可以参见连接Media Types和MIME 参考手册,较详细地列出了所有的数据的MIME类型。以下是几种常见数据的MIME类型
- json :application/json
- xml:application/xml
- png:image/png
- jpg: image/jpeg
- gif:image/gif
Ps:在该例中,请求体会放置在内存中,所以应该避免用该API发送超过1M的数据。
缓存响应结果
如果想缓存响应结果,我们就需要为Okhttp配置缓存目录,Okhttp可以写入和读取该缓存目录,并且我们需要限制该缓存目录的大小。Okhttp的缓存目录应该是私有的,不能被其他应用访问。
Okhttp中,多个缓存实例同时访问同一个缓存目录会出错,大部分的应用只应该调用一次new OkHttpClient(),然后为其配置缓存目录,然后在App的各个地方都使用这一个OkHttpClient实例对象,否则两个缓存实例会互相影响,导致App崩溃。
int cacheSize = 10 * 1024 * 1024; // 10 MiB
String okhttpCachePath = getCacheDir().getPath() + File.separator + "okhttp";
File okhttpCache = new File(okhttpCachePath);
if(!okhttpCache.exists()){
okhttpCache.mkdirs();
}
Cache cache = new Cache(okhttpCache, cacheSize);
OkHttpClient client = new OkHttpClient();
client = new OkHttpClient.Builder()
.cache(cache)
.build();
取消请求
当请求不再需要的时候,我们应该中止请求,比如退出当前的Activity了,那么在Activity中发出的请求应该被中止。可以通过调用Call的cancel方法立即中止请求,如果线程正在写入Request或读取Response,那么会抛出IOException异常。
//在Activity销毁的时候,可以手动调用
call.cancel();
设置超时
一次HTTP请求实际上可以分为三步:
- 客户端与服务器建立连接。
- 客户端发送请求数据到服务器,即数据上传
- 服务器将响应数据发送给客户端,即数据下载
三者都默认设置为10秒
client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS) //连接超时
.writeTimeout(10, TimeUnit.SECONDS) //上传超时
.readTimeout(30, TimeUnit.SECONDS) //读取超时
.build();
处理身份验证
有些网络请求是需要用户名密码登录的,如果没提供登录需要的信息,那么会得到401 Not Authorized未授权的错误,这时候Okhttp会自动查找是否配置了Authenticator,如果配置过Authenticator,会用Authenticator中包含的登录相关的信息构建一个新的Request,尝试再次发送HTTP请求。
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.authenticator(new Authenticator() {
@Nullable
@Override
public Request authenticate(Route route, Response response) throws IOException {
List<Challenge> challenges = response.challenges();
String credential = Credentials.basic("username", "password");
return response.request().newBuilder()
.header("Authorization", credential) //响应码为401时需要填写的header
.header("Proxy-Authorization", credential)//响应码为407时需要填写的header
.build();
//如用用户名密码有问题,return null
}
})
.build();
自定义拦截器
okHttp的自定义拦截器分两种
-
应用拦截器(Application interceptor):
为与整个拦截链的第一位
请求中只会执行一次
不会收到重定向和重试等影响
-
网络拦截器(NetworkInterceptor interceptor)
- 位于拦截链的倒数第二位
- 会因为重定向或者请求重试等影响而执行多次
使用
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.addNetworkInterceptor(new LoggingInterceptor())
.readTimeout(60L, TimeUnit.SECONDS)
.connectTimeout(60L, TimeUnit.SECONDS);
OkHttpClient client = clientBuilder.build();
在拦截器中可以加入一下诸如请求响应日志打印或者统一添加header等行为
public class LoggingInterceptor implements Interceptor {
private long t1, t2;
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.header("Content-Type", "application/json")
.header("Client-Agent", agent)
.build();
logging(request);
Response response = chain.proceed(request);
return logging(response);
}
private void logging(Request request) {
t1 = System.nanoTime();
Logger.d("---------------------------------------");
Logger.d(request.headers().toString());
Logger.d(String.format(Locale.CHINA, "Sending request %s on %n%s", request.url().toString(), Utils.bodyToString(request)));
Logger.d("---------------------------------------");
}
private Response logging(Response response){
try {
t2 = System.nanoTime();
String s = response.body().string();
Logger.d("---------------------------------------");
Logger.d(String.format(Locale.CHINA, "Received response for %s in %.1fms %n%s", response.request().url(), (t2 - t1) / 1e6d, s));
Logger.d("---------------------------------------");
ResponseBody newBody = ResponseBody.create(response.body().contentType(), s);
return response.newBuilder().body(newBody).build();
}catch (Exception e){
e.printStackTrace();
}
return response;
}