对于强大的注解框架,Dagger2的编译特点一直都让我觉得不舒服,强行学完Dagger2的使用和大体原理后,也一直没有将它投入生产中。后来在浏览博客的时候,发现Koin:
适用于Kotlin开发人员的实用轻量级依赖注入框架。仅使用功能分辨率编写的纯Kotlin:无代理,无代码生成,无反射!
Koin官网
这个三无产品一下子就吸引了我,不要998,不要9.8,什么都不要,来试试看~
添加依赖
当前最新版本
koin_version = '2.0.1'
Android
// Koin for Android
implementation "org.koin:koin-android:$koin_version"
// Koin Android Scope features
implementation "org.koin:koin-android-scope:$koin_version"
// Koin Android ViewModel features
implementation "org.koin:koin-android-viewmodel:$koin_version"
// Koin Android Experimental features
implementation "org.koin:koin-android-ext:$koin_version"
AndroidX
// Koin AndroidX Scope features
implementation "org.koin:koin-androidx-scope:$koin_version"
// Koin AndroidX ViewModel features
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
// Koin AndroidX Experimental features
implementation "org.koin:koin-androidx-ext:$koin_version"
开始使用
1.初始化Koin
三无产品特点,就是需要我们告诉它有哪些对象要注解,如何生成。
class App : Application() {
override fun onCreate() {
super.onCreate()
/*
开启Koin,这里需要将所有需要注解生成的对象添加进来
*/
startKoin {
//给Koin框架添加ApplicationContext
androidContext(this@App)
/*
这里设置Koin的日志打印
Koin提供了三种实现:
AndroidLogger:使用Android的Log.e/i/d()打印日志
PrintLogger:使用System.err/out打印日志
EmptyLogger:不打印日志,默认就是该实现
*/
logger(AndroidLogger())
/*
设置Koin配置文件,需要放在assets文件夹中
默认名称为:koin.propreties
可以快速获取配置文件中的内容,文件名可以修改,但是需要在这里保持一致
[getKoin().getProperty("name")]
*/
androidFileProperties("koin.properties")
modules(
/*
添加Module对象
*/
module {
/*
实例工厂,每次获取都是新的实例对象
*/
factory { FactoryModel() }
/*
获取的实例为单例
*/
single { SingleModel() }
single { MainModel() }
/*
获取的实例为ViewModel,并且具有ViewModel的功能
*/
viewModel { MainViewModel(get()) }
}
)
}
}
}
2.获取对象
2.1 获取factory对象
为了演示清晰,这里创建FactoryActivity
类
class FactoryActivity : AppCompatActivity() {
/**
* 使用[inject]获取FactoryModel实例
* 其他教程中有说也可以使用[get]获取
* 并且[inject]知识[lazy]版的[get]
* 可以点开[inject]看到源码确实如此,但我这里是用[get]时提示
* `Missing 'getValue(FactoryActivity, KProperty<*>)' method on delegate of type 'FactoryModel'`
* 推荐:
* 获取实例时使用[inject],初始化Koin时使用[get]
*/
private val mModelOne: FactoryModel by inject()
private val mModelTwo: FactoryModel by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_factory)
btn_show.setOnClickListener {
val msg = "one model is:\n $mModelOne\ntwo model is:\n $mModelTwo"
loge(msg)
tv.text = msg
}
}
companion object {
fun start(context: Context) {
context.startActivity(Intent(context, FactoryActivity::class.java))
}
}
}
用gif来看下效果
可以看到获取的对象是不同的,并且每次重新进入获取,都是新的对象。
2.2 获取single对象
SingleActivity
的代码与FactoryActivity
代码相似,这里获取的SingleModel
每次获取的对象都是同一个,关闭Activity重新进来获取依然是相同的。
2.3 获取ViewModel对象
获取方式与上面两个不同,这里需要用viewModel
来获取
class ViewModelActivity : AppCompatActivity() {
/**
* 这里区分开来:
* mViewModel由Koin的[viewModel]来生成
* mViewModel2由[ViewModelProvider]生成
* mModel由本身构造函数生成
* 旋转屏幕后看是否具有原生ViewModel的功能
*/
private val mViewModel: VmViewModel by viewModel()
private val mViewModel2: VmViewModel by lazy {
ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(VmViewModel::class.java)
}
private val mModel: VmViewModel by lazy { VmViewModel() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_view_model)
mViewModel.count.observe(this, Observer {
tv_vm.text = it.toString()
})
mViewModel2.count.observe(this, Observer {
tv_vm2.text = it.toString()
})
mModel.count.observe(this, Observer {
tv_m.text = it.toString()
})
btn_vm.setOnClickListener {
mViewModel.addCount()
}
btn_vm2.setOnClickListener {
mViewModel2.addCount()
}
btn_m.setOnClickListener {
mModel.addCount()
}
}
companion object {
fun start(context: Context) {
context.startActivity(Intent(context, ViewModelActivity::class.java))
}
}
}
VMViewModel
代码
class VmViewModel : ViewModel() {
val count = MutableLiveData()
init {
count.value = 0
}
fun addCount() {
count.value = count.value!!.toInt() + 1
}
}
Gif效果
前两个数据同步改变,说明Koin和ViewModelProvider获取的对象是同一个,这个涉及到ViewModel的实现原理,这里不做阐述。也就证明Koin获取的ViewModel真实可用。
由于横竖屏切换不方便GIF录制,这里就口述一下子,只有第三个数字会在切换时归0,所以,木有问题。
实现MVVM
框架再好,终究是要配合咱们实现功能的,这里以MVVM来做个示例:
Model
class MvvmModel {
/**
* 模拟获取消息
*/
fun getMsg(): String {
return "这是一个普通的消息"
}
fun getError(): String {
return "这是一个错误提醒"
}
}
ViewModel
class MvvmViewModel(private val mView: IMvvmView) : ViewModel(),
/**
* 标示该类为Koin的组件,这样就可以在该类自由的使用 get()/inject()
* 当然,如果你是个狼人,就喜欢不按套路走,也可以不实现该接口,使用
* GlobalContext.get().koin.get()
* GlobalContext.get().koin.inject()
*/
KoinComponent {
private val mModel: MvvmModel by inject()
fun show() {
mView.showMsg(mModel.getMsg())
}
fun error() {
mView.showError(mModel.getError())
}
}
View
interface IMvvmView{
fun showMsg(msg:String)
fun showError(error:String)
}
class MvvmActivity : AppCompatActivity(), IMvvmView {
/**
* 用Koin获取ViewModel
* 因为[MvvmViewModel]的构造函数有IMvvmView,且Koin无法提供其实现
* 这里需要手动添加该参数,配合App中的[viewModel { (view: IMvvmView) -> MvvmViewModel(view) }]
*/
private val mViewModel: MvvmViewModel by viewModel {
parametersOf(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding =
DataBindingUtil.setContentView(this, R.layout.activity_mvvm)
binding.viewModel = mViewModel
}
override fun showMsg(msg: String) {
showSnack(msg, btn_show)
}
override fun showError(error: String) {
showSnack(error, btn_error)
}
companion object {
fun start(context: Context) {
context.startActivity(Intent(context, MvvmActivity::class.java))
}
}
}
将MvvmModel和MvvmViewModel添加到Koin中
/*
这里MVVMViewModel需要传入IMvvmView对象
这个接口是Activity来实现的,没有办法在Koin中注明实例,所以需要以此方式
*/
viewModel { (view: IMvvmView) -> MvvmViewModel(view) }
single { MvvmModel() }
这个示例主要展示如何将View对象在Koin框架中传递给ViewModel,ViewModel中又如何使用Koin获取对象。
结语
相较于Dagger2的每次生成都需要重新编译,Koin给我的感觉真的超清新,对ViewModel的支持也让我在选择框架时有更多的选择,强力推荐各位同道中人尝试一下Koin框架。
Demo地址