【OkHttp】Android 项目集成 OkHttp 实战指南 ( 依赖管理 | 网络配置 | 视图绑定 | 异步请求示例 )

张开发
2026/4/18 14:01:17 15 分钟阅读

分享文章

【OkHttp】Android 项目集成 OkHttp 实战指南 ( 依赖管理 | 网络配置 | 视图绑定 | 异步请求示例 )
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 9API 28开始默认禁止明文传输HTTP这个安全策略让很多开发者头疼。原始文章中的方案是全局允许 HTTP但在生产环境中这很不安全。更合理的做法是针对性配置在 res/xml/network_security_config.xml 中network-security-config !-- 开发环境配置 -- debug-overrides trust-anchors certificates srcsystem / certificates srcuser / /trust-anchors /debug-overrides !-- 生产环境只允许特定域名使用HTTP -- domain-config cleartextTrafficPermittedtrue domain includeSubdomainstrueapi.your-test-server.com/domain /domain-config /network-security-configAndroidManifest.xml 中的配置保持不变application android:networkSecurityConfigxml/network_security_config ... 实测发现一个常见问题如果使用 Android 模拟器访问本地开发服务器如 10.0.2.2即使配置了 cleartextTrafficPermittedtrue 也可能失败。这时需要在 domain-config 中明确添加domain includeSubdomainstrue10.0.2.2/domain3. 视图绑定与网络请求联动ViewBinding 现在已经是 Android 开发的标配了比 findViewById 更安全高效。原始文章展示了基础用法我来分享几个实战技巧。首先确保模块级 build.gradle 中启用 ViewBindingandroid { 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) returnwithContext 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: (ResultString) - 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(): SingleString { 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 arrayOfTrustManager(object : X509TrustManager { override fun checkClientTrusted(chain: Arrayout X509Certificate?, authType: String?) {} override fun checkServerTrusted(chain: Arrayout X509Certificate?, authType: String?) {} override fun getAcceptedIssuers() arrayOfX509Certificate() }) 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 持久化需要自定义 CookieJarclass PersistentCookieJar(context: Context) : CookieJar { private val cookieStore PersistentCookieStore(context) override fun saveFromResponse(url: HttpUrl, cookies: ListCookie) { cookieStore.addAll(url, cookies) } override fun loadForRequest(url: HttpUrl): ListCookie { return cookieStore.get(url) } } // 使用 val okHttpClient OkHttpClient.Builder() .cookieJar(PersistentCookieJar(context)) .build()内存泄漏确保正确关闭响应response.use { // 处理响应 } // 或者手动关闭 response.close()

更多文章