网络请求

笔者没有把 OkHttp 归纳到 Android 体系内,还是放置到了 Java 体系内。Retrofit 本身就是对于 OkHttp 的封装。

Comparison( 比较 )

目前基本上每个应用都会使用 HTTP/HTTPS 协议来作为主要的传输协议来传输数据。即使你没有直接使用 HTTP 协议,也会有成堆的 SDK 会包含这些协议,譬如分析、Crash 反馈等等。当然,目前也有很多优秀的 HTTP 的协议库,可以很方便的帮助开发者构建应用,本篇博文中会尽可能地涵盖这些要点。Android 的开发者在选择一个合适的 HTTP 库时需要考虑很多的要点,譬如在使用 Apache Client 或者 HttpURLConnection 时可能会考虑:

  • 能够取消现有的网络请求

  • 能够并发请求

  • 连接池能够复用存在的 Socket 连接

  • 本地对于响应的缓存

  • 简单的异步接口来避免主线程阻塞

  • 对于 REST API 的封装

  • 重连策略

  • 能够有效地载入与传输图片

  • 支持对于 JSON 的序列化

  • 支持 SPDY、HTTP/2 最早的时候 Android 只有两个主要的 HTTP 客户端: HttpURLConnection, Apache HTTP Client。根据 Google 官方博客的内容,HttpURLConnection 在早期的 Android 版本中可能存在一些 Bug:

在 Froyo 版本之前,HttpURLConnection 包含了一些很恶心的错误。特别是对于关闭可读的 InputStream 时候可能会污染整个连接池。

同样,Google 官方并不想转到 Apache HTTP Client 中:

Apache HTTP Client 中复杂的 API 设计让人们根本不想用它,Android 团队并不能够有效地工作。

而对于大部分普通开发者而言,它们觉得应该根据不同的版本使用不同的客户端。对于 Gingerbread(2.3) 以及之后的版本,HttpURLConnection 会是最佳的选择,它的 API 更简单并且体积更小。透明压缩与数据缓存可以减少网络压力,提升速度并且能够节约电量。当我们审视 Google Volley 的源代码的时候,可以看得出来它也是根据不同的 Android 版本选择了不同的底层的网络请求库:

if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}

不过这样会很让开发者头疼,2013 年,Square 为了解决这种分裂的问题发布了 OkHttp。OkHttp 是直接架构与 Java Socket 本身而没有依赖于其他第三方库,因此开发者可以直接用在 JVM 中,而不仅仅是 Android。为了简化代码迁移速度,OkHttp 也实现了类似于 HttpUrlConnection 与 Apache Client 的接口。

OkHttp 获得了巨大的社区的支持,以至于 Google 最终是将它作为了 Android 4.4 默认的 Engine,并且会在 5.1 之后弃用 Apache Client。目前 OkHttp V2.5.0 支持如下特性:

  • HTTP/2 以及 SPDY 的支持多路复用

  • 连接池会降低并发连接数

  • 透明 GZIP 加密减少下载体积

  • 响应缓存避免大量重复请求

  • 同时支持同步的阻塞式调用与异步回调式调用

笔者关于 OkHttp 最喜欢的一点是它能够将异步请求较好的展示:

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(Request request, Throwable throwable) {
throwable.printStackTrace();
}
@Override
public void onResponse(Response response) throws IOException {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
});
}

这个用起来非常方便,因为往往大的数据请求都不能放置在 UI 主线程中进行。事实上,从 Android 3.0(Honeycomb 11) 开始,所有的网络操作都必须强制在单独的线程中进行。在当时如果要把 HttpUrlConnection 和 AsyncTask 结合起来使用,还是比较复杂的。而 2013 年的 Google IO 大会上,Google 提出了 Volley,一个提供了如下便利的 HTTP 库:

  • Automatic scheduling of network requests.

  • Multiple concurrent network connections.

  • Transparent disk and memory response caching with standard HTTP cache coherence.

  • Support for request prioritization.

  • Cancellation request API. You can cancel a single request, or you can set blocks or scopes of requests to cancel.

  • Ease of customization, for example, for retry and backoff.

  • Strong ordering that makes it easy to correctly populate your UI with data fetched asynchronously from the network.

  • Debugging and tracing tools.

Volley 主要架构在 HttpUrlConnection 之上,如果希望能够抓取图片或者 JSON 数据,Volley 有自定义的抽象类型 ImageRequest 与 JsonObjectRequest,可以自动转化为 HTTP 请求。同时,Volley 也有一个硬编码的网络连接池大小:

private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

不过 OkHttp 可以自定义连接池的大小:

private int maxRequests = 64;
private int maxRequestsPerHost = 5;
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));

在某些情况下,OkHttp 可以通过使用多线程来有更好的性能体现。不过如果现有的程序中已经用 Volley 做了顶层封装,那么也可以使用HttpStack implementation这个来使用 OkHttp 的请求与响应接口来替换 HttpUrlConnection。

到这里已经可以发现,OkHttp 本质上是自定义了一套底层的网络请求架构。目前 HTTP 客户端已经逐步转化为了支持大量图片,特别是那种无限滚动与图片传输的应用。同时,REST API 已经成为了业界标准,基本上每位开发者都需要处理大量标准化的任务,类似于 JSON 序列化与将 REST 请求映射到 Java 的接口上。Square 也在不久之后针对这两个问题提出了自己的解决方案:

  • Retrofit - 一个类型安全的 HTTP 客户端支持 REST 接口

  • Picasso - 针对 Android 的图片下载与缓存库

Retrofit 提供了一个面向 Java 代码与 REST 接口之间的桥接,可以迅速将 HTTP API 转化到 Java 接口中并且自动生成带有完整文档的实现:

public interface GitHubService {
@GET("/users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.build();
GitHubService service = retrofit.create(GitHubService.class);

除此之外,Retrofit 也支持面向 JSON、XML 以及 Protocol Buffers 的数据转化。在另一篇博客中将 AsyncTask 与 Volley 以及 Retrofit 做了一个比较,其性能对比如下: