什么是OkHttp?
OkHttp是一个来自Square的HTTP客户端,用于Java和Android应用程序。它的设计是为了更快地加载资源并节省带宽。OkHttp在开源项目中被广泛使用,是Retrofit、Picasso等库的骨干。
以下是使用OkHttp的主要优势。
- 支持HTTP/2(有效使用套接字)
- 连接池(在没有HTTP/2的情况下减少请求延迟)
- GZIP压缩(缩小下载大小)
- 响应缓存(避免了重新获取相同的数据)
- 从常见的连接问题中无声恢复
- 替代IP地址检测(在IPv4和IPv6环境下)
- 支持现代TLS功能(TLS 1.3,ALPN,证书钉子)。
- 支持同步和异步调用
在本指南中,我们将通过为Android构建一个假想的待办事项列表应用程序来介绍OkHttp的基础知识。
首先,让我们为我们的待办事项列表应用程序定义一些功能需求。我们的用户希望能够从待办事项服务器上看到他们保存的待办事项,在服务器上保存一个新的待办事项,并且安全地、单独地访问他们自己的待办事项。
作为开发者,我们希望能够轻松地调试我们应用程序的网络通信,并减少服务器端的负载。
先决条件
稳定的OkHttp 4.x在Android 5.0+(API级别21+)和Java 8+上工作。如果你需要较低的Android和Java版本的支持,你仍然可以依靠OkHttp 3.12.x分支,但要注意一些问题。
当导入OkHttp时,它还会带来两个依赖项。Okio,一个高性能的I/O库,以及Kotlin标准库。你不需要单独导入这些。
要在你的Android项目中使用OkHttp,你需要在应用级Gradle文件中导入它。
implementation("com.squareup.okhttp3:okhttp:4.9.1")
不要忘记,在Android上,如果你想访问网络资源,你需要在你的应用程序的AndroidManifest.xml 文件中申请INTERNET 的权限。
<uses-permission android:name="android.permission.INTERNET"/>
设置OkHttp
为了让我们的用户从服务器上看到他们所有保存的待办事项,我们需要同步和异步的GET请求,以及查询参数。
GET请求
为了从服务器上获得我们的待办事项列表,我们需要执行一个GET HTTP请求。OkHttp通过Request.Builder ,提供了一个不错的API来构建请求。
同步的GET
做一个GET请求就像这样简单。
OkHttpClient client = new OkHttpClient();
Request getRequest = new Request.Builder()
.url("https://mytodoserver.com/todolist")
.build();
try {
Response response = client.newCall(getRequest).execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
正如你所看到的,这是一个用OkHttp执行请求的同步方式。(你应该在一个非UI线程上运行,否则,在你的应用程序中会有性能问题,而且Android会抛出一个错误)。
异步GET
这个请求的异步版本为你提供了一个回调,当响应被取走或发生错误时。
OkHttpClient client = new OkHttpClient();
Request getRequest = new Request.Builder()
.url("https://mytodoserver.com/todolist")
.build();
client.newCall(getRequest).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
System.out.println(response.body().string());
}
});
注意:从现在开始,我将只展示同步版本的调用,以避免使用大量的模板代码。只要有可能,我也会尝试使用标准的Java APIs,以使代码在非Android环境中可重复使用。
OkHttp中的查询参数
你可以向你的请求传递查询参数,比如在服务器端实现对已完成或未完成的待办事项进行过滤。
OkHttpClient client = new OkHttpClient();
HttpUrl.Builder queryUrlBuilder = HttpUrl.get("https://mytodoserver.com/todolist").newBuilder();
queryUrlBuilder.addQueryParameter("filter", "done");
Request request = new Request.Builder()
.url(queryUrlBuilder.build())
.build();
try {
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
HttpUrl.Builder 将生成带有查询参数的适当URL。 [https://mytodoserver.com/todolist?filter=done](https://mytodoserver.com/todolist?filter=done).
你可能会理所当然地问:"为什么不直接使用手动创建的URL本身呢?"你可以。但是一旦你的URL构建逻辑变得更加复杂(更多的查询参数),那么这个类就会派上用场。该库的开发者有额外的理由来使用HttpUrl。
POST请求
现在我们已经从我们的服务器上下载了所有的待办事项。但是如何创建新的to-dos或将一个to-dos标记为完成?用一个简单的POST请求。
简单的POST请求
让我们向我们的端点发送POST请求。
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = new FormBody.Builder()
.add("new", "This is my new TODO")
.build();
Request postRequest = new Request.Builder()
.url("https://mytodoserver.com/new")
.post(requestBody)
.build();
try {
Response response = client.newCall(postRequest).execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
正如你所看到的,POST请求的主体是一个application/x-www-form-urlencoded key-value pair数据。但我们可以发送任何我们想要的类型。下面是一个以JSON为主体的例子。
OkHttpClient client = new OkHttpClient();
JSONObject jsonObject = new JSONObject();
jsonObject.put("todo_id", 123);
jsonObject.put("status", "done");
RequestBody requestJsonBody = RequestBody.create(
jsonObject.toString(),
MediaType.parse("application/json")
);
Request postRequest = new Request.Builder()
.url("https://mytodoserver.com/modify")
.post(requestJsonBody)
.build();
try {
Response response = client.newCall(postRequest).execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
文件上传
我们也有可能想给我们的新待办事项附上一个文件(如图片)。
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = new MultipartBody.Builder()
.addFormDataPart("new", "This is my new TODO")
.addFormDataPart("image", "attachment.png",
RequestBody.create(new File("path/of/attachment.png"), MediaType.parse("image/png"))
)
.setType(MultipartBody.FORM)
.build();
Request postRequest = new Request.Builder()
.url("https://mytodoserver.com/new")
.post(requestBody)
.build();
try {
Response response = client.newCall(postRequest).execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
与之前类似,我们执行一个多部分的HTTP请求,在那里我们可以附加所需的文件。
取消请求
在保存待办事项时,有可能不小心选择了错误的附件,所以与其等到上传结束,不如确保请求可以随时取消,并在以后用正确的值重新启动。
// same request as before
Request postRequest = new Request.Builder()
.url("https://mytodoserver.com/new")
.post(requestBody)
.build();
Call cancelableCall = client.newCall(postRequest);
try {
Response response = cancelableCall.execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
// ... few seconds later from an other thread
cancelableCall.cancel();
现在,我们拥有了在我们的应用程序中实现基本功能所需的所有知识。我们可以检查我们的待办事项列表,我们可以添加新的待办事项,我们也可以改变它们的状态。
让我们来看看我们应用程序的安全方面。
OkHttp中的安全和授权
在一个请求上设置一个HTTP头
我们的后端已经实现了一个基本的基于用户名/密码的认证,以避免看到和修改对方的待办事项。
现在访问我们的数据需要在我们的请求上设置一个Authorization 头。没有这个,请求可能会以401 Unauthorized 响应而失败。
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://mytodoserver.com/todolist")
.addHeader("Authorization", Credentials.basic("username", "password"))
.build();
try {
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
Request.Builder 上的addHeader() 方法将让我们指定任意多的自定义头信息。
现在,只有当有人知道我们的用户名和密码时,我们的敏感数据才能被访问。但是,如果有人在网络上监听,并试图用中间人攻击和伪造的证书劫持我们的请求怎么办?
OkHttp为你提供了一个简单的方法,通过使用证书平移器只信任你自己的证书。
在OkHttp中设置证书平移器
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
clientBuilder.certificatePinner(
new CertificatePinner.Builder().add(
"mytodoserver.com","sha256/public_key_hash_of_my_certification"
).build()
);
OkHttpClient client = clientBuilder.build();
在这里,我们使用OkHttpClient.Builder 来建立一个自定义的OkHttp客户端(后面会有更多的介绍)。然后,通过CertificatePinner ,我们选择哪些特定域名的证书被信任。
关于证书锁定和一般安全的更多信息,请访问相关的OkHttp文档页面。
用OkHttp进行调试
如果在发出请求时发生了问题,我们必须深入挖掘它发生的原因。OkHttp有自己的内部API来启用调试日志,这可以帮助我们。但是我们也可以利用OkHttp的拦截器API来使我们的生活更轻松。
拦截器
拦截器可以监控、重写和重试调用。我们可以利用它们在请求发出之前对其进行修改,在响应到达我们的逻辑之前对其进行预处理,或者简单地打印出一些关于请求的细节。
OkHttp有自己的预制日志拦截器,我们可以通过Gradle导入。
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
并使用它。
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build();
或者我们可以实现我们自己的自定义拦截器。
static class BasicLoggingInterceptor implements Interceptor {
@NotNull
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
System.out.println(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
System.out.println(String.format("Received response for %s %n%s",
response.request().url(), response.headers()));
return response;
}
}
// ...
// usage later on
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new BasicLoggingInterceptor())
.build();
我们也可以根据我们的需要,在应用和网络层面上声明我们的拦截器。你可以在这里阅读更多关于这方面的内容。
代理
有时候,操作我们后端API的响应是很有用的。我们可以通过操作服务器端的代码来实现这个目的,但是通过代理服务器会更有效率。
我们可以在设备本身使用一个系统范围内的代理配置,或者指示我们的OkHttp客户端在内部使用一个代理。
Proxy proxyServerOnLocalNetwork = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress("192.168.1.100", 8080) // the local proxy
);
OkHttpClient client = new OkHttpClient.Builder()
.proxy(proxyServerOnLocalNetwork)
.build();
OkHttp中的缓存
在我们调试了我们的应用程序之后,你可能已经注意到,我们完成了很多不必要的请求,给我们的服务器带来了额外的负载。如果后台没有变化,就没有必要再去获取待办事项列表。
在OkHttp中有一个默认的缓存实现,我们只需要指定缓存位置和它的大小,像这样。
OkHttpClient client = new OkHttpClient.Builder()
.cache(new Cache(new File("/local/cacheDirectory"), 10 * 1024 * 1024)) //10 MB
.build();
但如果你想自定义行为,你可以对它进行疯狂的操作。
如果你有自定义的缓存逻辑,你也可以实现你自己的缓存方式。例如,你可以先向你的服务器执行一个HEAD 请求,然后检查缓存指示头,如果有变化,就向同一个URL执行一个GET 请求来获取内容。
OkHttp配置
我们已经介绍了OkHttpClient.Builder 的一些用法。如果我们想改变默认的OkHttp客户端行为,这个类是很有用的。
有一些参数值得一提。
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache) // configure cache, see above
.proxy(proxy) // configure proxy, see above
.certificatePinner(certificatePinner) // certificate pinning, see above
.addInterceptor(interceptor) // app level interceptor, see above
.addNetworkInterceptor(interceptor) // network level interceptor, see above
.authenticator(authenticator) // authenticator for requests (it supports similar use-cases as "Authorization header" earlier
.callTimeout(10000) // default timeout for complete calls
.readTimeout(10000) // default read timeout for new connections
.writeTimeout(10000) // default write timeout for new connections
.dns(dns) // DNS service used to lookup IP addresses for hostnames
.followRedirects(true) // follow requests redirects
.followSslRedirects(true) // follow HTTP tp HTTPS redirects
.connectionPool(connectionPool) // connection pool used to recycle HTTP and HTTPS connections
.retryOnConnectionFailure(true) // retry or not when a connectivity problem is encountered
.cookieJar(cookieJar) // cookie manager
.dispatcher(dispatcher) // dispatcher used to set policy and execute asynchronous requests
.build();
完整的列表,请访问文档。
WebSocket
想到了一个合作的待办事项列表?或者一旦有新的待办事项加入就通知用户?在一个待办事项上进行实时聊天如何?OkHttp在这里也为你提供了帮助。
如果你已经完成了WebSocket服务器端的实现,你可以连接到该端点,并从OkHttp客户端获得实时信息传递。
OkHttpClient client = new OkHttpClient();
String socketServerUrl = "ws://mytodoserver.com/realtime";
Request request = new Request.Builder().url(socketServerUrl).build();
// connecting to a socket and receiving messages
client.newWebSocket(request, new WebSocketListener() {
@Override
public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
super.onClosed(webSocket, code, reason);
//TODO: implement your own event handling
}
@Override
public void onClosing(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
super.onClosing(webSocket, code, reason);
//TODO: implement your own event handling
}
@Override
public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) {
super.onFailure(webSocket, t, response);
//TODO: implement your own event handling
}
@Override
public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
super.onMessage(webSocket, text);
//TODO: implement your own event handling for incoming messages
}
@Override
public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString bytes) {
super.onMessage(webSocket, bytes);
//TODO: implement your own event handling for incoming messages
}
@Override
public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
super.onOpen(webSocket, response);
//TODO: implement your own event handling
}
});
// sending message
webSocket.send("new_todo_added");
测试
我们不能忘记测试。OkHttp提供了自己的MockWebServer来帮助测试HTTP和HTTPS网络调用。它可以让我们指定对哪个请求返回哪个响应,并验证该请求的每个部分。
要开始,我们需要通过Gradle导入它。
testImplementation("com.squareup.okhttp3:mockwebserver:4.9.1")
这里有一些重要的API。
MockWebServer.start(): 启动本地主机上的模拟网络服务器MockWebServer.enqueue(mockResponse): 排一个MockResponse。这是一个先进先出的队列,确保请求将按照排队的顺序收到响应。MockResponse:一个可编写的OkHttp响应RecordRequest:一个HTTP请求,该请求被收到了。MockWebServerMockWebServer.takeRequest():把下一个到达的请求转给一个队列。MockWebServer
一旦我们理解了这些基础知识,我们就可以编写我们的第一个测试。现在,对于一个基本的GET请求。
public class MockWebServerTest {
final MockWebServer server = new MockWebServer();
final OkHttpClient client = new OkHttpClient();
@Test
public void getRequest_Test() throws Exception {
final String jsonBody = "{'todo_id': '1'}";
// configure a MockResponse for the first request
server.enqueue(
new MockResponse()
.setBody(jsonBody)
.addHeader("Content-Type", "application/json")
);
// start the MockWebServer
server.start();
// create a request targeting the MockWebServer
Request request = new Request.Builder()
.url(server.url("/"))
.header("User-Agent", "MockWebServerTest")
.build();
// make the request with OkHttp
Call call = client.newCall(request);
Response response = call.execute();
// verify response
assertEquals(200, response.code());
assertTrue(response.isSuccessful());
assertEquals("application/json", response.header("Content-Type"));
assertEquals(jsonBody, response.body().string());
// verify the incoming request on the server-side
RecordedRequest recordedRequest = server.takeRequest();
assertEquals("GET", recordedRequest.getMethod());
assertEquals("MockWebServerTest", recordedRequest.getHeader("User-Agent"));
assertEquals(server.url("/"), recordedRequest.getRequestUrl());
}
}
结论
总之,OkHttp是一个强大的库,它提供了大量的好处,包括HTTP/2支持,连接问题的恢复机制,缓存,以及现代TLS支持。
如果你曾经试图通过默认的Android和Java网络API从头开始实现这些功能,你就会知道这是多么大的工作量和痛苦(以及有多少边缘情况是你忘记涵盖的)。幸运的是,用OkHttp在你的应用程序中实现网络,使之变得简单。
更多细节,请访问项目页面和GitHub。你可以找到一些有用的扩展、实现样本和测试实例。
The postA complete guide to OkHttpappeared first onLogRocket Blog.