Koin实战

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


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()

    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(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实战)