当前位置: 首页 > news >正文

如何更高效地使用 OkHttp

本文讲的是如何更高效地使用 OkHttp,

  • 原文链接: Effective OkHttp
  • 原文作者 : Michael Parker
  • 译文出自 : 掘金翻译计划
  • 译者 : Brucezz
  • 校对者: iThreeKing, Adam Shen, Jaeger
  • 译文链接:如何更高效地使用 OkHttp
  • 转载请联系,并注明出处。

在为可汗学院开发 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 是一个 Facebook 出品的超赞的开源库,它可以让你用 Chrome 的功能——开发者工具来检查调试你的 Android 应用。

Stetho 不仅能够检查应用的 SQLite 数据库和视图层次,还可以检查 OkHttp 的每一条请求和响应消息:

Image of Stetho

这种自我检查方式(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 构建脚本中的 applicationIdversionCode 和 versionName 的值来确定的。了解更多信息请参考文档 应用版本控制,和 使用 Gradle 配置你的 applicationId

小提示:如果你的应用中用到了 WebView,你可以配置使用相同的 User-Agent 值,即之前创建的 UserAgentInterceptor

WebSettings settings = webView.getSettings();
settings.setUserAgentString(userAgentHeaderValue);

5. 指定合理的超时

在 2.5.0 版本之前,OkHttp 请求默认永不超时。从 2.5.0 版本开始,如果建立了一个连接,或从连接读取下一个字节,或者向连接写入下一个字节,用时超过了10秒,请求就会超时。分别调用 setConnectTimeoutsetReadTimeout 或 setWriteTimeout 方法可以重写那些默认值。

小提示:Picasso 和 Retrofit 为它们的默认 OkHttpClient 实例指定不同的超时时长。 默认情况下, Picasso 设定如下:

  • 连接超时15秒
  • 读取超时20秒
  • 写入超时20秒

Retrofit 设定如下:

  • 连接超时15秒
  • 读取超时20秒
  • 写入无超时

用你自己的 OkHttpClient 实例配置好 Picasso 和 Retrofit 之后,就能确保所有请求超时的一致性了。

结论

再次强调,OkHttp 的默认配置提供了显著的效果,但是采取以上的措施,可以提高 OkHttp 的可用性和自我检查能力,并且提升你的应用的质量。





原文发布时间为:2016年01月07日

本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。

相关文章:

  • BZOJ-3713[PA2014]Iloczyn
  • Spring绑定请求参数过程以及使用@InitBinder来注册自己的属性处理器
  • 铁路(栈)
  • Aspose------导入Excel
  • 生活:高效且健康的生活习惯
  • value toDF is not a member of org.apache.spark.rdd.RDD
  • MySql中把一个表的数据插入到另一个表中的实现代码
  • 图片定位问题
  • xencenter如何安装系统
  • ASP.NET MVC Model元数据及其定制: Model元数据的定制
  • 04-String
  • 社工-入侵
  • Spring声明式事务管理之一:五大属性分析
  • 解决Activity启动黑屏及设置android:windowIsTranslucent不兼容activity切换动画问题
  • UWP Popup 弹出提示框
  • go append函数以及写入
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • JavaScript HTML DOM
  • Java编程基础24——递归练习
  • Logstash 参考指南(目录)
  • mysql 数据库四种事务隔离级别
  • Python中eval与exec的使用及区别
  • TypeScript实现数据结构(一)栈,队列,链表
  • 从重复到重用
  • 服务器之间,相同帐号,实现免密钥登录
  • 记录:CentOS7.2配置LNMP环境记录
  • 经典排序算法及其 Java 实现
  • 开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?
  • 手机app有了短信验证码还有没必要有图片验证码?
  • 跳前端坑前,先看看这个!!
  • 问题之ssh中Host key verification failed的解决
  • Java总结 - String - 这篇请使劲喷我
  • LIGO、Virgo第三轮探测告捷,同时探测到一对黑洞合并产生的引力波事件 ...
  • #laravel 通过手动安装依赖PHPExcel#
  • $forceUpdate()函数
  • (4)logging(日志模块)
  • (C语言)字符分类函数
  • (c语言版)滑动窗口 给定一个字符串,只包含字母和数字,按要求找出字符串中的最长(连续)子串的长度
  • (二)Eureka服务搭建,服务注册,服务发现
  • (附源码)springboot 基于HTML5的个人网页的网站设计与实现 毕业设计 031623
  • (附源码)计算机毕业设计SSM疫情居家隔离服务系统
  • (力扣)1314.矩阵区域和
  • (亲测)设​置​m​y​e​c​l​i​p​s​e​打​开​默​认​工​作​空​间...
  • (十五)Flask覆写wsgi_app函数实现自定义中间件
  • (五)MySQL的备份及恢复
  • (学习日记)2024.04.10:UCOSIII第三十八节:事件实验
  • (转载)从 Java 代码到 Java 堆
  • *++p:p先自+,然后*p,最终为3 ++*p:先*p,即arr[0]=1,然后再++,最终为2 *p++:值为arr[0],即1,该语句执行完毕后,p指向arr[1]
  • *ST京蓝入股力合节能 着力绿色智慧城市服务
  • .Net Remoting常用部署结构
  • .NET Standard、.NET Framework 、.NET Core三者的关系与区别?
  • .Net 路由处理厉害了
  • .NET 应用架构指导 V2 学习笔记(一) 软件架构的关键原则
  • .NET(C#) Internals: as a developer, .net framework in my eyes
  • .NET/C# 项目如何优雅地设置条件编译符号?