Koin与Retrofit:网络模块的Koin封装

网络模块,几乎是每个Android应用的标配。Retrofit + OkHttpClient 这套组合拳,大家都不陌生。但问题来了——你每次写网络请求,是不是都要手动创建 Retrofit 实例?是不是在多个地方重复配置 OkHttpClient?

我个人习惯,把这些交给 Koin 来管理。说白了,就是让 Koin 帮我们搞定对象的创建和生命周期。这样代码清爽,维护也方便。

为什么要把网络模块交给Koin?

你想想看,一个典型的网络模块包含哪些东西?

  • OkHttpClient:负责底层的网络请求,需要配置拦截器、超时时间、缓存等
  • Retrofit:基于 OkHttp 的上层封装,负责接口定义和请求转换
  • API Service 接口:定义具体的网络请求方法

如果这些对象散落在各个 Activity 或 Fragment 里,一旦需要修改超时时间,你得翻遍整个项目。我在项目中遇到过这种情况——一个同事把 OkHttpClient 的配置写在了三个不同的地方,结果线上出了超时问题,排查了半天才发现是某个地方漏改了。

用 Koin 封装后,所有网络相关的配置都集中在一个 Module 里。改一处,全局生效。

OkHttpClient的Koin封装

我们先从最底层的 OkHttpClient 开始。它通常需要配置拦截器、超时时间、日志等。

// 网络模块的 Koin 定义
val networkModule = module {

    // 单例:整个应用只创建一个 OkHttpClient
    single {
        OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BODY
            })
            .addInterceptor(AuthInterceptor()) // 自定义拦截器
            .build()
    }
}

这里我用的是 single,而不是 factory。为什么?因为 OkHttpClient 是重量级对象,内部维护了连接池和线程池。如果每次请求都创建新的实例,性能会大打折扣。嗯,这里要注意——single 保证了全局唯一实例,这是最佳实践。

我的经验:如果你需要为不同的 Base URL 创建不同的 OkHttpClient(比如同时访问多个 API 服务),可以用 named 来区分。比如 single(named("api1")) { ... }single(named("api2")) { ... }

Retrofit实例的封装

接下来是 Retrofit。它依赖 OkHttpClient,同时需要配置 Base URL 和转换器。

// 在同一个 module 中继续定义
val networkModule = module {

    // 先定义 OkHttpClient(同上)
    single {
        OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BODY
            })
            .build()
    }

    // 定义 Retrofit 实例
    single {
        Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .client(get()) // 从 Koin 容器中获取 OkHttpClient
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}

注意看 .client(get()) 这一行。get() 是 Koin 的关键函数,它会自动从容器中查找并返回之前定义的 OkHttpClient 实例。这就是依赖注入的核心——你不需要手动 new 对象,Koin 帮你搞定。

避坑指南:我曾经犯过一个错误——在定义 Retrofit 时,把 get() 写成了 get<OkHttpClient>(),虽然也能工作,但类型声明是多余的。Koin 会根据返回值类型自动推断。保持简洁就好。

API Service接口的注入

有了 Retrofit 实例,接下来就是创建具体的 API Service 接口。比如:

interface UserApi {
    @GET("users")
    suspend fun getUsers(): List<User>
}

interface PostApi {
    @GET("posts")
    suspend fun getPosts(): List<Post>
}

在 Koin 中,我们可以这样定义:

val networkModule = module {

    // ... 前面的 OkHttpClient 和 Retrofit 定义

    // 定义 API Service
    single { get<Retrofit>().create(UserApi::class.java) }
    single { get<Retrofit>().create(PostApi::class.java) }
}

这里有个小技巧——我习惯把 API Service 也定义为 single。因为 Retrofit 的 create() 方法返回的是代理对象,本身是轻量级的,而且线程安全。全局共享一个实例完全没问题。

在ViewModel中使用

封装好了,怎么用?很简单,在 ViewModel 中直接注入:

class MainViewModel(
    private val userApi: UserApi,
    private val postApi: PostApi
) : ViewModel() {

    fun loadUsers() {
        viewModelScope.launch {
            val users = userApi.getUsers()
            // 处理数据
        }
    }
}

// Koin 模块中定义 ViewModel
val viewModelModule = module {
    viewModel { MainViewModel(get(), get()) }
}

你看,ViewModel 不需要知道 OkHttpClient 是怎么配置的,也不需要关心 Retrofit 是怎么创建的。它只需要拿到 UserApi 和 PostApi 就行。这就是依赖注入的魅力——解耦。

整体架构图

下面这张图展示了 Koin 如何管理网络模块的依赖关系:

Koin 容器 OkHttpClient (single) Retrofit (single) UserApi (single) | PostApi (single) | ... ViewModel / Fragment / Activity

从图中可以看出,Koin 容器负责创建和管理所有对象。OkHttpClient 被 Retrofit 依赖,Retrofit 又被各个 API Service 依赖,最终这些 Service 被注入到 ViewModel 或 UI 层。整个链条清晰明了。

多Base URL的处理

实际项目中,我们经常需要访问多个不同的 API 服务。比如一个用于用户认证,一个用于业务数据。这时候可以用 Koin 的 named 来区分:

val networkModule = module {

    // 两个不同的 OkHttpClient
    single(named("auth")) {
        OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .addInterceptor(AuthInterceptor())
            .build()
    }

    single(named("business")) {
        OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(LoggingInterceptor())
            .build()
    }

    // 两个不同的 Retrofit
    single(named("auth")) {
        Retrofit.Builder()
            .baseUrl("https://auth.example.com/")
            .client(get(named("auth")))
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    single(named("business")) {
        Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .client(get(named("business")))
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    // 对应的 API Service
    single { get<Retrofit>(named("auth")).create(AuthApi::class.java) }
    single { get<Retrofit>(named("business")).create(BusinessApi::class.java) }
}

使用时,在 ViewModel 中通过 get(named("auth"))get(named("business")) 来获取对应的实例。这样既保持了灵活性,又避免了配置混乱。

核心要点:
  • OkHttpClient 用 single,全局共享连接池
  • Retrofit 用 single,轻量级代理对象
  • API Service 用 single,线程安全
  • 多 Base URL 用 named 区分
  • 使用 get() 自动解析依赖

好了,这就是 Koin 封装网络模块的完整思路。说白了,就是把那些重复的、容易出错的配置工作,交给 Koin 来管理。你只需要关注业务逻辑,网络层的细节交给框架去操心。

我在项目中用这套方案已经两年多了,团队里新来的同事上手很快,因为所有网络配置都在一个文件里,一目了然。如果你还在手动管理网络对象,不妨试试 Koin 的封装方式。

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