1. 为什么需要封装网络框架?
在Android开发中,网络请求是最基础也是最频繁使用的功能之一。每次项目启动时,我们都会面临几个核心问题:如何统一处理网络请求?如何优雅地管理回调?如何简化异步操作?这些问题如果每次都从头解决,不仅浪费时间,还会导致代码风格不统一。
我经历过不少项目,发现很多团队都会在OkHttp3和Retrofit2的基础上进行二次封装。这种封装不是简单的工具类包装,而是基于项目实际需求对网络层进行系统性的架构设计。特别是在Kotlin协程(Coroutines)普及后,传统的Callback方式显得更加臃肿。
提示:好的网络框架封装应该像搭积木一样,既保持基础功能的稳定性,又能灵活应对各种业务场景的变化。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础组件选型与配置
2.1 OkHttp3的核心配置
OkHttp3作为底层网络库,我们需要重点关注几个配置点:
kotlin复制val okHttpClient = OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS) // 连接超时
.readTimeout(15, TimeUnit.SECONDS) // 读取超时
.writeTimeout(15, TimeUnit.SECONDS) // 写入超时
.addInterceptor(HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
})
.addInterceptor(HeaderInterceptor()) // 统一请求头
.addInterceptor(TokenInterceptor()) // Token处理
.build()
这里有几个关键点需要注意:
- 超时时间需要根据业务特点调整,电商类APP可以适当缩短,而文件上传类应用可能需要延长
- 日志拦截器只在Debug模式开启Body级别日志,避免生产环境泄露敏感信息
- 自定义拦截器的执行顺序很重要,通常Token处理要放在靠前位置
2.2 Retrofit2的初始化
Retrofit2的配置相对简单,但有几个细节容易忽略:
kotlin复制val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
特别注意:
- baseUrl必须以"/"结尾,否则会报错
- GsonConverterFactory可以自定义Gson实例来处理特殊序列化需求
- CoroutineCallAdapterFactory是将Retrofit与协程桥接的关键
3. 协程与网络请求的完美结合
3.1 传统方式 vs 协程方式
先看传统Callback方式的代码:
kotlin复制apiService.getUser().enqueue(object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
if (response.isSuccessful) {
val user = response.body()
// 处理成功逻辑
} else {
// 处理错误逻辑
}
}
override fun onFailure(call: Call<User>, t: Throwable) {
// 处理失败逻辑
}
})
同样的功能用协程实现:
kotlin复制viewModelScope.launch {
try {
val user = apiService.getUser()
// 处理成功逻辑
} catch (e: Exception) {
// 统一异常处理
}
}
协程版本的优势显而易见:
- 代码更简洁,消除了回调地狱
- 错误处理更集中
- 天然支持取消操作
3.2 异常统一处理
网络请求中常见的异常类型:
- HttpException: 状态码非200
- SocketTimeoutException: 超时
- UnknownHostException: 网络不可用
- SSLHandshakeException: 证书问题
我们可以定义一个密封类来封装这些异常:
kotlin复制sealed class NetworkException : Exception() {
data class HttpError(val code: Int, val message: String?) : NetworkException()
data class TimeoutError(val message: String) : NetworkException()
data class NetworkError(val message: String) : NetworkException()
data class UnknownError(val message: String) : NetworkException()
}
然后在Repository层统一转换异常:
kotlin复制suspend fun <T> safeApiCall(apiCall: suspend () -> T): T {
return try {
apiCall()
} catch (e: Exception) {
throw when (e) {
is HttpException -> NetworkException.HttpError(
e.code(),
e.response()?.errorBody()?.string()
)
is SocketTimeoutException -> NetworkException.TimeoutError(e.message ?: "")
is UnknownHostException -> NetworkException.NetworkError(e.message ?: "")
else -> NetworkException.UnknownError(e.message ?: "")
}
}
}
4. 高级功能封装
4.1 文件上传下载
文件上传的常见需求:
- 显示上传进度
- 支持多文件上传
- 支持取消上传
使用OkHttp的拦截器实现进度监听:
kotlin复制class ProgressInterceptor(
private val progressListener: (Long, Long) -> Unit
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalResponse = chain.proceed(chain.request())
return originalResponse.newBuilder()
.body(originalResponse.body?.let {
ProgressResponseBody(it, progressListener)
})
.build()
}
}
4.2 缓存策略
根据业务需求设计缓存策略:
kotlin复制val cacheSize = 10 * 1024 * 1024 // 10MB
val cache = Cache(File(context.cacheDir, "http_cache"), cacheSize.toLong())
val okHttpClient = OkHttpClient.Builder()
.cache(cache)
.addInterceptor(CacheInterceptor())
.addNetworkInterceptor(CacheInterceptor())
.build()
CacheInterceptor的实现:
kotlin复制class CacheInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
if (!NetworkUtil.isNetworkAvailable(context)) {
request = request.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=604800")
.build()
}
return chain.proceed(request)
}
}
5. 测试与调试技巧
5.1 Mock API测试
使用MockWebServer进行单元测试:
kotlin复制@Before
fun setup() {
mockWebServer = MockWebServer()
mockWebServer.start()
val retrofit = Retrofit.Builder()
.baseUrl(mockWebServer.url("/"))
.client(OkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build()
apiService = retrofit.create(ApiService::class.java)
}
@Test
fun testGetUser() = runBlockingTest {
mockWebServer.enqueue(MockResponse()
.setBody("""{"id":1,"name":"John"}""")
.setResponseCode(200))
val user = apiService.getUser()
assertEquals(1, user.id)
assertEquals("John", user.name)
}
5.2 网络日志分析
使用Charles或Fiddler抓包时,需要注意HTTPS的证书配置。在Debug模式下可以添加以下代码信任所有证书(仅限开发环境):
kotlin复制fun getUnsafeOkHttpClient(): OkHttpClient {
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 sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
return OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true }
.build()
}
6. 性能优化实践
6.1 连接池优化
OkHttp默认维护一个连接池,我们可以根据应用特点调整参数:
kotlin复制val connectionPool = ConnectionPool(
maxIdleConnections = 5, // 最大空闲连接数
keepAliveDuration = 5, // 保持时间(分钟)
timeUnit = TimeUnit.MINUTES
)
val okHttpClient = OkHttpClient.Builder()
.connectionPool(connectionPool)
.build()
6.2 DNS优化
使用HTTPDNS替代系统DNS解析:
kotlin复制class CustomDns : Dns {
override fun lookup(hostname: String): List<InetAddress> {
return try {
// 先尝试HTTPDNS
val httpDnsResult = HttpDns.getIpByHost(hostname)
if (httpDnsResult != null) {
return listOf(InetAddress.getByName(httpDnsResult))
}
// 回退到系统DNS
Dns.SYSTEM.lookup(hostname)
} catch (e: Exception) {
Dns.SYSTEM.lookup(hostname)
}
}
}
7. 封装成果与应用
最终的封装结构如下:
code复制network/
├── api/
│ ├── ApiService.kt # 接口定义
│ └── ApiHelper.kt # 对外暴露的接口
├── interceptors/
│ ├── HeaderInterceptor.kt
│ ├── TokenInterceptor.kt
│ └── LoggingInterceptor.kt
├── exception/
│ └── NetworkException.kt
├── model/
│ └── BaseResponse.kt # 统一响应体
└── NetworkManager.kt # 单例入口
使用示例:
kotlin复制viewModelScope.launch {
when (val result = safeApiCall { apiHelper.getUser() }) {
is Result.Success -> { /* 处理成功 */ }
is Result.Error -> { /* 处理错误 */ }
}
}
在实际项目中,这套封装方案带来了几个明显的好处:
- 网络请求代码量减少60%以上
- 错误处理更加统一规范
- 协程的引入使得异步代码更易读易维护
- 方便进行统一的网络监控和统计
我在多个项目中实践后发现,初期花时间做好网络层封装,后期维护成本会大幅降低。特别是在需要更换网络库或调整公共逻辑时,只需要修改封装层即可,业务代码几乎不需要变动。
