1. OkHttp 依赖管理与版本控制
在 Android 项目中集成 OkHttp 的第一步就是正确配置依赖。现在主流项目都推荐使用版本目录(Version Catalogs)进行集中式依赖管理,这比直接在 build.gradle 里写死版本号要优雅得多。
在项目的 settings.gradle 文件中添加版本目录配置:
dependencyResolutionManagement { versionCatalogs { libs { library('okhttp', 'com.squareup.okhttp3:okhttp:4.10.0') library('okhttp-logging', 'com.squareup.okhttp3:logging-interceptor:4.10.0') } } }然后在模块的 build.gradle 中引用:
dependencies { implementation libs.okhttp debugImplementation libs.okhttp.logging // 开发环境日志拦截器 }这里我强烈建议使用最新稳定版(当前是4.10.0),相比原始文章中使用的3.14.x版本,新版有这些优势:
- 支持 Kotlin 协程
- 更完善的 HTTP/2 支持
- 更高效的内存管理
- 内置了更多实用功能如 cookie 持久化
踩过的一个坑:如果项目同时使用 Retrofit,要注意两者的版本兼容性。建议保持主版本号一致,比如都用4.x系列。
2. 网络安全性配置实战
从 Android 9(API 28)开始,默认禁止明文传输(HTTP),这个安全策略让很多开发者头疼。原始文章中的方案是全局允许 HTTP,但在生产环境中这很不安全。更合理的做法是针对性配置:
在 res/xml/network_security_config.xml 中:
<network-security-config> <!-- 开发环境配置 --> <debug-overrides> <trust-anchors> <certificates src="system" /> <certificates src="user" /> </trust-anchors> </debug-overrides> <!-- 生产环境只允许特定域名使用HTTP --> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">api.your-test-server.com</domain> </domain-config> </network-security-config>AndroidManifest.xml 中的配置保持不变:
<application android:networkSecurityConfig="@xml/network_security_config" ... >实测发现一个常见问题:如果使用 Android 模拟器访问本地开发服务器(如 10.0.2.2),即使配置了 cleartextTrafficPermitted="true" 也可能失败。这时需要在 domain-config 中明确添加:
<domain includeSubdomains="true">10.0.2.2</domain>3. 视图绑定与网络请求联动
ViewBinding 现在已经是 Android 开发的标配了,比 findViewById 更安全高效。原始文章展示了基础用法,我来分享几个实战技巧。
首先确保模块级 build.gradle 中启用 ViewBinding:
android { buildFeatures { viewBinding true } }在 Activity 中使用时,可以结合网络请求状态优化用户体验:
class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private val okHttpClient = OkHttpClient() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.apply { btnFetchData.setOnClickListener { progressBar.visibility = View.VISIBLE btnFetchData.isEnabled = false fetchData() } } } private fun fetchData() { // 网络请求逻辑... } }这种模式在真实项目中很实用:
- 请求开始时禁用按钮并显示加载动画
- 请求结束后恢复按钮状态
- 通过 binding 对象可以安全地更新任何视图
4. 异步请求的现代化实现
原始文章展示了同步请求+线程的方案,这已经不符合现代 Android 开发的最佳实践了。以下是更推荐的三种方式:
4.1 协程方案(推荐)
private suspend fun fetchUserData(): String = withContext(Dispatchers.IO) { val request = Request.Builder() .url("https://api.example.com/users") .build() okHttpClient.newCall(request).execute().use { response -> if (!response.isSuccessful) throw IOException("Unexpected code $response") return@withContext response.body?.string() ?: "" } } // 在ViewModel或Activity中调用 viewModelScope.launch { try { val result = fetchUserData() binding.tvResult.text = result } catch (e: Exception) { binding.tvResult.text = "Error: ${e.message}" } }4.2 回调方案
fun fetchData(callback: (Result<String>) -> Unit) { val request = Request.Builder() .url("https://api.example.com/data") .build() okHttpClient.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { callback(Result.failure(e)) } override fun onResponse(call: Call, response: Response) { response.use { callback(Result.success(it.body?.string() ?: "")) } } }) } // 调用示例 fetchData { result -> runOnUiThread { result.onSuccess { data -> binding.tvResult.text = data }.onFailure { error -> Toast.makeText(this, error.message, Toast.LENGTH_SHORT).show() } } }4.3 RxJava 方案
fun fetchDataObservable(): Single<String> { return Single.create { emitter -> val request = Request.Builder() .url("https://api.example.com/data") .build() okHttpClient.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { emitter.onError(e) } override fun onResponse(call: Call, response: Response) { response.use { emitter.onSuccess(it.body?.string() ?: "") } } }) } } // 调用示例 fetchDataObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ data -> binding.tvResult.text = data }, { error -> Toast.makeText(this, error.message, Toast.LENGTH_SHORT).show() })5. 高级配置与性能优化
OkHttp 的强大之处在于它的可配置性。这里分享几个实战中特别有用的配置项:
5.1 连接池配置
val okHttpClient = OkHttpClient.Builder() .connectionPool(ConnectionPool(5, 5, TimeUnit.MINUTES)) .build()这个配置表示:
- 最大空闲连接数:5
- 保持时间:5分钟
- 适合中等规模的请求频率
5.2 超时设置
val okHttpClient = OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .build()根据业务需求调整:
- 移动网络环境下建议适当延长超时
- 大文件上传下载需要单独设置更长的超时
5.3 缓存配置
val cacheSize = 10 * 1024 * 1024 // 10MB val cache = Cache(File(context.cacheDir, "http_cache"), cacheSize.toLong()) val okHttpClient = OkHttpClient.Builder() .cache(cache) .build()缓存使用技巧:
- 只缓存 GET 请求
- 通过 CacheControl 控制缓存行为
- 注意缓存目录的清理策略
6. 拦截器实战应用
OkHttp 的拦截器机制是其核心功能之一,这里演示几个实用案例:
6.1 日志拦截器
val loggingInterceptor = HttpLoggingInterceptor().apply { level = if (BuildConfig.DEBUG) { HttpLoggingInterceptor.Level.BODY } else { HttpLoggingInterceptor.Level.NONE } } val okHttpClient = OkHttpClient.Builder() .addInterceptor(loggingInterceptor) .build()6.2 认证拦截器
class AuthInterceptor(private val token: String) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request().newBuilder() .header("Authorization", "Bearer $token") .build() return chain.proceed(request) } } // 使用 val okHttpClient = OkHttpClient.Builder() .addInterceptor(AuthInterceptor(userToken)) .build()6.3 重试拦截器
class RetryInterceptor(private val maxRetries: Int) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { var retryCount = 0 var response: Response var lastException: IOException? = null while (retryCount < maxRetries) { try { response = chain.proceed(chain.request()) if (response.isSuccessful) { return response } } catch (e: IOException) { lastException = e } retryCount++ } throw lastException ?: IOException("Unknown error") } }7. 文件上传下载实践
7.1 文件上传
fun uploadFile(file: File, url: String) { val requestBody = MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart( "file", file.name, file.asRequestBody("image/*".toMediaType()) ) .build() val request = Request.Builder() .url(url) .post(requestBody) .build() okHttpClient.newCall(request).enqueue(...) }7.2 文件下载
fun downloadFile(url: String, outputFile: File) { val request = Request.Builder() .url(url) .build() okHttpClient.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { // 处理错误 } override fun onResponse(call: Call, response: Response) { response.body?.byteStream()?.use { input -> outputFile.outputStream().use { output -> input.copyTo(output) } } } }) }8. 常见问题排查指南
在实际项目中遇到过几个典型问题:
- SSL 证书问题:当遇到 "CertificateException" 时,可以这样处理:
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager { override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {} override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {} override fun getAcceptedIssuers() = arrayOf<X509Certificate>() }) val sslSocketFactory = SSLContext.getInstance("SSL").apply { init(null, trustAllCerts, SecureRandom()) }.socketFactory val okHttpClient = OkHttpClient.Builder() .sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager) .hostnameVerifier { _, _ -> true } // 仅限测试环境! .build()- Cookie 持久化:需要自定义 CookieJar:
class PersistentCookieJar(context: Context) : CookieJar { private val cookieStore = PersistentCookieStore(context) override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) { cookieStore.addAll(url, cookies) } override fun loadForRequest(url: HttpUrl): List<Cookie> { return cookieStore.get(url) } } // 使用 val okHttpClient = OkHttpClient.Builder() .cookieJar(PersistentCookieJar(context)) .build()- 内存泄漏:确保正确关闭响应:
response.use { // 处理响应 } // 或者手动关闭 response.close()