
1. 问题现象与背景分析最近在调试Android应用时遇到一个典型问题使用Retrofit请求本地测试服务器https://192.168.43.73:8080时出现Handshake failed错误。这种情况在开发环境中相当常见特别是当使用自签名证书或特殊网络配置时。先来看下我的环境配置开发工具Android Studio 2023.2.1目标设备Pixel 4a (Android 13)网络环境本地WiFi网络192.168.43.x段服务端Spring Boot 2.7 自签名证书出现这个错误时Logcat通常会输出类似这样的堆栈信息javax.net.ssl.SSLHandshakeException: Handshake failed at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:286) at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.kt:379)2. HTTPS握手失败的根本原因2.1 证书信任问题本地开发环境最常出现的问题就是证书信任链不完整。Android系统默认只信任CA机构颁发的证书而我们本地测试用的自签名证书不在系统信任列表中。可以通过以下命令检查证书链openssl s_client -connect 192.168.43.73:8080 -showcerts2.2 TLS版本不匹配某些旧版Android设备特别是三星系列对TLS 1.2的支持可能存在问题。可以通过配置OkHttp强制使用特定TLS版本val connectionSpec ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_3) .build() val client OkHttpClient.Builder() .connectionSpecs(listOf(connectionSpec)) .build()2.3 主机名验证失败当服务器证书中的CN(Common Name)或SAN(Subject Alternative Name)不包含我们请求的IP地址时就会触发主机名验证失败。对于本地测试可以临时禁用主机名验证val hostnameVerifier HostnameVerifier { _, _ - true } // 仅限测试环境 val client OkHttpClient.Builder() .hostnameVerifier(hostnameVerifier) .build()3. 完整解决方案与实现步骤3.1 创建自定义信任管理器对于开发环境我们可以创建一个信任所有证书的信任管理器fun createUnsafeOkHttpClient(): OkHttpClient { val trustAllCerts arrayOfTrustManager(object : X509TrustManager { override fun checkClientTrusted(chain: Arrayout X509Certificate?, authType: String?) {} override fun checkServerTrusted(chain: Arrayout X509Certificate?, authType: String?) {} override fun getAcceptedIssuers() arrayOfX509Certificate() }) val sslContext SSLContext.getInstance(SSL) sslContext.init(null, trustAllCerts, java.security.SecureRandom()) return OkHttpClient.Builder() .sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager) .hostnameVerifier { _, _ - true } .build() }警告这种配置会完全禁用SSL验证仅限开发和测试环境使用生产环境必须使用正规CA颁发的证书。3.2 配置Retrofit实例将自定义的OkHttpClient应用到Retrofitval retrofit Retrofit.Builder() .baseUrl(https://192.168.43.73:8080/) .client(createUnsafeOkHttpClient()) .addConverterFactory(GsonConverterFactory.create()) .build()3.3 添加网络调试日志为了更好排查问题建议添加HttpLoggingInterceptorval logging HttpLoggingInterceptor().apply { level HttpLoggingInterceptor.Level.BODY } val client OkHttpClient.Builder() .addInterceptor(logging) .build()4. 生产环境的最佳实践4.1 正确配置证书对于正式环境应该申请正规CA机构颁发的证书确保证书包含完整的信任链配置正确的SAN包含所有可能访问的域名4.2 证书固定(Certificate Pinning)为防止中间人攻击可以实现证书固定val certificatePinner CertificatePinner.Builder() .add(example.com, sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) .build() val client OkHttpClient.Builder() .certificatePinner(certificatePinner) .build()4.3 网络安全性配置在res/xml/network_security_config.xml中定义网络安全策略network-security-config domain-config domain includeSubdomainstrueexample.com/domain trust-anchors certificates srcraw/my_ca/ /trust-anchors /domain-config /network-security-config然后在AndroidManifest.xml中引用application android:networkSecurityConfigxml/network_security_config ... 5. 常见问题排查指南5.1 错误Certificate pinning failure解决方案检查证书指纹是否正确确保证书没有过期或被吊销如果更换了证书记得更新pinning配置5.2 错误unexpected status 404 not found可能原因请求路径配置错误服务端路由未正确设置请求头缺失必要信息排查步骤使用Postman测试相同接口检查Retrofit的baseUrl和接口注解路径查看服务端日志确认请求是否到达5.3 特定设备上的握手失败特别是三星设备可能出现的问题检查设备系统版本是否过旧尝试更新WebView组件在设备设置中清除应用数据和缓存我在实际项目中遇到最棘手的情况是某些国产ROM修改了SSL实现解决方案是在Application初始化时强制使用Conscrypt提供者Security.insertProviderAt(Conscrypt.newProvider(), 1)6. 性能优化建议6.1 连接池配置合理配置连接池可以提升HTTPS性能val client OkHttpClient.Builder() .connectionPool(ConnectionPool(5, 5, TimeUnit.MINUTES)) .build()6.2 会话复用启用SSL会话复用可以减少握手开销val sslSocketFactory SSLContext.getDefault().socketFactory val client OkHttpClient.Builder() .sslSocketFactory(sslSocketFactory, trustManager) .build()6.3 HTTP/2支持确保服务端支持HTTP/2可以显著提升HTTPS性能val client OkHttpClient.Builder() .protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1)) .build()通过以上配置和优化我们不仅解决了最初的Handshake failed问题还建立了一套完善的HTTPS通信方案。在实际开发中建议根据不同的构建变体debug/release使用不同的网络配置既保证开发效率又不牺牲生产环境的安全性。