现代Android开发依赖注入框架:为何首选 Koin 而非 Hilt?
点击关注公众号,“技术干货”及时达!
引言在现代软件开发中,依赖注入(Dependency Injection, DI)已成为一种广泛使用的设计模式,能够有效解耦代码,提升模块化和可测试性。通过 DI,可以轻松管理依赖关系,避免手动实例化对象并显式传递依赖。Android 开发中,Hilt 和 Koin 是两大常见的 DI 框架。本文从集成难易、性能对比,跨平台性以及背后维护公司的角度,探讨为什么在现代Android开发中 Koin 更适合做为依赖注入框架。
什么是依赖注入?依赖注入是一种设计模式,用于将对象的依赖从内部移到外部进行管理。它的主要好处包括:
「解耦代码」:通过接口或抽象类注入依赖,降低模块之间的耦合度。「提升测试性」:可以使用 Mock 对象注入,方便单元测试。「易于维护」:通过集中管理依赖关系,减少手动创建对象的复杂性。以下是一个没有使用 DI 的传统实现:
class UserRepository { fun getUser(): String = "User Data"}
class UserService { private val userRepository = UserRepository()
fun getUserInfo(): String { return userRepository.getUser() }}
这种方式的问题在于 UserService 与 UserRepository 的耦合度过高。而使用 DI,可以改为:
interface UserRepository { fun getUser(): String}
class UserRepositoryImpl : UserRepository { override fun getUser(): String = "User Data"}
class UserService(private val userRepository: UserRepository) { fun getUserInfo(): String { return userRepository.getUser() }}
依赖通过构造函数传入,这种方式更灵活,方便测试和扩展。
什么是控制反转( IoC)?说到依赖注入,我们常常会和控制反转这个概念弄混淆。确实这两个概念是相关的,因为实际上依赖注入是实现控制反转的一种手段。
控制反转(Inversion of Control,简称 IoC)是一种软件设计原则,旨在通过将对象的控制权从开发者控制转移到外部框架来实现解耦。这意味着对象的生命周期和依赖关系由框架或容器管理,而非由编码者自身管理。这种管理依赖的关系的控制权发生了反转,从自身交给了外部容器(框架)。比如Java中大名鼎鼎的Spring框架,就具有Ioc容器功能。
DI 是 IoC 的一种具体实现方式。它通过构造函数、方法参数或属性注入对象的依赖,从而实现控制反转。DI 框架(如 Hilt 或 Koin)可以自动管理依赖的创建和注入。
为什么要引入依赖注入?在ViewModel中使用先看看如果不用依赖注入框架,有一个ViewModel,现在需要给它传入不同的参数,我们会怎么做呢?
classUserViewModel(privatevarid:String?):ViewModel(){
为了给这个UserViewModel传入id参数,androidx的lifecycle库需要我们写一个继承自ViewModelProvider.Factory的工厂类,使用的是工厂设计模式,目的是提供一个能传参的ViewModel。
classUseViewModelFactory(privatevalid:String?):ViewModelProvider.Factory{
overridefunT:ViewModelcreate(modelClass:ClassT):T{
if(modelClass.isAssignableFrom(UserViewModel::class.java)){
returnUserViewModel(id)asT
}
throwIllegalArgumentException("UnknownViewModelclass")
}
}
自定义了一个UseViewModelFactory,我们才能在UI中使用viewModel函数创建出这个UserViewModel对象,整个过程比较繁琐。特别是UserViewModel中需要传入多个参数或者Repository接口的不同实现类的话,创建起来会更加麻烦。
@Composable
funUserScreen(
id:String?,
viewModel:UserViewModel=viewModel(
factory=UseViewModelFactory(
id
)
),
onBackPressed:()-Unit={},
)
看看使用Koin,依赖注入的方式是不是简单很多呢?
我们先在Koin的module方法里,使用简洁的DSL声明一下依赖关系
valuserModule=module{
viewModel{(id:String)-UserViewModel(id)}
}
然后在项目中的MyApplication类(继承自系统的Application类)的onCreate方法里初始化一下
overridefunonCreate(){
super.onCreate()
startKoin{
androidLogger()
androidContext(this@AppBridgeApplication)
modules(userModule)
}
}
这样我们就能在Compose方法组件里面使用了
@Composable
funUserScreen(
id:String?,
viewModel:UserViewModel=koinViewModel(
parameters={parametersOf(id)}
),
onBackPressed:()-Unit={},
)
可以看到使用Koin依赖注入,我们能省掉写一个自定义的ViewModel Factory类。使用koinViewModel方法,就能直接传入想要的参数,是不是非常的方便。
多模块间的通讯服务而且Koin的功能不止是做依赖注入,还可以实现多模块项目间的通讯服务,在Base模块定义接口,在子模块实现。能做到和ARouter一样的跨模块API调用,达到各模块间的解耦。
比如说我们在Base模块定义了一个IUserService类,来获取用户的信息。
interfaceIUserService{
fungetUserInfo():FlowResponse
}
然后我们在User模块去实现这个类:
classIUserServiceImpl:IUserService{
overridefungetUserInfo():FlowResponse{
TODO("Notyetimplemented")
}
}
接着也需要在User模块定义Koin的module方法里申明IUserService类具体实现的子类是IUserServiceImpl,
valuserModule=module{
singleIUserService{IUserServiceImpl()}
}
然后我们就能在Base模块使用injectOrNull注入方法从Koin的依赖注入容器中创建一个IUserService接口,就能调用这个服务接口里的方法了,而不用去关心这个接口的具体实现子类是谁,实现了各模块间的解耦。
privatevaluserService:IUserService?byinjectOrNull(IUserService::class.java)
userService?.getUserInfo()?.collect{
}
感兴趣的同学可以去Koin的官网查看更多使用介绍。Koin 介绍文档
为什么选择Koin?「简单且对开发人员友好:」 Koin 干净的 DSL、无编译时开销、最少的设置和简单的测试让您可以专注于业务逻辑的实现。「非常的小巧:」 不论是小型项目还是复杂项目,Koin都是可以轻松扩展以满足您的需求。「安全性检查:」 Koin运行时能够进行依赖对象合法性检查,而且它的「IDE 插件」也即将发布,以后也不需要再羡慕Hilt的IDE插件导航了。「支持Kotlin Multiplatform:」 Koin因为是纯Kotlin写的,可以无缝的管理 iOS、Android、桌面和 Web 上的依赖项,使其成为跨平台开发的首选 DI 框架。「非常适合Jetpack Compose:」 Koin 与Jetpack Compose集成起来非常轻松,支持界面组件( 「ViewModel」)的依赖注入,未来实现迁移到iOS的Compose Multiplatform跨平台也是非常的方便。对比HiltKoin无论是使用的便捷性上还是未来非常重要的跨平台性上都是完胜Hilt的,具体对比如下:
「使用Hilt需要引入hilt-compiler插件,会增加编译时间,而Koin是不需要任何插件;」Hilt 通过注解处理器(Annotation Processor)生成代码,增加了编译时间。Koin使用 Kotlin DSL代码配置依赖,无需生成额外代码,对编译时间不影响。
「Hilt需要学习各种注解,学习曲线高」,@HiltViewModel,@Inject,@Module,@AndroidEntryPoint等,配置起来比较麻烦,而Koin配置很直观,上手容易。
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var repository: MyRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 使用 repository
}
}
@HiltViewModel
classHotNewsViewModel@Injectconstructor(privatevalopenApiRepository:OpenApiRepository)
3. 「性能对比上」,国外有人把Android官方使用Hilt写的Now in Android 和使用Koin重写的Now in Android APP进行基准测试对比,发现差距很小。说明Koin的性能也不差,足以应对我们的要求。
4. 「IDE插件上」,Hilt给类加上注入注解,左侧会有快捷导航图标,点击能导航到该注入类被声明的地方,非常的方便;
Koin的IDE插件目前没有,不过也即将发布,大家可以期待一波。
「跨平台性:」 因为Hilt是对Java写的Dagger2框架的移动端优化,要想实现像kotlin写的Koin一样支持Kotlin Multiplatform实现跨平台,并不容易,大量的代码需要重写,恐怕未来很长一段时间都将无法做到,更适合专注于Android APP开发的团队。?从未来企业降本增效的背景下,越来越多的公司的APP开发,都开始选择了跨平台,而且人员配备上,有些公司都裁员到只剩一个Android和一个iOS来负责维护公司APP的程度了。
Koin支持Kotlin Multiplatform,Koin依赖注入的代码可以在多个平台间共享,减少了重复开发。这样公司可以招两个Android一个iOS的APP团队,UI层分别使用各自原生的UI框架Jetpack Compose和Swift UI实现,业务逻辑使用KMP实现,既能保证用户的原生体验,又能节约成本。
?「官方支持:」Hilt 是 Android Jetpack 的一部分,有Android官方的背书,文档说明很详细,Hilt介绍文档;Koin由Kotzilla Team和社区支持,也有广泛的群众基础,文档说明也很详尽,而且Kotzilla是Kotlin基金会的银牌会员,大家无需担心后期维护的问题。Koin 介绍文档总结综合以上分析,以下是选择 Koin 的主要理由:
「简单易用」:无需生成代码,配置直观,易于学习。「跨平台支持」:能够在多种平台上使用,适合未来APP开发的趋势。「轻量级」:相较于 Hilt 的复杂性,Koin 更轻便,减少了维护成本。现代Android开发,Koin无疑是更为合适的选择,新开发Android项目我建议直接上Koin。当然现有的Android项目也可以渐进式的开始使用Koin,来替代原来的Hilt,或者二者共存也是没有问题的,大家赶紧用起来吧。
点击关注公众号,“技术干货”及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线