美文网首页
Koin实战

Koin实战

作者: 一缸米 | 来源:发表于2019-09-26 13:41 被阅读0次

    对于强大的注解框架,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<String>("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来看下效果


    factory Gif

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

    2.2 获取single对象

    SingleActivity的代码与FactoryActivity代码相似,这里获取的SingleModel

    single GIF
    每次获取的对象都是同一个,关闭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<Int>()
    
        init {
            count.value = 0
        }
    
        fun addCount() {
            count.value = count.value!!.toInt() + 1
        }
    
    }
    

    Gif效果


    ViewModel 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<ActivityMvvmBinding>(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地址

    相关文章

      网友评论

          本文标题:Koin实战

          本文链接:https://www.haomeiwen.com/subject/huwlyctx.html