21、扩展函数与高阶函数在网络请求中的应用:Retrofit的扩展函数、OkHttp拦截器的DSL配置、网络请求的链式调用
网络请求,说白了就是App跟服务器之间的“对话”。在Android开发中,Retrofit和OkHttp几乎是标配。但很多人用它们,只是停留在“照着官方文档写”的阶段。今天我想聊聊,怎么用Kotlin的扩展函数和高阶函数,让这些网络请求代码变得更优雅、更灵活。
我个人习惯,在封装网络层时,一定会用扩展函数来“加料”。为什么呢?因为Retrofit的接口定义通常是固定的,但调用方的需求千变万化。扩展函数就像给接口“开外挂”,不破坏原有结构,又能注入新能力。
Retrofit的扩展函数:让接口调用更自然
先看一个典型场景。你定义了一个Retrofit接口:
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: String): User
}
调用时,你可能会写:
val user = apiService.getUser("123")
这没什么问题。但如果你需要加统一的错误处理、重试逻辑、或者日志打印呢?难道每个调用处都写一遍try-catch?
嗯,这里就可以用扩展函数来包装。我曾在项目中遇到过这样的需求:所有网络请求失败时,都要弹一个统一的Toast。如果每个Activity都写一遍,那代码就太“脏”了。
于是,我写了一个扩展函数:
suspend fun <T> ApiService.safeCall(
block: suspend ApiService.() -> T
): Result<T> {
return try {
Result.success(block())
} catch (e: Exception) {
Log.e("ApiService", "网络请求失败", e)
Result.failure(e)
}
}
调用时变成:
val result = apiService.safeCall { getUser("123") }
result.onSuccess { user -> /* 更新UI */ }
.onFailure { error -> /* 显示错误 */ }
你看,扩展函数把“安全调用”的逻辑抽离出来了。而且因为用了高阶函数(block参数),调用方可以自由传入任何Retrofit接口方法。这就是扩展函数+高阶函数的组合拳。
OkHttp拦截器的DSL配置:告别冗长的Builder
OkHttp的拦截器配置,官方推荐用Builder模式。但如果你有多个拦截器,代码会变成这样:
val client = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.addInterceptor(HeaderInterceptor())
.addInterceptor(CacheInterceptor())
.build()
这还算清晰。但如果拦截器本身需要配置参数呢?比如日志拦截器要区分debug和release模式。代码就会越来越长。
我建议用DSL风格来重构。Kotlin的高阶函数配合扩展函数,可以写出这样的配置方式:
val client = okHttpClient {
logLevel = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY
else HttpLoggingInterceptor.Level.NONE
interceptors {
add(HeaderInterceptor())
add(CacheInterceptor())
}
timeout(30, TimeUnit.SECONDS)
}
是不是清爽多了?实现这个DSL,核心就是定义一个高阶函数:
fun okHttpClient(block: OkHttpDsl.() -> Unit): OkHttpClient {
val dsl = OkHttpDsl()
dsl.block()
return dsl.build()
}
class OkHttpDsl {
var logLevel = HttpLoggingInterceptor.Level.NONE
private val interceptorList = mutableListOf<Interceptor>()
fun interceptors(block: MutableList<Interceptor>.() -> Unit) {
interceptorList.block()
}
fun timeout(duration: Long, unit: TimeUnit) {
// 设置超时
}
fun build(): OkHttpClient {
val builder = OkHttpClient.Builder()
interceptorList.forEach { builder.addInterceptor(it) }
builder.addInterceptor(HttpLoggingInterceptor().apply { level = logLevel })
return builder.build()
}
}
你看,DSL的本质就是利用高阶函数接收一个lambda,lambda的接收者是配置对象本身。这样调用方就能在lambda里直接操作配置对象的属性和方法。
网络请求的链式调用:像写诗一样写网络请求
链式调用,说白了就是“点一下,再点一下”。Retrofit本身不支持链式调用,但我们可以用扩展函数和高阶函数来“造”一个。
想象一下,如果网络请求能这样写:
apiService.getUser("123")
.onSuccess { user -> showUser(user) }
.onError { error -> showError(error) }
.retry(3)
.cache(5, TimeUnit.MINUTES)
.enqueue()
是不是很直观?每个步骤都像一个“动作”,串联起来就是完整的请求流程。
实现这个链式调用,需要定义一个包装类:
class RequestBuilder<T>(
private val block: suspend () -> T
) {
private var onSuccess: ((T) -> Unit)? = null
private var onError: ((Exception) -> Unit)? = null
private var retryCount = 0
private var cacheDuration: Long? = null
fun onSuccess(action: (T) -> Unit): RequestBuilder<T> {
onSuccess = action
return this
}
fun onError(action: (Exception) -> Unit): RequestBuilder<T> {
onError = action
return this
}
fun retry(times: Int): RequestBuilder<T> {
retryCount = times
return this
}
fun cache(duration: Long, unit: TimeUnit): RequestBuilder<T> {
cacheDuration = unit.toMillis(duration)
return this
}
fun enqueue() {
// 实际执行请求,包含重试、缓存逻辑
}
}
然后定义一个扩展函数,把Retrofit的调用转换成RequestBuilder:
fun <T> ApiService.request(block: suspend ApiService.() -> T): RequestBuilder<T> {
return RequestBuilder { block() }
}
调用时:
apiService.request { getUser("123") }
.onSuccess { user -> showUser(user) }
.onError { error -> showError(error) }
.retry(3)
.enqueue()
你看,每个方法都返回this,这就是链式调用的核心。而高阶函数(block参数)让调用方可以自由传入任何请求逻辑。
知识体系总览
下面这张图,概括了本章的核心内容:
这三个技术点,其实都是围绕一个核心思想:把“行为”作为参数传递。扩展函数让你能“加方法”,高阶函数让你能“传逻辑”,两者结合,就能写出高度可复用的网络层代码。
我个人觉得,掌握了这些技巧,你的网络请求代码会从“能用”变成“好用”。而且这些模式不仅适用于Retrofit和OkHttp,其他第三方库也可以用同样的思路去封装。
好了,这一章的内容就到这里。记住,扩展函数和高阶函数不是炫技,而是实实在在能提升代码质量的手段。下次写网络请求时,不妨试试这些模式。
公众号:蓝海资料掘金营,微信deep3321