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接口方法。这就是扩展函数+高阶函数的组合拳。

我的小技巧: 扩展函数的名字尽量用动词开头,比如safeCall、retryCall、logCall。这样读代码时,一眼就能看出这个函数做了什么。

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里直接操作配置对象的属性和方法。

我曾经踩过的坑: 在DSL里配置拦截器时,注意拦截器的添加顺序。OkHttp的拦截器是按添加顺序执行的。如果你先加了重试拦截器,再加了日志拦截器,那重试时的日志也会被打印。我曾经因为顺序搞反,导致日志里出现了重复的请求记录,排查了半天。

网络请求的链式调用:像写诗一样写网络请求

链式调用,说白了就是“点一下,再点一下”。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扩展函数 • 安全调用包装 • 统一错误处理 • 重试逻辑注入 • 日志打印增强 核心:扩展函数 + 高阶函数 block参数接收任意接口方法 OkHttp拦截器DSL • DSL配置对象 • 拦截器顺序管理 • 超时配置 • 日志级别控制 核心:高阶函数接收lambda lambda接收者为配置对象 网络请求链式调用 • onSuccess回调 • onError回调 • retry重试 • cache缓存 核心:方法返回this 高阶函数传递请求逻辑 三者结合:代码更简洁、更灵活、更易维护

这三个技术点,其实都是围绕一个核心思想:把“行为”作为参数传递。扩展函数让你能“加方法”,高阶函数让你能“传逻辑”,两者结合,就能写出高度可复用的网络层代码。

我个人觉得,掌握了这些技巧,你的网络请求代码会从“能用”变成“好用”。而且这些模式不仅适用于Retrofit和OkHttp,其他第三方库也可以用同样的思路去封装。

避坑指南: 我曾经在链式调用里,不小心在onSuccess里又调用了enqueue,导致递归调用。后来我加了一个状态标记,确保每个RequestBuilder只执行一次。你如果也写链式调用,记得加这个保护。

好了,这一章的内容就到这里。记住,扩展函数和高阶函数不是炫技,而是实实在在能提升代码质量的手段。下次写网络请求时,不妨试试这些模式。


公众号:蓝海资料掘金营,微信deep3321