深入浅出OKHttp:高效网络请求处理指南

更新:11-20 名人轶事 我要投稿 纠错 投诉

很多朋友对于深入浅出OKHttp:高效网络请求处理指南和不太懂,今天就由小编来为大家分享,希望可以帮助到大家,下面一起来看看吧!

API设计轻量,基本上通过几行代码的链式调用就可以得到结果。支持同步和异步请求。同步请求会阻塞当前线程,异步请求不会阻塞当前线程。异步执行完成后,会执行相应的回调方法。它支持HTTP/2协议。通过HTTP/2,所有从客户端到同一服务器的请求都可以共享同一个Socket连接。如果请求不支持HTTP/2协议,Okhttp会在内部维护一个连接池,通过该连接池可以复用HTTP/1.x连接,减少延迟。透明的GZIP 处理可减少下载数据大小。请求的数据将被相应地缓存。下次请求时,如果服务器通知你304(表示数据没有改变),就会直接从缓存中读取数据,减少重复请求的次数。目前Okhttp最新版本为3.x,支持Android 2.3+。要在Java 应用程序中使用Okhttp,最低JRE 版本要求是1.7。

Okhttp API:http://square.github.io/okhttp/3.x/okhttp/

本文代码来自Okhttp官方Wiki中的示例代码。

下载Okhttp

在Gradle 中配置以下内容以在Android Studio 中使用它:

compile "com.squareup.okhttp3:okhttp:3.3.1"

核心类

我们在使用Okhttp进行开发时,主要涉及以下几个核心类:OkHttpClient、Request、Call、Response。

OkHttpClient

OkHttpClient代表HTTP请求的客户端类。在大多数应用程序中,我们应该只执行一次new OkHttpClient() 并将其保存为全局实例,以便在应用程序中的任何地方都只使用该实例对象。这样所有的HTTP请求就可以共享Response缓存、共享线程池和共享连接池。

默认情况下,直接执行OkHttpClient client=new OkHttpClient()即可实例化OkHttpClient对象。

可以配置OkHttpClient的一些参数,比如超时、缓存目录、代理、Authenticator等,那么就需要使用内部类OkHttpClient.Builder。设置如下:

OkHttpClient 客户端=new OkHttpClient.Builder()。

readTimeout(30, TimeUnit.SECONDS)。

缓存(缓存)。

代理(代理)。

验证器(验证器)。

建造(); OkHttpClient本身无法设置参数,需要借助其内部类Builder来设置参数。参数设置完成后,调用Builder的build方法即可获取配置参数的OkHttpClient对象。这些配置的参数将影响OkHttpClient对象生成的所有HTTP请求。

有时我们想为某个网络请求单独设置几个特殊的参数。例如,我们只想将某个请求的超时设置为一分钟,但我们还希望在OkHttpClient对象中保留其他参数设置。然后我们就可以调用OkHttpClient对象了。 newBuilder()方法,代码如下:

OkHttpClient 客户端=.

OkHttpClient clientWith60sTimeout=client.newBuilder().

readTimeout(60, TimeUnit.SECONDS)。

build();clientWith60sTimeout中的参数来自客户端中的配置参数,除了覆盖了读取超时参数外,其他参数与客户端中一致。

要求

Request类封装了请求消息信息:请求的Url地址、请求方法(如GET、POST等)、各种请求头(如Content-Type、Cookie)以及可选的请求体。 Request对象一般是通过内部类Request.Builder的链式调用生成的。

称呼

Call代表了一个实际的HTTP请求,是连接Request和Response的桥梁。通过Request对象的newCall()方法可以获取Call对象。 Call 对象支持同步和异步数据采集。

执行Call对象的execute()方法会阻塞当前线程获取数据。该方法返回一个Response 对象。执行Call对象的enqueue()方法不会阻塞当前线程。该方法接收一个Callback 对象。当异步获取数据时,会回调并执行Callback对象对应的方法。如果请求成功,则执行Callback对象的onResponse方法,并将Response对象传入该方法中;如果请求失败,则执行Callback对象的onFailure方法。回复

Response 类封装了响应消息信息:状态(200、404 等)、响应头(Content-Type、Server 等)和可选的响应正文。 Response对象可以通过Call对象的execute()方法获得。异步回调执行Callback对象的onResponse方法时也可以获得Response对象。

同步GET

以下示例演示如何同步发送GET请求,输出响应头,并将响应体转换为字符串。

私有最终OkHttpClient 客户端=new OkHttpClient();

公共无效运行()抛出异常{

请求request=new Request.Builder()

.url("http://publicobject.com/helloworld.txt")

。建造();

响应response=client.newCall(request).execute();

if (!response.isSuccessful()) throw new IOException("意外代码" + response);

标头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());

}下面对上述代码进行简单解释:

当客户端执行newCall方法时,会得到一个Call对象,代表一个新的网络请求。

Call对象的execute方法是一个同步方法,它会阻塞当前线程并返回一个Response对象。

您可以通过Response对象的isSuccessful()方法判断请求是否成功。

通过Response的headers()方法可以获取响应头对象,通过for循环索引可以遍历所有响应头的名称和值。响应头的名称可以通过Headers.name(index) 方法获取,响应头的值可以通过Headers.value(index) 方法获取。

除了索引遍历之外,还可以通过Headers.get(headerName) 方法获取响应头的值,比如通过headers.get("Content-Type") 获取服务器返回给客户端的数据类型。但服务器返回给客户端的响应头中可能存在多个同名的响应头。例如,在某个请求中,如果服务器想要给客户端设置多个Cookie,就会写入多个Set-Cookie响应头,并且这些Set-Cookie响应头的值是不同的。当您访问百度首页时,可以看到7个Set-Cookie响应头,如下图所示:

1.png 为了解决同时获取多个同名响应头的值的问题,Headers提供了一个公共的Listvalues(String name)方法,该方法会返回一个List对象,所以这里的values("Set-Cookie")可以获取所有Cookie信息。如果调用Headers 对象的get("Set-Cookie") 方法,则只会获取到最后的Cookie 信息。

响应主体ResponseBody对象可以通过Response对象的body()方法获取。调用其string()方法可以轻松将响应体中的数据转换为字符串。该方法会将所有数据放入内存中。因此,如果数据超过1M,最好不要调用string()方法,避免占用过多内存。在这种情况下,您可以考虑将数据作为Stream 进行处理。

异步GET

以下示例演示如何异步发送GET 网络请求。代码如下:

私有最终OkHttpClient 客户端=new OkHttpClient();

公共无效运行()抛出异常{

请求request=new Request.Builder()

.url("http://publicobject.com/helloworld.txt")

。建造();

client.newCall(请求).enqueue(new Callback() {

@覆盖

公共无效onFailure(调用调用,IOException e){

e.printStackTrace();

}

@覆盖

public void onResponse(Call call, Response response) throws IOException {

if (!response.isSuccessful()) throw new IOException("意外代码" + response);

标头responseHeaders=response.headers();

for (int i=0, size=responseHeaders.size(); i 大小; i++) {

System.out.println(responseHeaders.name(i) + ":" + responseHeaders.value(i));

}

System.out.println(response.body().string());

}

});

}下面是对上述代码的说明:

要异步执行网络请求,需要执行Call对象的enqueue方法。该方法接收一个okhttp3.Callback 对象。 enqueue方法不会阻塞当前线程,而是会开启一个新的工作线程,让实际的网络请求在工作线程中执行。通常,该工作线程的名称以“Okhttp”开头,并包含连接主机信息。例如,上例中工作线程的名称是“Okhttp http://publicobject.com/.”

当异步请求成功时,会回调Callback对象的onResponse方法,在该方法中可以获得Response对象。当异步请求失败或者调用Call对象的cancel方法时,会回调Callback对象的onFailure方法。 onResponse 和onFailure 方法都是在工作线程中执行的。

请求头和响应头

典型的HTTP请求头和响应头与Map类似,每个名称对应一个值。但是,正如我们之前提到的,也会出现多个重复的名称。例如,对应的结果中可能存在多个Set-Cookie响应头。同样,可能同时存在多个同名的请求头。上面我们已经提到了响应头的读取,这里不再赘述。一般情况下,我们只需要调用header(name, value)方法来设置请求头的name和value即可。调用该方法将保证整个请求头中不会出现多个同名的名称。如果要添加多个同名的请求头,应该调用addHeader(name, value)方法,这样就可以添加名称重复的请求头,并且它们的值可以不同,例如如下:

私有最终OkHttpClient 客户端=new OkHttpClient();

公共无效运行()抛出异常{

请求request=new Request.Builder()

.url("https://api.github.com/repos/square/okhttp/issues")

.header("用户代理", "OkHttp Headers.java")

.addHeader("接受", "application/json; q=0.5")

.addHeader("接受", "application/vnd.github.v3+json")

。建造();

响应response=client.newCall(request).execute();

if (!response.isSuccessful()) throw new IOException("意外代码" + response);

System.out.println("Server:" + response.header("服务器"));

System.out.println("Date:" + response.header("日期"));

System.out.println("Vary:" + response.headers("Vary"));

}上面的代码通过addHeader方法添加了两个Accept请求头,并且两者的值不同。这样,服务器收到客户端的请求后,就知道客户端既支持application/json类型的数据,又支持application。 /vnd.github.v3+json类型数据。

用POST发送String

您可以使用POST方法发送请求正文。下面的例子演示了如何将markdown文本作为请求体发送到服务器,服务器将其转换为html文档并发送给客户端。

公共静态最终媒体类型MEDIA_TYPE_MARKDOWN

=MediaType.parse("text/x-markdown; charset=utf-8");

私有最终OkHttpClient 客户端=new OkHttpClient();

公共无效运行()抛出异常{

字符串postBody=""

+ "发布n"

+ "--------n"

+ "n"

+ " * _1.0_ 2013 年5 月6 日n"

+ " * _1.1_ 2013 年6 月15 日n"

+ " * _1.2_ 2013 年8 月11 日n";

请求request=new Request.Builder()

.url("https://api.github.com/markdown/raw")

.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))

。建造();

响应response=client.newCall(request).execute();

if (!response.isSuccessful()) throw new IOException("意外代码" + response);

System.out.println(response.body().string());

}上面的代码解释如下:

Request.Builder 的post 方法接收一个RequestBody 对象。

RequestBody 是请求正文。一般情况下,可以通过调用该类的五个重载的静态create()方法来获取RequestBody对象。 create()方法的第一个参数是MediaType类型,create()方法的第二个参数可以是String、File、byte[]或okio.ByteString。除了调用create()方法之外,还可以调用RequestBody的writeTo()方法向其中写入数据。 writeTo() 方法通常在使用POST 发送Stream 时使用。

MediaType是指要传输的数据的MIME类型。 MediaType对象包含三种类型的信息:类型、子类型和CharSet。一般情况下,这些信息会被传递到parse()方法中,以便可以解析MediaType对象,如上面的例子。在文本/x-markdown 中; charset=utf-8,type值为text,表示是文本的大类;/下面的x-markdown是subtype,表示它是markdown在text大类下的子类; charset=utf-8 表示使用UTF-8编码。如果您不知道某类数据的MIME类型,可以参考Connection Media Types and MIME参考手册,其中更详细地列出了所有数据的MIME类型。以下是几种常见数据的MIME类型值:

json: application/jsonxml: application/xmlpng: image/pngjpg: image/jpeggif: image/gif 在本例中,请求体将被放置在内存中,因此应避免使用此API 发送超过1M 的数据。

用POST发送Stream流

以下示例演示如何使用POST 发送流。

公共静态最终媒体类型MEDIA_TYPE_MARKDOWN

=MediaType.parse("text/x-markdown; charset=utf-8");

私有最终OkHttpClient 客户端=new OkHttpClient();

公共无效运行()抛出异常{

RequestBody requestBody=new RequestBody() {

@覆盖

公共媒体类型内容类型(){

返回MEDIA_TYPE_MARKDOWN;

}

@覆盖

公共无效writeTo(BufferedSink 接收器) 抛出IOException {

sink.writeUtf8("数字n");

ink.writeUtf8("--------n");

for (int i=2; i=997; i++) {

sink.writeUtf8(String.format(" * %s=%sn", i, 因子(i)));

}

}

私有字符串因子(int n){

for (int i=2; i n; i++) {

int x=n/i;

if (x * i==n) 返回因子(x) + " " + i;

}

返回Integer.toString(n);

}

};

请求request=new Request.Builder()

.url("https://api.github.com/markdown/raw")

.post(请求正文)

。建造();

响应response=client.newCall(request).execute();

if (!response.isSuccessful()) throw new IOException("意外代码" + response);

System.out.println(response.body().string());

}上面的代码解释如下:

上面的代码在实例化RequestBody对象时重写了contentType()和writeTo()方法。

重写contentType() 方法并返回markdown 类型的MediaType。

重写writeTo() 方法。该方法将传入一个Okia BufferedSink 类型对象。可以通过BufferedSink的各种写入方法向其写入各种类型的数据。在这个例子中,它的writeUtf8方法用于将UTF写入其中。 -8 文本数据。还可以通过其outputStream()方法获取输出流OutputStream,并通过OutputSteram将数据写入BufferedSink。

用POST发送File

以下代码演示了如何使用POST 发送文件。

公共静态最终媒体类型MEDIA_TYPE_MARKDOWN

=MediaType.parse("text/x-markdown; charset=utf-8");

私有最终OkHttpClient 客户端=new OkHttpClient();

公共无效运行()抛出异常{

文件file=new File("README.md");

请求request=new Request.Builder()

.url("https://api.github.com/markdown/raw")

.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, 文件))

。建造();

响应response=client.newCall(request).execute();

if (!response.isSuccessful()) throw new IOException("意外

d code " + response); System.out.println(response.body().string()); }我们之前提到,RequestBody.create()静态方法可以接收File参数,将File转换成请求体,将其传递给post()方法。

用POST发送Form表单中的键值对

如果想用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了,服务器端会其自动解码。

用POST发送multipart数据

我们可以通过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处理JSON响应

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.Entryentry : gist.files.entrySet()) { System.out.println(entry.getKey()); System.out.println(entry.getValue().content); } } static class Gist { Mapfiles; } static class GistFile { String content; }下面对以上代码进行说明: Gist类对应着整个JSON的返回结果,Gist中的Mapfiles对应着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)); }下面对以上代码进行说明: 我们在App的cache目录下创建了一个子目录okhttp,将其作为Okhttp专门用于缓存的目录,并设置其上限为10M,Okhttp需要能够读写该目录。 不要让多个缓存实例同时访问同一个缓存目录,因为多个缓存实例会相互影响,导致出错,甚至系统崩溃。在绝大多数的App中,我们只应该执行一次new OkHttpClient(),将其作为全局的实例进行保存,从而在App的各处都只使用这一个实例对象,这样所有的HTTP请求都可以共用Response缓存。 上面代码,我们对于同一个URL,我们先后发送了两个HTTP请求。第一次请求完成后,Okhttp将请求到的结果写入到了缓存目录中,进行了缓存。response1.networkResponse()返回了实际的数据,response1.cacheResponse()返回了null,这说明第一次HTTP请求的得到的响应是通过发送实际的网络请求,而不是来自于缓存。然后对同一个URL进行了第二次HTTP请求,response2.networkResponse()返回了null,response2.cacheResponse()返回了缓存数据,这说明第二次HTTP请求得到的响应来自于缓存,而不是网络请求。 如果想让某次请求禁用缓存,可以调用request.cacheControl(CacheControl.FORCE_NETWORK)方法,这样即便缓存目录有对应的缓存,也会忽略缓存,强制发送网络请求,这对于获取最新的响应结果很有用。如果想强制某次请求使用缓存的结果,可以调用request.cacheControl(CacheControl.FORCE_CACHE),这样不会发送实际的网络请求,而是读取缓存,即便缓存数据过期了,也会强制使用该缓存作为响应数据,如果缓存不存在,那么就返回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请求头。 如果用户名密码有问题,那么Okhttp会一直用这个错误的登录信息尝试登录,我们应该判断如果之前已经用该用户名密码登录失败了,就不应该再次登录,这种情况下需要让Authenticator对象的authenticate()方法返回null,这就避免了没必要的重复尝试,代码片段如下所示: if (credential.equals(response.request().header("Authorization"))) { return null; }

ResponseBody

通过Response的body()方法可以得到响应体ResponseBody,响应体必须最终要被关闭,否则会导致资源泄露、App运行变慢甚至崩溃。 ResponseBody和Response都实现了Closeable和AutoCloseable接口,它们都有close()方法,Response的close()方法内部直接调用了ResponseBody的close()方法,无论是同步调用execute()还是异步回调onResponse(),最终都需要关闭响应体,可以通过如下方法关闭响应体: Response.close()Response.body().close()Response.body().source().close()Response.body().charStream().close()Response.body().byteString().close()Response.body().bytes()Response.body().string()对于同步调用,确保响应体被关闭的最简单的方式是使用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.

文章到此结束,如果本次分享的深入浅出OKHttp:高效网络请求处理指南和的问题解决了您的问题,那么我们由衷的感到高兴!

用户评论

凉城°

这篇文章终于讲解了Okhttp的使用方法!一直想知道怎么更好地控制网络请求,这下可以开始尝试了。

    有14位网友表示赞同!

一点一点把你清空

学习网络编程一直是我的目标,这篇文章正好能帮到我入门Okhttp,感觉很实用。

    有16位网友表示赞同!

心悸╰つ

之前用的是老旧的HttpClient,想尝试一下更现代的Okhttp,看看它的优势在哪里。

    有8位网友表示赞同!

作业是老师的私生子

期待深入了解Okhttp的异步请求机制和缓存功能,这些对我现在在做的项目很有帮助。

    有18位网友表示赞同!

疯人疯语疯人愿

学习Android开发就离不开网络请求,使用Okhttp可以提升效率,希望这篇博客讲解详细

    有8位网友表示赞同!

男神大妈

感觉Okhttp应该是目前Android开发者使用得较多的网络请求库了,学习它还是很值得的。

    有5位网友表示赞同!

半世晨晓。

Okhttp在缓存方面做得怎么样?这篇文章里会不会提到具体的处理方式?

    有16位网友表示赞同!

笑傲苍穹

我正在做一个需要大量数据交互的项目,想用Okhttp提高效率,看看这篇文章能不能给我一些思路。

    有5位网友表示赞同!

搞搞嗎妹妹

以前没听说过Okhttp,这篇文章正好让我了解一下它的用法和特点。学习新知识真开心!

    有17位网友表示赞同!

伪心

这篇博客能讲明白Okhttp如何实现网络请求的拦截机制吗?这是我想知道的一点。

    有6位网友表示赞同!

夏日倾情

希望这篇文章能让新手更容易入门Okhttp的使用,不要写得太專業了。

    有13位网友表示赞同!

棃海

Okhttp的支持Http2协议看起来很强大,文章里会不会解释一下具体是怎么实现的?

    有5位网友表示赞同!

晨与橙与城

学习Okhttp可以让我更好地控制网络请求流程,提升代码的鲁棒性,这真是个好工具!

    有17位网友表示赞同!

走过海棠暮

看到文章标题就想试一试Okhttp,方便快捷的网络请求库就最好了。

    有13位网友表示赞同!

逃避

如果想使用Okhttp来处理数据加密和解密,这篇文章里会提到吗?

    有14位网友表示赞同!

君临臣

了解Okhttp的好处后,我更想要学习一下它的具体的使用方法了,期待文章讲解详细!

    有7位网友表示赞同!

心已麻木i

希望这篇文章能提供一些实际案例,让我更好地理解Okhttp的使用方式。

    有12位网友表示赞同!

嗯咯

这篇博客的介绍是否包含了 OkHttp 的历史背景?

    有12位网友表示赞同!

【深入浅出OKHttp:高效网络请求处理指南】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活

上一篇:你是否为我自豪?关切依然,你的子女已成长为独立个体! 下一篇:《奇幻漫画推荐:妖精标本》