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 保证了全局唯一实例,这是最佳实践。
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 帮你搞定。
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 被 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 的封装方式。