在Android开发中,发送HTTP请求是很常见的。SDK中自带的HttpURLConnection虽然能基本满足需求,但是在使用上有诸多不便,为此,square公司实现了一个HTTP客户端的类库——Okhttp 。
Okhttp是一个支持HTTP 和 HTTP/2 的客户端,可以在Android和Java应用程序中使用,其具有以下特点:
当前Okhttp最新的版本是3.x,支持Android 2.3+。要想在Java应用程序中使用Okhttp,JRE的最低版本要求是1.7。
Okhttp API: http://square.github.io/okhttp/3.x/okhttp/
本文中的代码来自于Okhttp的官方Wiki中的示例代码。
可以点此下载最新的Okhttp的jar包,也可以通过Maven获取:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.3.1</version>
</dependency>
我们在使用Okhttp进行开发的时候,主要牵扯到以下几个核心类:OkHttpClient、Request、Call 和 Response。
OkHttpClient
OkHttpClient表示了HTTP请求的客户端类,在绝大多数的App中,我们只应该执行一次new OkHttpClient()
,将其作为全局的实例进行保存,从而在App的各处都只使用这一个实例对象,这样所有的HTTP请求都可以共用Response缓存、共用线程池以及共用连接池。
默认情况下,直接执行OkHttpClient client = new OkHttpClient()
就可以实例化一个OkHttpClient对象。
可以配置OkHttpClient的一些参数,比如超时时间、缓存目录、代理、Authenticator等,那么就需要用到内部类OkHttpClient.Builder,设置如下所示:
OkHttpClient client = new OkHttpClient.Builder().
readTimeout(30, TimeUnit.SECONDS).
cache(cache).
proxy(proxy).
authenticator(authenticator).
build();
OkHttpClient本身不能设置参数,需要借助于其内部类Builder设置参数,参数设置完成后,调用Builder的build方法得到一个配置好参数的OkHttpClient对象。这些配置的参数会对该OkHttpClient对象所生成的所有HTTP请求都有影响。
newBuilder()
方法,代码如下所示:OkHttpClient client = ...
OkHttpClient clientWith60sTimeout = client.newBuilder().
readTimeout(60, TimeUnit.SECONDS).
build();
clientWith60sTimeout中的参数来自于client中的配置参数,只不过它覆盖了读取超时时间这一个参数,其余参数与client中的一致。
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对象。
以下示例演示了如何同步发送GET请求,输出响应头以及将响应体转换为字符串。
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.body().string());
}
下面对以上代码进行简单说明:
isSuccessful()
方法可以判断请求是否成功。headers()
方法可以得到响应头Headers对象,可以通过for循环索引遍历所有的响应头的名称和值。可以通过Headers.name(index)
方法获取响应头的名称,通过Headers.value(index)
方法获取响应头的值。Headers.get(headerName)
方法也可以获取某个响应头的值,比如通过headers.get(“Content-Type”)获得服务器返回给客户端的数据类型。但是服务器返回给客户端的响应头中有可能有多个重复名称的响应头,比如在某个请求中,服务器要向客户端设置多个Cookie,那么会写入多个Set-Cookie
响应头,且这些Set-Cookie
响应头的值是不同的,访问百度首页,可以看到有7个Set-Cookie
的响应头,如下图所示:为了解决同时获取多个name相同的响应头的值,Headers
中提供了一个public List values(String name)
方法,该方法会返回一个List
对象,所以此处通过Headers对象的values(‘Set-Cookie’)可以获取全部的Cookie信息,如果调用Headers对象的get(‘Set-Cookie’)方法,那么只会获取最后一条Cookie信息。
body()
方法可以得到响应体ResponseBody对象,调用其string()
方法可以很方便地将响应体中的数据转换为字符串,该方法会将所有的数据放入到内存之中,所以如果数据超过1M,最好不要调用string()
方法以避免占用过多内存,这种情况下可以考虑将数据当做Stream流处理。以下示例演示了如何异步发送GET网络请求,代码如下所示:
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
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());
}
});
}
下面对以上代码进行一下说明:
okhttp3.Callback
对象,enqueue方法不会阻塞当前线程,会新开一个工作线程,让实际的网络请求在工作线程中执行。一般情况下这个工作线程的名字以“Okhttp”开头,并包含连接的host信息,比如上面例子中的工作线程的name是"Okhttp http://publicobject.com/..."
典型的HTTP请求头、响应头都是类似于Map
,每个name对应一个value值。不过像我们之前提到的,也会存在多个name重复的情况,比如相应结果中就有可能存在多个Set-Cookie
响应头,同样的,也可能同时存在多个名称一样的请求头。响应头的读取我们在上文已经说过了,在此不再赘述。一般情况下,我们只需要调用header(name, value)
方法就可以设置请求头的name和value,调用该方法会确保整个请求头中不会存在多个名称一样的name。如果想添加多个name相同的请求头,应该调用addHeader(name, value)
方法,这样可以添加重复name的请求头,其value可以不同,例如如下所示:
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
.header("User-Agent", "OkHttp Headers.java")
.addHeader("Accept", "application/json; q=0.5")
.addHeader("Accept", "application/vnd.github.v3+json")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println("Server: " + response.header("Server"));
System.out.println("Date: " + response.header("Date"));
System.out.println("Vary: " + response.headers("Vary"));
}
上面的代码通过addHeader
方法添加了两个Accept
请求头,且二者的值不同,这样服务器收到客户端发来的请求后,就知道客户端既支持application/json
类型的数据,也支持application/vnd.github.v3+json
类型的数据。
可以使用POST方法发送请求体。下面的示例演示了如何将markdown文本作为请求体发送到服务器,服务器会将其转换成html文档,并发送给客户端。
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
String postBody = ""
+ "Releases\n"
+ "--------\n"
+ "\n"
+ " * _1.0_ May 6, 2013\n"
+ " * _1.1_ June 15, 2013\n"
+ " * _1.2_ August 11, 2013\n";
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
下面对以上代码进行说明:
Request.Builder
的post方法接收一个RequestBody对象。
RequestBody就是请求体,一般可通过调用该类的5个重载的static的create()
方法得到RequestBody对象。create()
方法第一个参数都是MediaType类型,create()
方法的第二个参数可以是String、File、byte[]或okio.ByteString。除了调用create()
方法,还可以调用RequestBody的writeTo()方法向其写入数据,writeTo()
方法一般在用POST发送Stream流的时候使用。
MediaType指的是要传递的数据的MIME类型,MediaType
对象包含了三种信息:type、subtype以及CharSet,一般将这些信息传入parse()方法中,这样就能解析出MediaType
对象,比如在上例中text/x-markdown; charset=utf-8
,type值是text,表示是文本这一大类;/
后面的x-markdown
是subtype,表示是文本这一大类下的markdown这一小类;charset=utf-8
则表示采用UTF-8编码。如果不知道某种类型数据的MIME类型,可以参见连接Media Types和MIME 参考手册,较详细地列出了所有的数据的MIME类型。以下是几种常见数据的MIME类型值:
json :application/json
xml:application/xml
png:image/png
jpg: image/jpeg
gif:image/gif
在该例中,请求体会放置在内存中,所以应该避免用该API发送超过1M的数据。
下面的示例演示了如何使用POST发送Stream流。
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
RequestBody requestBody = new RequestBody() {
@Override public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
}
@Override public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("Numbers\n");
sink.writeUtf8("-------\n");
for (int i = 2; i <= 997; i++) {
sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
}
}
private String factor(int n) {
for (int i = 2; i < n; i++) {
int x = n / i;
if (x * i == n) return factor(x) + " × " + i;
}
return Integer.toString(n);
}
};
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
下面对以上代码进行说明:
RequestBody
对象的时候重写了contentType()
和writeTo()
方法。contentType()
方法,返回markdown类型的MediaType。writeTo()
方法,该方法会传入一个Okia的BufferedSink类型的对象,可以通过BufferedSink
的各种write方法向其写入各种类型的数据,此例中用其writeUtf8方法向其中写入UTF-8的文本数据。也可以通过它的outputStream()方法,得到输出流OutputStream,从而通过OutputSteram向BufferedSink写入数据。下面的代码演示了如何用POST发送文件。
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
File file = new File("README.md");
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
我们之前提到,RequestBody.create()静态方法可以接收File参数,将File转换成请求体,将其传递给post()方法。
如果想用POST发送键值对字符串,可以使用在post()方法中传入FormBody对象,FormBody
继承自RequestBody
,类似于Web前端中的Form
表单。可以通过FormBody.Builder
构建FormBody
。
示例代码如下所示:
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
RequestBody formBody = new FormBody.Builder()
.add("search", "Jurassic Park")
.build();
Request request = new Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(formBody)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
需要注意的是,在发送数据之前,Android会对非ASCII码字符调用encodeURIComponent方法进行编码,例如”Jurassic Park”会编码成”Jurassic%20Park”,其中的空格符被编码成%20了,服务器端会其自动解码。
我们可以通过Web前端的Form
表单上传一个或多个文件,Okhttp也提供了对应的功能,如果我们想同时发送多个Form
表单形式的文件,就可以使用在post()方法中传入MultipartBody对象。MultipartBody
继承自RequestBody
,也表示请求体。只不过MultipartBody
的内部是由多个part组成的,每个part
就单独包含了一个RequestBody
请求体,所以可以把MultipartBody
看成是一个RequestBody
的数组,而且可以分别给每个RequestBody
单独设置请求头。
示例代码如下所示:
private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "Square Logo")
.addFormDataPart("image", "logo-square.png",
RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
.build();
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
下面对以上代码进行说明:
MultipartBody
要通过其内部类MultipartBody.Builder进行构建。MultipartBody.Builder
的setType()方法设置MultipartBody
的MediaType
类型,一般情况下,将该值设置为MultipartBody.FORM,即W3C定义的multipart/form-data
类型,详见Forms in HTML documents。MultipartBody.Builder
的方法addFormDataPart(String name, String value)或addFormDataPart(String name, String filename, RequestBody body)添加数据,其中前者添加的是字符串键值对数据,后者可以添加文件。MultipartBody.Builder
还提供了三个重载的addPart
方法,其中通过addPart(Headers headers, RequestBody body)方法可以在添加RequestBody
的时候,同时为其单独设置请求头。Gson是Google开源的一个用于进行JSON处理的Java库,通过Gson可以很方面地在JSON和Java对象之间进行转换。我们可以将Okhttp和Gson一起使用,用Gson解析返回的JSON结果。
下面的示例代码演示了如何使用Gson解析GitHub API的返回结果。
private final OkHttpClient client = new OkHttpClient();
private final Gson gson = new Gson();
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://api.github.com/gists/c2a7c39532239ff261be")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue().content);
}
}
static class Gist {
Map<String, GistFile> files;
}
static class GistFile {
String content;
}
下面对以上代码进行说明:
Gist
类对应着整个JSON的返回结果,Gist
中的Map files
对应着JSON中的files
。files
中的每一个元素都是一个key-value的键值对,key是String类型,value是GistFile
类型,并且GistFile
中必须包含一个String content
。OkHttp.txt
就对应着一个GistFile
对象,其content值就是GistFile
中的content。Gist gist = gson.fromJson(response.body().charStream(), Gist.class)
,将JSON字符串转换成了Java对象。将ResponseBody
的charStream方法返回的Reader
传给Gson
的fromJson
方法,然后传入要转换的Java类的class。如果想缓存响应结果,我们就需要为Okhttp配置缓存目录,Okhttp可以写入和读取该缓存目录,并且我们需要限制该缓存目录的大小。Okhttp的缓存目录应该是私有的,不能被其他应用访问。
Okhttp中,多个缓存实例同时访问同一个缓存目录会出错,大部分的应用只应该调用一次new OkHttpClient()
,然后为其配置缓存目录,然后在App的各个地方都使用这一个OkHttpClient
实例对象,否则两个缓存实例会互相影响,导致App崩溃。
缓存示例代码如下所示:
private final OkHttpClient client;
public CacheResponse(File cacheDirectory) throws Exception {
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);
client = new OkHttpClient.Builder()
.cache(cache)
.build();
}
public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
Response response1 = client.newCall(request).execute();
if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);
String response1Body = response1.body().string();
System.out.println("Response 1 response: " + response1);
System.out.println("Response 1 cache response: " + response1.cacheResponse());
System.out.println("Response 1 network response: " + response1.networkResponse());
Response response2 = client.newCall(request).execute();
if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);
String response2Body = response2.body().string();
System.out.println("Response 2 response: " + response2);
System.out.println("Response 2 cache response: " + response2.cacheResponse());
System.out.println("Response 2 network response: " + response2.networkResponse());
System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));
}
下面对以上代码进行说明:
new OkHttpClient()
,将其作为全局的实例进行保存,从而在App的各处都只使用这一个实例对象,这样所有的HTTP请求都可以共用Response缓存。response1.networkResponse()
返回了实际的数据,response1.cacheResponse()
返回了null,这说明第一次HTTP请求的得到的响应是通过发送实际的网络请求,而不是来自于缓存。然后对同一个URL进行了第二次HTTP请求,response2.networkResponse()
返回了null,response2.cacheResponse()
返回了缓存数据,这说明第二次HTTP请求得到的响应来自于缓存,而不是网络请求。504 Unsatisfiable Request
错误。也可以向请求中中加入类似于Cache-Control: max-stale=3600
之类的请求头,Okhttp也会使用该配置对缓存进行处理。当请求不再需要的时候,我们应该中止请求,比如退出当前的Activity了,那么在Activity中发出的请求应该被中止。可以通过调用Call的cancel方法立即中止请求,如果线程正在写入Request或读取Response,那么会抛出IOException
异常。同步请求和异步请求都可以被取消。
示例代码如下所示:
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
.build();
final long startNanos = System.nanoTime();
final Call call = client.newCall(request);
// Schedule a job to cancel the call in 1 second.
executor.schedule(new Runnable() {
@Override public void run() {
System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
call.cancel();
System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
}
}, 1, TimeUnit.SECONDS);
try {
System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
Response response = call.execute();
System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
(System.nanoTime() - startNanos) / 1e9f, response);
} catch (IOException e) {
System.out.printf("%.2f Call failed as expected: %s%n",
(System.nanoTime() - startNanos) / 1e9f, e);
}
}
上述请求,服务器端会有两秒的延时,在客户端发出请求1秒之后,请求还未完成,这时候通过cancel方法中止了Call,请求中断,并触发IOException
异常。
一次HTTP请求实际上可以分为三步:
由于网络、服务器等各种原因,这三步中的每一步都有可能耗费很长时间,导致整个HTTP请求花费很长时间或不能完成。
为此,可以通过OkHttpClient.Builder
的connectTimeout()方法设置客户端和服务器建立连接的超时时间,通过writeTimeout()方法设置客户端上传数据到服务器的超时时间,通过readTimeout()方法设置客户端从服务器下载响应数据的超时时间。
在2.5.0版本之前,Okhttp默认不设置任何的超时时间,从2.5.0版本开始,Okhttp将连接超时、写入超时(上传数据)、读取超时(下载数据)的超时时间都默认设置为10秒。如果HTTP请求需要更长时间,那么需要我们手动设置超时时间。
示例代码如下所示:
private final OkHttpClient client;
public ConfigureTimeouts() throws Exception {
client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
}
public void run() throws Exception {
Request request = new Request.Builder()
.url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
.build();
Response response = client.newCall(request).execute();
System.out.println("Response completed: " + response);
}
如果HTTP请求的某一部分超时了,那么就会触发异常。
有些网络请求是需要用户名密码登录的,如果没提供登录需要的信息,那么会得到401 Not Authorized
未授权的错误,这时候Okhttp会自动查找是否配置了Authenticator
,如果配置过Authenticator
,会用Authenticator
中包含的登录相关的信息构建一个新的Request,尝试再次发送HTTP请求。
示例代码如下所示:
private final OkHttpClient client;
public Authenticate() {
client = new OkHttpClient.Builder()
.authenticator(new Authenticator() {
@Override public Request authenticate(Route route, Response response) throws IOException {
System.out.println("Authenticating for response: " + response);
System.out.println("Challenges: " + response.challenges());
String credential = Credentials.basic("jesse", "password1");
return response.request().newBuilder()
.header("Authorization", credential)
.build();
}
})
.build();
}
public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/secrets/hellosecret.txt")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
上面对以上代码进行说明:
OkHttpClient.Builder
的authenticator()方法接收一个Authenticator对象,我们需要实现Authenticator
对象的authenticate()方法,该方法需要返回一个新的Request
对象,该新的Request
对象基于原始的Request
对象进行拷贝,而且要通过header("Authorization", credential)
方法对其设置登录授权相关的请求头信息。Response
对象的challenges()方法可以得到第一次请求失败的授权相关的信息。如果响应码是401 unauthorized
,那么会返回”WWW-Authenticate”相关信息,这种情况下,要执行OkHttpClient.Builder
的authenticator()
方法,在Authenticator
对象的authenticate()
中 对新的Request对象调用header("Authorization", credential)
方法,设置其Authorization
请求头;如果Response的响应码是407 proxy unauthorized
,那么会返回”Proxy-Authenticate”相关信息,表示不是最终的服务器要求客户端登录授权信息,而是客户端和服务器之间的代理服务器要求客户端登录授权信息,这时候要执行OkHttpClient.Builder
的proxyAuthenticator()方法,在Authenticator
对象的authenticate()
中 对新的Request对象调用header("Proxy-Authorization", credential)
方法,设置其Proxy-Authorization
请求头。Authenticator
对象的authenticate()
方法返回null,这就避免了没必要的重复尝试,代码片段如下所示:if (credential.equals(response.request().header("Authorization"))) {
return null;
}
通过Response的body()方法可以得到响应体ResponseBody,响应体必须最终要被关闭,否则会导致资源泄露、App运行变慢甚至崩溃。
ResponseBody和Response都实现了Closeable
和AutoCloseable
接口,它们都有close()
方法,Response的close()
方法内部直接调用了ResponseBody的close()
方法,无论是同步调用execute()
还是异步回调onResponse()
,最终都需要关闭响应体,可以通过如下方法关闭响应体:
对于同步调用,确保响应体被关闭的最简单的方式是使用try
代码块,如下所示:
Call call = client.newCall(request);
try (Response response = call.execute()) {
... // Use the response.
}
将Response response = call.execute()
放入到try()
的括号之中,由于Response 实现了Closeable
和AutoCloseable
接口,这样对于编译器来说,会隐式地插入finally
代码块,编译器会在该隐式的finally
代码块中执行Response的close()
方法。
也可以在异步回调方法onResponse()
中,执行类似的try
代码块,try()
代码块括号中的ResponseBody也实现了Closeable
和AutoCloseable
接口,这样编译器也会在隐式的finally
代码块中自动关闭响应体,代码如下所示:
Call call = client.newCall(request);
call.enqueue(new Callback() {
public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
... // Use the response.
}
}
public void onFailure(Call call, IOException e) {
... // Handle the failure.
}
});
响应体中的数据有可能很大,应该只读取一次响应体的数据。调用ResponseBody的bytes()
或string()
方法会将整个响应体数据写入到内存中,可以通过source()
、byteStream()
或charStream()
进行流式处理。
参考:
http://square.github.io/okhttp/3.x/okhttp/
https://github.com/square/okhttp/wiki/Recipes
https://github.com/square/okhttp/blob/master/CHANGELOG.md
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/qq_43842093/article/details/121877245
内容来源于网络,如有侵权,请联系作者删除!