[关闭]
@martin0207 2019-09-05T02:40:17.000000Z 字数 5317 阅读 1263

Koin实战

android


对于强大的注解框架,Dagger2的编译特点一直都让我觉得不舒服,强行学完Dagger2的使用和大体原理后,也一直没有将它投入生产中。后来在浏览博客的时候,发现Koin:

适用于Kotlin开发人员的实用轻量级依赖注入框架。仅使用功能分辨率编写的纯Kotlin:无代理,无代码生成,无反射!

Koin官网

这个三无产品一下子就吸引了我,不要998,不要9.8,什么都不要,来试试看~

添加依赖

当前最新版本

  1. koin_version = '2.0.1'

Android

  1. // Koin for Android
  2. implementation "org.koin:koin-android:$koin_version"
  3. // Koin Android Scope features
  4. implementation "org.koin:koin-android-scope:$koin_version"
  5. // Koin Android ViewModel features
  6. implementation "org.koin:koin-android-viewmodel:$koin_version"
  7. // Koin Android Experimental features
  8. implementation "org.koin:koin-android-ext:$koin_version"

AndroidX

  1. // Koin AndroidX Scope features
  2. implementation "org.koin:koin-androidx-scope:$koin_version"
  3. // Koin AndroidX ViewModel features
  4. implementation "org.koin:koin-androidx-viewmodel:$koin_version"
  5. // Koin AndroidX Experimental features
  6. implementation "org.koin:koin-androidx-ext:$koin_version"

开始使用

1.初始化Koin

三无产品特点,就是需要我们告诉它有哪些对象要注解,如何生成。

  1. class App : Application() {
  2. override fun onCreate() {
  3. super.onCreate()
  4. /*
  5. 开启Koin,这里需要将所有需要注解生成的对象添加进来
  6. */
  7. startKoin {
  8. //给Koin框架添加ApplicationContext
  9. androidContext(this@App)
  10. /*
  11. 这里设置Koin的日志打印
  12. Koin提供了三种实现:
  13. AndroidLogger:使用Android的Log.e/i/d()打印日志
  14. PrintLogger:使用System.err/out打印日志
  15. EmptyLogger:不打印日志,默认就是该实现
  16. */
  17. logger(AndroidLogger())
  18. /*
  19. 设置Koin配置文件,需要放在assets文件夹中
  20. 默认名称为:koin.propreties
  21. 可以快速获取配置文件中的内容,文件名可以修改,但是需要在这里保持一致
  22. [getKoin().getProperty<String>("name")]
  23. */
  24. androidFileProperties("koin.properties")
  25. modules(
  26. /*
  27. 添加Module对象
  28. */
  29. module {
  30. /*
  31. 实例工厂,每次获取都是新的实例对象
  32. */
  33. factory { FactoryModel() }
  34. /*
  35. 获取的实例为单例
  36. */
  37. single { SingleModel() }
  38. single { MainModel() }
  39. /*
  40. 获取的实例为ViewModel,并且具有ViewModel的功能
  41. */
  42. viewModel { MainViewModel(get()) }
  43. }
  44. )
  45. }
  46. }
  47. }

2.获取对象

2.1 获取factory对象

为了演示清晰,这里创建FactoryActivity

  1. class FactoryActivity : AppCompatActivity() {
  2. /**
  3. * 使用[inject]获取FactoryModel实例
  4. * 其他教程中有说也可以使用[get]获取
  5. * 并且[inject]知识[lazy]版的[get]
  6. * 可以点开[inject]看到源码确实如此,但我这里是用[get]时提示
  7. * `Missing 'getValue(FactoryActivity, KProperty<*>)' method on delegate of type 'FactoryModel'`
  8. * 推荐:
  9. * 获取实例时使用[inject],初始化Koin时使用[get]
  10. */
  11. private val mModelOne: FactoryModel by inject()
  12. private val mModelTwo: FactoryModel by inject()
  13. override fun onCreate(savedInstanceState: Bundle?) {
  14. super.onCreate(savedInstanceState)
  15. setContentView(R.layout.activity_factory)
  16. btn_show.setOnClickListener {
  17. val msg = "one model is:\n $mModelOne\ntwo model is:\n $mModelTwo"
  18. loge(msg)
  19. tv.text = msg
  20. }
  21. }
  22. companion object {
  23. fun start(context: Context) {
  24. context.startActivity(Intent(context, FactoryActivity::class.java))
  25. }
  26. }
  27. }

用gif来看下效果
factory Gif

可以看到获取的对象是不同的,并且每次重新进入获取,都是新的对象。

2.2 获取single对象

SingleActivity的代码与FactoryActivity代码相似,这里获取的SingleModel
single GIF
每次获取的对象都是同一个,关闭Activity重新进来获取依然是相同的。

2.3 获取ViewModel对象

获取方式与上面两个不同,这里需要用viewModel来获取

  1. class ViewModelActivity : AppCompatActivity() {
  2. /**
  3. * 这里区分开来:
  4. * mViewModel由Koin的[viewModel]来生成
  5. * mViewModel2由[ViewModelProvider]生成
  6. * mModel由本身构造函数生成
  7. * 旋转屏幕后看是否具有原生ViewModel的功能
  8. */
  9. private val mViewModel: VmViewModel by viewModel()
  10. private val mViewModel2: VmViewModel by lazy {
  11. ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(VmViewModel::class.java)
  12. }
  13. private val mModel: VmViewModel by lazy { VmViewModel() }
  14. override fun onCreate(savedInstanceState: Bundle?) {
  15. super.onCreate(savedInstanceState)
  16. setContentView(R.layout.activity_view_model)
  17. mViewModel.count.observe(this, Observer {
  18. tv_vm.text = it.toString()
  19. })
  20. mViewModel2.count.observe(this, Observer {
  21. tv_vm2.text = it.toString()
  22. })
  23. mModel.count.observe(this, Observer {
  24. tv_m.text = it.toString()
  25. })
  26. btn_vm.setOnClickListener {
  27. mViewModel.addCount()
  28. }
  29. btn_vm2.setOnClickListener {
  30. mViewModel2.addCount()
  31. }
  32. btn_m.setOnClickListener {
  33. mModel.addCount()
  34. }
  35. }
  36. companion object {
  37. fun start(context: Context) {
  38. context.startActivity(Intent(context, ViewModelActivity::class.java))
  39. }
  40. }
  41. }

VMViewModel代码

  1. class VmViewModel : ViewModel() {
  2. val count = MutableLiveData<Int>()
  3. init {
  4. count.value = 0
  5. }
  6. fun addCount() {
  7. count.value = count.value!!.toInt() + 1
  8. }
  9. }

Gif效果
ViewModel GIF
前两个数据同步改变,说明Koin和ViewModelProvider获取的对象是同一个,这个涉及到ViewModel的实现原理,这里不做阐述。也就证明Koin获取的ViewModel真实可用。
由于横竖屏切换不方便GIF录制,这里就口述一下子,只有第三个数字会在切换时归0,所以,木有问题。

实现MVVM

框架再好,终究是要配合咱们实现功能的,这里以MVVM来做个示例:

Model

  1. class MvvmModel {
  2. /**
  3. * 模拟获取消息
  4. */
  5. fun getMsg(): String {
  6. return "这是一个普通的消息"
  7. }
  8. fun getError(): String {
  9. return "这是一个错误提醒"
  10. }
  11. }

ViewModel

  1. class MvvmViewModel(private val mView: IMvvmView) : ViewModel(),
  2. /**
  3. * 标示该类为Koin的组件,这样就可以在该类自由的使用 get()/inject()
  4. * 当然,如果你是个狼人,就喜欢不按套路走,也可以不实现该接口,使用
  5. * GlobalContext.get().koin.get()
  6. * GlobalContext.get().koin.inject()
  7. */
  8. KoinComponent {
  9. private val mModel: MvvmModel by inject()
  10. fun show() {
  11. mView.showMsg(mModel.getMsg())
  12. }
  13. fun error() {
  14. mView.showError(mModel.getError())
  15. }
  16. }

View

  1. interface IMvvmView{
  2. fun showMsg(msg:String)
  3. fun showError(error:String)
  4. }
  5. class MvvmActivity : AppCompatActivity(), IMvvmView {
  6. /**
  7. * 用Koin获取ViewModel
  8. * 因为[MvvmViewModel]的构造函数有IMvvmView,且Koin无法提供其实现
  9. * 这里需要手动添加该参数,配合App中的[viewModel { (view: IMvvmView) -> MvvmViewModel(view) }]
  10. */
  11. private val mViewModel: MvvmViewModel by viewModel {
  12. parametersOf(this)
  13. }
  14. override fun onCreate(savedInstanceState: Bundle?) {
  15. super.onCreate(savedInstanceState)
  16. val binding =
  17. DataBindingUtil.setContentView<ActivityMvvmBinding>(this, R.layout.activity_mvvm)
  18. binding.viewModel = mViewModel
  19. }
  20. override fun showMsg(msg: String) {
  21. showSnack(msg, btn_show)
  22. }
  23. override fun showError(error: String) {
  24. showSnack(error, btn_error)
  25. }
  26. companion object {
  27. fun start(context: Context) {
  28. context.startActivity(Intent(context, MvvmActivity::class.java))
  29. }
  30. }
  31. }

将MvvmModel和MvvmViewModel添加到Koin中

  1. /*
  2. 这里MVVMViewModel需要传入IMvvmView对象
  3. 这个接口是Activity来实现的,没有办法在Koin中注明实例,所以需要以此方式
  4. */
  5. viewModel { (view: IMvvmView) -> MvvmViewModel(view) }
  6. single { MvvmModel() }

这个示例主要展示如何将View对象在Koin框架中传递给ViewModel,ViewModel中又如何使用Koin获取对象。

结语

相较于Dagger2的每次生成都需要重新编译,Koin给我的感觉真的超清新,对ViewModel的支持也让我在选择框架时有更多的选择,强力推荐各位同道中人尝试一下Koin框架。
Demo地址

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注