跳到内容

升级到 OkHttp 4

OkHttp 4.x 将实现语言从 Java 升级到 Kotlin,同时保持其他一切不变。我们选择 Kotlin 是因为它提供了强大的新功能,并且能与 Java 紧密集成。

我们在保持与 OkHttp 3.x 的严格兼容性上投入了大量时间和精力。我们甚至保留了相同的包名:okhttp3

我们关注三种兼容性

  • 二进制兼容性是指针对 OkHttp 3.x 编译的程序能够针对 OkHttp 4.x 运行。我们通过优秀的 japicmp 库及其 Gradle 插件来强制执行二进制兼容性。

  • Java 源代码兼容性是指在不修改 .java 文件的情况下,将使用 OkHttp 3.x 的 Java 代码升级到 4.x。

  • Kotlin 源代码兼容性是指在不修改 .kt 文件的情况下,将使用 OkHttp 3.x 的 Kotlin 代码升级到 4.x。

除了一些小的例外(如下所述),OkHttp 4.x 与 OkHttp 3.x 具有二进制和 Java 源代码兼容性。您可以将 OkHttp 4.x 的 .jar 文件用于为 OkHttp 3.x 构建的应用程序或库。

OkHttp 与 Kotlin 调用者具备源代码兼容性,但由于 Kotlin 强大的废弃功能,升级应该是自动化的。大多数开发者可以使用 IntelliJ 的 代码清理 功能来实现安全快速的升级。

向后不兼容的变更

OkHttpClient final 方法

OkHttpClient 有 26 个访问器(例如 interceptors()writeTimeoutMillis()),它们在 OkHttp 3.x 中不是 final,但在 4.x 中是 final。这些访问器在 OkHttp 3.x 中设置为非 final 是为了与 Mockito 等 mocking 框架一起使用。我们认为继承 OkHttpClient 不是使用 OkHttp 进行测试的正确方法。如果必须进行模拟,请模拟 Call.Factory 接口,OkHttpClient 就是实现了这个接口。

内部 API 变更

okhttp3.internal 包不是公开的 API,我们经常在没有任何警告的情况下对其进行更改。依赖此包中的代码是不好的做法,在任何升级过程中都会给您带来问题!但是对于那些从该包导入的开发者来说,4.x 将会特别痛苦!我们做了很多改动来利用 Kotlin 的强大特性。

Credentials.basic()

Credentials.basic() 方法的用户名和密码参数现在是非空字符串。在 OkHttp 3.x 中,null 会产生用户名或密码为“null”。

HttpUrl.queryParameterValues()

HttpUrl.queryParameterValues() 的返回类型是 List<String?>。可能包含 null 的列表不常见,Kotlin 调用者可能错误地将结果赋值给了 List<String>

代码清理

IntelliJ 和 Android Studio 提供了代码清理功能,可以自动将已废弃的 API 更新为新的 API。可以通过 搜索任意位置 对话框(双击 shift)或 分析 菜单访问此功能。

我们在 OkHttp 4.0 中包含了已废弃的 API,因为它们可以简化迁移。我们将在未来的版本中移除它们!如果您正在跳过版本,最好先升级到 OkHttp 4.0 作为中间步骤,这样会更容易。

Vars 和 Vals

Java 没有对属性的语言支持,因此开发者使用 getter 和 setter。Kotlin 拥有属性,我们在 OkHttp 中利用了它们。

  • Address:certificatePinner, connectionSpecs, dns, hostnameVerifier, protocols, proxy, proxyAuthenticator, proxySelector, socketFactory, sslSocketFactory, url
  • Cache:directory
  • CacheControl:immutable, maxAgeSeconds, maxStaleSeconds, minFreshSeconds, mustRevalidate, noCache, noStore, noTransform, onlyIfCached, sMaxAgeSeconds
  • Challenge:authParams, charset, realm, scheme
  • CipherSuite:javaName
  • ConnectionSpec:cipherSuites, supportsTlsExtensions, tlsVersions
  • Cookie:domain, expiresAt, hostOnly, httpOnly, name, path, persistent, value
  • Dispatcher:executorService
  • FormBody:size
  • Handshake:cipherSuite, localCertificates, localPrincipal, peerCertificates, peerPrincipal, tlsVersion
  • HandshakeCertificates:keyManager, trustManager
  • Headers:size
  • HeldCertificate:certificate, keyPair
  • HttpLoggingInterceptor:level
  • HttpUrl:encodedFragment, encodedPassword, encodedPath, encodedPathSegments, encodedQuery, encodedUsername, fragment, host, password, pathSegments, pathSize, port, query, queryParameterNames, querySize, scheme, username
  • MockResponse:headers, http2ErrorCode, socketPolicy, status, trailers
  • MockWebServer:bodyLimit, port, protocolNegotiationEnabled, protocols, requestCount, serverSocketFactory
  • MultipartBody.Part:body, headers
  • MultipartBody.:boundary, parts, size, type
  • OkHttpClient:authenticator, cache, callTimeoutMillis, certificatePinner, connectTimeoutMillis, connectionPool, connectionSpecs, cookieJar, dispatcher, dns, eventListenerFactory, followRedirects, followSslRedirects, hostnameVerifier, interceptors, networkInterceptors, pingIntervalMillis, protocols, proxy, proxyAuthenticator, proxySelector, readTimeoutMillis, retryOnConnectionFailure, socketFactory, sslSocketFactory, writeTimeoutMillis
  • PushPromise:headers, method, path, response
  • Request:body, cacheControl, headers, method, url
  • Response:body, cacheControl, cacheResponse, code, handshake, headers, message, networkResponse, priorResponse, protocol, receivedResponseAtMillis, request, sentRequestAtMillis
  • Route:address, proxy, socketAddress
  • TlsVersion:javaName

函数重命名

  • Headers.of():为了与 listOf(), setOf() 等保持对称性,我们将 Headers.of(String...) 替换为 headersOf(vararg String)

扩展函数

在我们认为合适的地方,我们将静态函数迁移到了扩展函数。

Java Kotlin
Handshake.get(SSLSession) SSLSession.handshake()
Headers.of(Map) Map.toHeaders()
HttpUrl.get(String) String.toHttpUrl()
HttpUrl.get(URI) URI.toHttpUrlOrNull()
HttpUrl.get(URL) URL.toHttpUrlOrNull()
HttpUrl.parse(String) String.toHttpUrlOrNull()
HttpUrl.uri() HttpUrl.toUri()
HttpUrl.url() HttpUrl.toUrl()
MediaType.get(String) String.toMediaType()
MediaType.parse(String) String.toMediaTypeOrNull()
RequestBody.create(ByteArray) ByteArray.toRequestBody()
RequestBody.create(ByteString) ByteString.toRequestBody()
RequestBody.create(File) File.asRequestBody()
RequestBody.create(String) String.toRequestBody()
ResponseBody.create(BufferedSource) BufferedSource.asResponseBody()
ResponseBody.create(ByteArray) ByteArray.toResponseBody()
ResponseBody.create(ByteString) ByteString.toResponseBody()
ResponseBody.create(String) String.toResponseBody()

SAM 转换

当您在 Kotlin 中使用 Java API 时,可以将 Java 接口视为 Kotlin lambda 来操作。此功能适用于定义了单个抽象方法 (SAM) 的接口。

但是当您在 Kotlin 中使用 Kotlin API 时,没有自动转换。在 OkHttp 3.x 中使用了 SAM lambda 的代码:在 OkHttp 4.x 中必须使用 object :

Kotlin 调用 OkHttp 3.x

val client = OkHttpClient.Builder()
    .dns { hostname -> InetAddress.getAllByName(hostname).toList() }
    .build()

Kotlin 调用 OkHttp 4.x

val client = OkHttpClient.Builder()
    .dns(object : Dns {
      override fun lookup(hostname: String) =
          InetAddress.getAllByName(hostname).toList()
    })
    .build()

SAM 转换影响以下 API

  • Authenticator
  • Dispatcher.setIdleCallback(Runnable)
  • Dns
  • EventListener.Factory
  • HttpLoggingInterceptor.Logger
  • LoggingEventListener.Factory
  • OkHttpClient.Builder.hostnameVerifier(HostnameVerifier)

JetBrains 正在开发 Kotlin 接口的 SAM 转换。预计将在未来的 Kotlin 语言版本中推出。

伴生对象导入

Kotlin 中与 Java 的静态方法等效的是伴生对象函数。字节码是相同的,但 .kt 文件现在需要在 import 中包含 Companion

这在 OkHttp 3.x 中有效

import okhttp3.CipherSuite.forJavaName

但 OkHttp 4.x 需要 Companion

import okhttp3.CipherSuite.Companion.forJavaName

万一您有很多此类情况,请运行此命令

sed -i "" \
  's/^\(import okhttp3\.[^.]*\)\.\([a-z][a-zA-Z]*\)$/\1.Companion.\2/g' \
  `find . -name "*.kt"`

高级性能分析

Android Studio 的高级性能分析功能会重写 OkHttp 字节码进行 instrumentation。不幸的是,它在 OkHttp 4.x 的字节码上会崩溃。在 Google 的错误 修复之前,您必须在 Android Studio 中禁用高级性能分析。

Disable Advanced Profiling

R8 / ProGuard

R8 和 ProGuard 都是用于 .class 文件的代码优化器。

R8 是 Android Studio 3.4 及更新版本中的默认优化器。它与所有 OkHttp 版本都能很好地配合使用。

ProGuard 是之前的默认优化器。我们正在跟踪 ProGuard、OkHttp 4.x 和 Kotlin 生成的 .class 文件之间的交互问题。如果您正在使用 ProGuard,请确保使用最新版本。

Gradle

OkHttp 4 的最低要求是 Java 8+ 和 Android 5+。这些要求是随 OkHttp 3.13 首次引入的

以下是您需要在 build.gradle 中配置的内容,以便 Kotlin、Java 和 Android 插件分别面向 Java 8 字节码。

compileKotlin {
  kotlinOptions {
    jvmTarget = "1.8"
  }
}
compileTestKotlin {
  kotlinOptions {
    jvmTarget = "1.8"
  }
}

compileJava {
  sourceCompatibility = JavaVersion.VERSION_1_8
  targetCompatibility = JavaVersion.VERSION_1_8
}

android {
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}