如何更高效地使用 OkHttp
来自: http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0222/3988.html
-
原文链接: Effective OkHttp
-
原文作者 : Michael Parker
-
译文出自 : 掘金翻译计划
-
译者 : Brucezz
-
校对者: iThreeKing , Adam Shen , Jaeger
-
转载请联系,并注明出处。
在为 可汗学院 开发 Android app 时, OkHttp 是一个很重要的开源库。虽然它的默认配置已经提供了很好的效果,但是我们还是采取了一些措施提高 OkHttp 的可用性和自我检查能力:
1. 在文件系统中开启响应缓存
有些响应消息通过包含 Cache-Control HTTP 首部字段允许缓存,但是默认情况下,OkHttp 并不会缓存这些响应消息。因此你的客户端可能会因为不断请求相同的资源而浪费时间和带宽,而不是简单地读取一下首次响应消息的缓存副本。
为了在文件系统中开启响应缓存,需要配置一个 com.squareup.okhttp.Cache 实例,然后把它传递给 OkHttpClient 实例的 setCache 方法。你必须用一个表示目录的 File 对象和最大字节数来实例化 Cache 对象。那些能够缓存的响应消息会被写在指定的目录中。如果已缓存的响应消息导致目录内容超过了指定的大小,响应消息会按照最近最少使用( LRU Policy )的策略被移除。
正如 Jesse Wilson 所建议的 ,我们将响应消息缓存在 context.getCacheDir() 的子文件夹中:
// 缓存根目录,由这里推荐 -> http://stackoverflow.com/a/32752861/400717. // 小心可能为空,参考下面两个链接 // https://groups.google.com/d/msg/android-developers/-694j87eXVU/YYs4b6kextwJ 和 // http://stackoverflow.com/q/4441849/400717. final @Nullable File baseDir = context.getCacheDir(); if (baseDir != null) { final File cacheDir = new File(baseDir, "HttpResponseCache"); okHttpClient.setCache(new Cache(cacheDir, HTTP_RESPONSE_DISK_CACHE_MAX_SIZE)); }
在可汗学院的应用中,我们指定了 HTTP_RESPONSE_DISK_CACHE_MAX_SIZE 的大小为 10 * 1024 * 1024 ,即 10MB。
2. 集成 Stetho
Stetho 是一个 非死book 出品的超赞的开源库,它可以让你用 Chrome 的功能—— 开发者工具 来检查调试你的 Android 应用。
Stetho 不仅能够检查应用的 SQLite 数据库和视图层次,还可以检查 OkHttp 的每一条请求和响应消息:
这种自我检查方式(Introspection)有效地确保了服务器返回允许缓存资源的 HTTP 首部时,且核缓存资源存在时,不再发出任何请求。
开启 Stetho,只用简单地添加一个 StethoInterceptor 实例到网络拦截器(Network Interceptor)的列表中去:
okHttpClient.networkInterceptors().add(new StethoInterceptor());
应用运行完毕之后,打开 Chrome 然后跳转到 chrome://inspect。设备、应用以及应用标识符信息会被陈列出来。直接点击“inspect”链接就可以打开开发者工具,然后切换到 Network 标签开始监测 OkHttp 发出的请求。
3. 使用 Picasso 和 Retrofit
可能和我们一样,你使用 Picasso 来加载网络图片,或者使用 Retrofit 来简化网络请求和解析响应消息。在默认情况下,如果你没有显式地指定一个 OkHttpClient,这些开源库会隐式地创建它们自己的 OkHttpClient 实例以供内部使用。以下代码来自于 Picasso 2.5.2 版本的OkHttpDownloader 类:
private static OkHttpClient defaultOkHttpClient() { OkHttpClient client = new OkHttpClient(); client.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); client.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); client.setWriteTimeout(Utils.DEFAULT_WRITE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); return client; }
Retrofit 也有类似的工厂方法用来创建它自己的 OkHttpClient。
图片是应用中需要加载的最大的资源之一。Picasso 是严格地按照 LRU 策略在内存中维护它的图片缓存。如果客户端尝试用 Picasso 加载一张图片,并且 Picasso 没有在内存缓存中找到该图片,那么它会委托内部的 OkHttpClient 实例来加载该图片。在默认情况下,由于前面的defaultOkHttpClient 方法没有在文件系统中配置响应缓存,该实例会一直从服务器加载图片。
自定义一个 OkHttpClient 实例,将从文件系统返回一个已缓存的响应消息这种情况考虑在内。没有一张图片直接从服务器加载。这在应用第一次加载时是尤为重要的。在这个时候,Picasso 的内存中的缓存是 “冷” 的,它会频繁地委托 OkHttpClient 实例去加载图片。
这就需要构建一个用你的 OkHttpClient 配置的 Picasso 实例。如果你在代码中使用Picasso.with(context).load(...) 来加载图片,你所使用的 Picasso 单例对象,是在with 方法中用自己的 OkHttpClient 延迟加载和配置的。因此我们必须在第一次调用 with方法之前指定自己的 Picasso 实例作为单例对象。
简单地把 OkHttpClient 实例包装到一个 OkHttpDownloader 对象中,然后传递给Picasso.Builder 实例的 downloader 方法:
final Picasso picasso = new Picasso.Builder(context) .downloader(new OkHttpDownloader(okHttpClient)) .build(); //客户端应该在任何需要的时候来创建这个实例 //以防万一,替换掉那个单例对象 Picasso.setSingletonInstance(picasso);
在 Retrofit 1.9.x 中,通过 RestAdapter 使用你的 OkHttpClient 实例,把 OkHttpClient实例包装到一个 OkClient 实例中,然后传递给 RestAdapter.Builder 实例的 setClient方法:
restAdapterBuilder.setClient(new OkClient(httpClient));
在 Retrofit 2.0 中,直接把 OkHttpClient 实例传递给 Retrofit.Builder 实例的 client即可。
在可汗学院的应用中,我们使用 Dagger 来确保只有一个 OkHttpClient 实例,而且 Picasso 和 Retrofit 都会使用到它。我们为带 @Singleton 注解的 OkHttpClient 实例创建了一个 provider:
@Provides @Singleton public OkHttpClient okHttpClient(final Context context, ...) { final OkHttpClient okHttpClient = new OkHttpClient(); configureClient(okHttpClient, ...); return okHttpClient; }
这个 OkHttpClient 实例随后通过 Dagger 注入到其他用来创建 RestAdapter 和 Picasso实例的 provider 里。
4. 设置用户代理拦截器(User-Agent Interceptor)
当客户端在每一次请求中都提供一个详细的 User-Agent 头部信息时,日志文件和分析数据提供了很有用的信息。默认情况下,OkHttp 的 User-Agent 值仅仅只有它的版本号。要设定你自己的 User-Agent,创建一个拦截器(Interceptor)然后替换掉默认值,参考 StackOverflow 上的建议 :
public final class UserAgentInterceptor implements Interceptor { private static final String USER_AGENT_HEADER_NAME = "User-Agent"; private final String userAgentHeaderValue; public UserAgentInterceptor(String userAgentHeaderValue) { this.userAgentHeaderValue = Preconditions.checkNotNull(userAgentHeaderValue); } @Override public Response intercept(Chain chain) throws IOException { final Request originalRequest = chain.request(); final Request requestWithUserAgent = originalRequest.newBuilder() .removeHeader(USER_AGENT_HEADER_NAME) .addHeader(USER_AGENT_HEADER_NAME, userAgentHeaderValue) .build(); return chain.proceed(requestWithUserAgent); } }
使用任何你觉得有价值的信息,来创建 User-Agent 值,然后传递给UserAgentInterceptor 的构造函数。我们使用了这些字段:
-
os 字段,值设置为 Android,明确表明这是一个 Android 设备
-
Build.MODEL 字段,即用户可见的终端产品的名称
-
Build.BRAND 字段,即消费者可见的跟产品或硬件相关的商标
-
Build.VERSION.SDK_INT 字段,即用户可见的 [Android] 框架版本号
-
BuildConfig.APPLICATION_ID 字段
-
BuildConfig.VERSION_NAME 字段
-
BuildConfig.VERSION_CODE字段
最后三个字段是根据我们的 Gradle 构建脚本中的 applicationId, versionCode 和versionName 的值来确定的。了解更多信息请参考文档 应用版本控制 ,和 使用 Gradle 配置你的 applicationId 。
小提示:如果你的应用中用到了 WebView,你可以配置使用相同的 User-Agent 值,即之前创建的 UserAgentInterceptor:
WebSettings settings = webView.getSettings(); settings.setUserAgentString(userAgentHeaderValue);
5. 指定合理的超时
在 2.5.0 版本之前,OkHttp 请求默认永不超时。从 2.5.0 版本开始,如果建立了一个连接,或从连接读取下一个字节,或者向连接写入下一个字节,用时超过了10秒,请求就会超时。分别调用 setConnectTimeout,setReadTimeout 或 setWriteTimeout 方法可以重写那些默认值。
小提示:Picasso 和 Retrofit 为它们的默认 OkHttpClient 实例指定不同的超时时长。 默认情况下, Picasso 设定如下:
-
连接超时15秒
-
读取超时20秒
-
写入超时20秒
Retrofit 设定如下:
-
连接超时15秒
-
读取超时20秒
-
写入无超时
用你自己的 OkHttpClient 实例配置好 Picasso 和 Retrofit 之后,就能确保所有请求超时的一致性了。
结论
再次强调,OkHttp 的默认配置提供了显著的效果,但是采取以上的措施,可以提高 OkHttp 的可用性和自我检查能力,并且提升你的应用的质量。