美文网首页Android开发Android Tips
Koin--适用于Kotlin的超好用依赖注入框架,Dagger

Koin--适用于Kotlin的超好用依赖注入框架,Dagger

作者: 古诚欺 | 来源:发表于2020-02-16 21:48 被阅读0次

    今年呆在家中实在无聊,外面太危险了,还是在家学习比较安全可持续。

    过年期间,我又复习了几遍依赖注入控件Dagger.

    诶,什么是依赖注入?

    说白了就是降低跟类对象之间的耦合,当需要修改类对象的时候,能不修改被依赖的实例.其实我们平常就用到了很多的依赖注入,比如一个类的构造函数有一个参数,你new该类时要传一个参数,这就是一个注入,又比如类中常用的set()方法,这也是一个注入.

    这时就会有同学问了:那既然我们平常都有用到,为什么还要引用第三方的注入控件?

    那如果某一天,该类的构造函数中,需要再加一个参数怎么办,少量该类的实例修改比较方便,如果你持有该类的实例有100个呢,那如果一个个去修改地话,就非常不现实.又或者几个月后,该类的构造参数又增加了一个怎么办?所以我们就引用了依赖注入控件.

    说回Dagger,了解过Dagger的同学都知道,其实Dagger本身并不难(java适用部分)(狗头),Dagger当初就是为了方便Java开发而设计的,但是在Android中的适用就不太友善了,后来为了更加符合Android的风格,又有了DaggerAndroid部分的api(主要新增了很多标签),这两者结合,学习起来就很难,而且成本高,而且难。我废了九牛二虎之力终于弄懂Dagger之后(狗头),无意间发现了Koin这个框架,我的天,感觉像打开了新世界的大门,那么多天Dagger的东西都白学了。

    好了废话不多说,开始进入主题。代替Dagger的注入框架-----Koin(点看官网地址)

    什么是Koin(官方原文)?

    A pragmatic lightweight dependency injection framework for Kotlin developers. Written in pure Kotlin using functional resolution only: no proxy, no code generation, no reflection!

    (这年头没点英文也配叫专业(狗头))

    Koin是适用于Kotlin的轻量级注入工具,它的特点是:无代理,无代码生成,无反射。(Dagger时不时就要build,而且会产生大量复杂的代码)

    (我一开始以为koin是Kotlin团队开发的,一看koin跟Kotlin这么像(狗头))

    那接下来,怎么使用koin这个库,官网上也写的很清楚。

    pic1.png

    如果你是支持AndroidX的,那就使用


    pic2.png

    这边对这些地址大致介绍下,里面有个坑

    koin-android:这个是核心
    koin-androidx-scope:这个是作用域相关,通俗点讲就是数据D在A界面有用,在B界面无效
    koin-androidx-viewmodel:这个是viewmodel相关的
    koin-androidx-fragment:fragment相关的,不过这边有点问题,官网上说在2.1.0-alpha-3才加入这个库,但是我依赖之后发现该库报错
    

    接下来再附上我demo的github地址和图片,东西很简单,一条条列出来,大家可以看得全面清晰点.

    https://github.com/CaesarShao/CSKoin

    pic3.png

    1.初始化

    2.Factory用法

    3.Single用法

    4.viewModel用法

    5.带参的构造函数使用

    --5.1带多个参数的常规使用

    --5.2 qualifier--限定符使用

    --5.3 get()中使用qualifier

    --5.4声明进样参数---构造参数从外面传入

    6.KoinComponent接口,普通类中怎么使用注入对象

    7.跨Module模块注入依赖

    8.Scope--作用域的使用

    9.fragment模式地使用

    10.其他

    --10.1Properties--调用资产中的数值

    --10.2在startkoin之外,加载module(适用于组件化开发)

    1.在Application中初始化

    开始,接下来我们来看一下用法.在调用之前,我们需要在Application中,初始化一下koin.

    class MyApp : Application() {
        override fun onCreate() {
            super.onCreate()
            startKoin {
                //开始启动koin
                androidContext(this@MyApp)//这边传Application对象,这样你注入的类中,需要app对象的时候,可以直接使用
                modules(appModule)//这里面传各种被注入的模块对象,支持多模块注入
            }
        }
        val appModule = module {//里面添加各种注入对象
        }
    }
    

    通过调用startKoin来启动koin,里面填注入对象.

    接下来,我们来看一下,在实际项目中怎么使用,主要分为3大类,Factory,Single,viewmodel,我们来一个个了解

    2.普通注入使用方式--Factory注入

    Factory注入方式跟普通new一个对象一毛一样.

    class Person {
        fun speak() {
            CSKoinLog.I("武汉加油,中国加油")
        }
    }
    

    首先我们创建一个Person类,然后在我们的MyApp中的appModule中,将该Person类注入一下

    class MyApp : Application() {
          ...
            val appModule = module {//里面添加各种注入对象
            factory {//普通的注入方式
                Person()
            }
        }
    }
    

    大家可以看到就很像new了一个新的对象一样,好,注入完了之后,就可以在Activity中调用了,我在FactoryActivity中调用它.

    class FactoryActivity : AppCompatActivity() {
        //调用方式有大致下面几种,后面会再说到
        val person: Person by inject()//方法一
        val person2 by inject<Person>()//方法二
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_factory)
            val person3 = get<Person>()//方法三
            person.speak()
            person2.speak()
            person3.speak()
            CSKoinLog.I(person.hashCode().toString())
            CSKoinLog.I(person2.hashCode().toString())
            CSKoinLog.I(person3.hashCode().toString())
        }
    }
    
    pic4.png

    通过by inject(),get<>()这几种方法就可以获取被注入的对象,从日志上就可以看到,每次调用之后,都会生成一个新的对象,是不是很简单

    3.单例模式--Single用法

    Koin支持调用单例的方法,而且调用起来非常简单,也是在MyApp中的appModule中注入,不过这次注入方式为single

    class UserData {
        var userName: String? = null
        var age: Int? = null
        fun info() {
            CSKoinLog.I("用户名:" + userName + "////年龄:" + age)
        }
    }
    
    

    我新建了一个UserData类,该类中有几个属性,和一个输出所有属性的方法,在application的appModule中,将该类注入一下.

    val appModule = module {//里面添加各种注入对象
            ...
            single {//单例的注入方式
                UserData()
            }
        }
    

    然后我们在SingleActivity中调用

    class SingleActivity : AppCompatActivity() {
        val userData: UserData by inject()
        val userData2: UserData by inject()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_single)
            CSKoinLog.I(userData.hashCode().toString())
            CSKoinLog.I(userData2.hashCode().toString())
            userData.userName = "张飞"
            userData.age = 17
            userData2.info()
            userData2.userName = "关羽"
            userData2.age = 18
            userData.info()
        }
    }
    
    pic5.png

    从打印的日志中,我们可以看到,2个UserDate的对象的地址位是同一个,然后一个属性改变之后,另一个也对应地改变.

    4.viewModel用法(官方原文)

    our declared component must at least extends the android.arch.lifecycle.ViewModel class. You can specify how you inject the constructor of the class and use the get() function to inject dependencies.(时不时来句英文,尽显专业)

    koin还对MVVM模式中的viewModel封装,调用起来更加方便了,你的viewModel要继承lifecycle的ViewModel,然后可以通过get()方法,调用其他的注入对象(这个后面详细解说)

    class MyViewModel:ViewModel() {
        var NumData :Int = 0
        override fun onCleared() {
            super.onCleared()
            CSKoinLog.I("调用了销毁方法")
        }
    }
    

    我这边创建了一个MyViewModel类,该类继承lifecycle的ViewModel,在Application中的appModule中,将该类注册下:

    val appModule = module {//里面添加各种注入对象
            ...
            viewModel {
                MyViewModel()
            }
        }
    
    class ViewModelActivity : AppCompatActivity() {
        val myViewModel: MyViewModel by viewModel()
        val myViewModel2 by viewModel<MyViewModel>()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_view_model)
            CSKoinLog.I(myViewModel.hashCode().toString())
            CSKoinLog.I(myViewModel2.hashCode().toString())
            CSKoinLog.I(myViewModel.NumData.toString())
            findViewById<Button>(R.id.btn_change).setOnClickListener {
                myViewModel.NumData = 1
                CSKoinLog.I(myViewModel.NumData.toString())
            }
        }
    }
    
    pic6.png

    在ViewModelActivity中,我获取了2个viewModel对象,里面写了个按钮,点击可以改变viewModel中的属性,大家可以看到,这2个viewModel都是同一个,然后我修改了里面的值,接着我将手机屏幕变成横屏(这个看实际操作),再变成竖屏,Activity重新调用生命周期,但是viewModel仍旧是那个viewModel(地址不变),接着退出该界面,发现viewModel中的clear回调被调用了.
    好了到此,Koin的三种注入方式都讲完了,这时,就会有同学问:哎呀,古诚欺啊,如果我的构造函数中有参数,那应该如何调用呢?别急,请继续往下看

    5.1带参数的构造函数使用

    class AppData(var mApp:Application)
    

    大家请看,我现在有一个AppData类,该类有一个Application属性,那么该如何去注入这个类

    val appModule = module {
            ...
            factory {
                AppData(get())
            }
        }
    

    这时,就会有同学疑问,这个get()又是个啥?

    还记得最开始初始化Koin的时候,不是传了一个androidContext(this@MyApp),将appLication对象传了进去,Koin中,就已经记录了这个application,所以在你需要用到application对象的时候,直接通过get()方法调用就可以了.接着在NormalActivity中调用一下,看一下日志结果.

    class NormalActivity : AppCompatActivity() {
        val appData by inject<AppData>()
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_normal)
            CSKoinLog.I("application是否为空:" + (appData.mApp == null))
        }
    }
    
    pic7.png

    5.2通过限定符标记构造方法--qualifier

    class NormalData {
        var numData: Int = 0
        var userName: String = ""
        var mApp: Application? = null
        var appData: AppData? = null
        constructor(userName: String, numData: Int) {
            this.userName = userName
            this.numData = numData
        }
        constructor(appData: AppData) {
            this.appData = appData
        }
        constructor(mApp: Application) {
            this.mApp = mApp
        }
        fun printInfo(str: String) {//打印里面的信息
            CSKoinLog.I(str + "的信息    numData:" + numData + "///userName:" + userName + "///application是否为空:" + (mApp == null) + "///appData是否为空:" + (appData == null))
        }
    }
    

    现在我有一个NormalData类,里面是有三个构造函数,而且需要的参数都不同,那这种情况下应该怎么办,单独地注入方式已经无法满足我们了,这个时候,就需要用到qualifier限定符了.

    那什么是qualifier限定符,你可以理解为标签,就是给一个注入的构造函数贴个标签,用的时候,通过标签获取.首先我们来看一下factory注入的源码:

    inline fun <reified T> factory(
                qualifier: Qualifier? = null,
                override: Boolean = false,
                noinline definition: Definition<T>
        ): BeanDefinition<T> {
            val beanDefinition = DefinitionFactory.createFactory(qualifier, definition = definition)
            declareDefinition(beanDefinition, Options(override = override))
            return beanDefinition
        }
    

    (时不时来点源码,专业无疑)

    大家可以看到,factory注入方式中有一个Qualifier参数(single和viewModel这2个注入方式用法跟factory都一样),默认为null,这个就是要填写的限定符,然后我们再来看Qualifier的源码.

    interface Qualifier
    
    /**
     * Give a String qualifier
     */
    fun named(name: String) = StringQualifier(name)
    
    /**
     * Give a Type based qualifier
     */
    inline fun <reified T> named() = TypeQualifier(T::class)
    
    

    就是一个接口,通过named(string)的方法来标记.哦!原来里面填一个字符串啊,那so easy.

    好我们来看一下实际的用法,该类中有3个不同的构造函数,然后有一个方法,能够打印出该类中所有的属性

    class NormalData {
        var numData: Int = 0
        var userName: String = ""
        var mApp: Application? = null
        var appData: AppData? = null
        constructor(userName: String, numData: Int) {//构造方法1
            this.userName = userName
            this.numData = numData
        }
        constructor(appData: AppData) {//构造方法2
            this.appData = appData
        }
        constructor(mApp: Application) {//构造方法3
            this.mApp = mApp
        }
        fun printInfo(str: String) {//打印里面的信息
            CSKoinLog.I(str + "的信息    numData:" + numData + "///userName:" + userName + "///application是否为空:" + (mApp == null) + "///appData是否为空:" + (appData == null))
        }
    }
    

    这3种不同的构造函数,在application中分别注入

    val appModule = module {
            //里面添加各种注入对象
           ...
            factory {
                AppData(get())
            }
    
            factory(named("nameAnum")) {
                //该限定符的构造方法中包含字符串和数字
                NormalData("曹老板", 12)
            }
            factory(named("app")) {
                //该限定符定义构造方法中有appliaction的
                NormalData(get<Application>())
            }
            factory(named("appData")){
                //该限定符定义构造方法中有AppData的
                NormalData(get<AppData>())
            }
        }
    

    注入方式就是这样,通过named方法添加标签.其中,有2个构造函数需要各传一个参数,其中一个是application对象,另一个构造函数需要传AppData这个对象,这个对象在上面我们已经是有注入过的.(如果没注入过怎么办,我们下面会说).所以可以通过get()方式获取,但是获取哪一个呢,在get()中,我们有个泛型,通过泛型,就能够判断需要传哪一个(get这个方法也有个限定符named功能,后续会讲到).好了,注入完成之后,我们来看一下在NormalActivity中如何调用并且结果咋么样.

    class NormalActivity : AppCompatActivity() {
        val appData by inject<AppData>()
    ////
    
        val norData1 by inject<NormalData>(named("nameAnum"))//限定符1
        val norData2: NormalData by inject(named("app"))//限定符2
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_normal)
            CSKoinLog.I("application是否为空:" + (appData.mApp == null))
            ////////////
    
            val norData3 = get<NormalData>(named("appData"))//限定符3
            norData1.printInfo("norData1")
            norData2.printInfo("norData2")
            norData3.printInfo("norData3")
        }
    }
    
    I/caesarLogkoin: norData1的信息    numData:12///userName:曹老板///application是否为空:true///appData是否为空:true
    I/caesarLogkoin: norData2的信息    numData:0///userName:///application是否为空:false///appData是否为空:true
    I/caesarLogkoin: norData3的信息    numData:0///userName:///application是否为空:true///appData是否为空:false
    

    从打印的结果看,是不是就是我们所需要的,'nameAnum'标签的类中,只有字符串和数字,'app'标签的类中,只有application对象,'appData'标签的类中,只有AppData,是不是就是我们所预期的.

    5.3引用其他注入时的Qualifier使用---在get()中使用Qualifier

    我上面也说过,在注入的时候,如果你的构造方法中有参数,通过get()方法可以直接传你注入过的对象,但是如果对象有多种的话(例如我上面的NormalData),那你可以通过在get()中,加入Qualifier限定符来获取你所需要的对象

    class WeatherData(val normalData: NormalData) {
        fun printData(string: String) {
            normalData.printInfo(string)
        }
    }
    

    现在有一个WeatherData类,该类的构造方法需要传上面的NormalData,里面有一个输出传入的NormalData的属性的方法.然后我们看application类中的注入方式.

    val appModule = module {
            //里面添加各种注入对象
            ...
            factory(named("nameAnum")) {
                //该限定符的构造方法中包含字符串和数字
                NormalData("曹老板", 12)
            }
            factory(named("app")) {
                //该限定符定义构造方法中有appliaction的
                NormalData(get<Application>())
            }
            factory(named("appData")) {
                //该限定符定义构造方法中有AppData的
                NormalData(get<AppData>())
            }
            factory(named("wea_name")) {
                WeatherData(get<NormalData>(named("nameAnum")))
                //这边get方法中有一个泛型,可以指定传入的对象的类型,因为我构造函数只有一个,所以会智能输入,可以省略掉
            }
            factory(named("wea_app")) {
                WeatherData(get(named("app")))//这边就智能省略掉泛型了
            }
            factory(named("wea_appData")) {
                WeatherData(get(named("appData")))
            }
        }
    

    大家可以看下面的3个注入,在get的方法中,我传了限定符标签,便签里的值是上面的normalData注入的限定符的值,然后我在NormalTwoActivity中,分别获取这3个对象,看打印的日志

    class NormalTwoActivity : AppCompatActivity() {
        val weatherData by inject<WeatherData>(named("wea_name"))
        val weatherData2 by inject<WeatherData>(named("wea_app"))
        val weatherData3 by inject<WeatherData>(named("wea_appData"))
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_normal_two)
            weatherData.printData("weather1")
            weatherData2.printData("weather2")
            weatherData3.printData("weather3")
        }
    }
    
    I/caesarLogkoin: weather1的信息    numData:12///userName:曹老板///application是否为空:true///appData是否为空:true
    I/caesarLogkoin: weather2的信息    numData:0///userName:///application是否为空:false///appData是否为空:true
    I/caesarLogkoin: weather3的信息    numData:0///userName:///application是否为空:true///appData是否为空:false
    

    是不是也是符合我们所需要的,根据限定符的不同,获取不同的对象.

    5.4声明进样参数---构造参数从外面传入

    这时聪明的同学会问:哎呀,古诚欺啊,如果构造函数的参数,我想要自己从外面传入,比如说构造函数的参数是一个View对象,那我怎么调用呢?

    比如我有一个类ViewData,它的构造参数是View,里面有一个方法输出id

    class ViewData(val view: View) {
        fun prinId() {
            CSKoinLog.I(view.id.toString())
        }
    }
    

    像比如这种类的构造函数,我们无法通过注入获取,只能通过外部方式来传入参数.如何注入与调用.首先我们来看Application中的appModule

    val appModule = module {
            //里面添加各种注入对象
             ...
             factory {
                    (view: View) -> ViewData(view)//外部调用的方式,如果是多参数也一样,聪明的同学么应该要学会举一反三了
            }
        }
    

    然后我们在NormalTwoActivity中这样调用:

    class NormalTwoActivity : AppCompatActivity() {
        ...
        var btnShow: Button? = null
        val viewData by inject<ViewData> { parametersOf(btnShow) }
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_normal_two)
             ...
            btnShow = findViewById(R.id.btn_show)//这边要注意,btn的初始化要在ViewData的调用之前,否则会报空指针.koin的注入是懒加载模式的,只有在调用对象的时候,才会实例化对象
            viewData.prinId()
            CSKoinLog.I("这个是直接获取按钮id" + btnShow?.id.toString())
        }
    }
    

    通过parametersOf()函数来传参,打印的日志如下

    I/caesarLogkoin: 获取ViewData的按钮id2131165254
    I/caesarLogkoin: 这个是直接获取按钮id2131165254
    

    大家可以看到,ViewData中我们成功获取到了View视图对象.

    6实现KoinComponent,普通类中使用注入对象

    在一般的类中,我们如何依赖注入?

    class CompontData : KoinComponent {
        val appD1 by inject<AppData>()//懒加载模式
        val appD2 = get<AppData>()//非懒加载模式
        fun priInfo() {
            CSKoinLog.I("CompontData中appD1地址:" + appD1.hashCode() + "////appD2地址:" + appD2.hashCode())
        }
    }
    

    我们创建一个CompontData,该类实现了KoinComponent,在该类中,我们就可以通过by inject和get来过去被注入过的对象了.

    class NormalTwoActivity : AppCompatActivity() {
       ...
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_normal_two)
           ...
    
            CompontData().priInfo()//这边直接new对象,看里面注入的对象信息
        }
    }
    
    

    在NormalTwoActivity中,我这边new了一个NormalTwoActivity对象,然后直接打印里面的属性,结果OK

    I/caesarLogkoin: CompontData中appD1地址:228506535////appD2地址:871168084
    
    

    7.跨Module模块注入依赖

    大家的项目中肯定是有多个Module依赖,那么如何调用其他Module中注入的对象.

    我在项目中创建了一个mylibrary模块,该模块中有一个LibData这个空类

    class LibData {
    }
    
    object libModule {
        val theLibModule = module {
            //koin支持多个module注入
            single { LibData() }//这边用single方式注入
        }
    }
    

    然后又有一个libModule的object类,里面有一个module的方法,这个方法是不是跟我们application中的appModule是不是很像,其实一毛一样.
    然后我们在application中,将这个theLibModule添加进去.

    class MyApp : Application() {
        override fun onCreate() {
            super.onCreate()
            startKoin {
                //开始启动koin
                androidContext(this@MyApp)//这边传Application对象,这样你注入的类中,需要app对象的时候,可以直接使用
                modules(appModule,libModule.theLibModule)//这里面传各种被注入的模块对象,支持多模块注入,在2.0.1之后才支持vararg调用,之前可以使用集合来调用
            }
        }
    

    大家可以看到,我添加了一个libModule.theLibModule对象,好,这样之后,我们就可以使用libModule.theLibModule下被注入的对象了,使用方式跟上面的一样.

    然后我的app模块中,有一个ModuleData类,该构造函数需要传一个LibData对象

    class ModuleData(val libData:LibData) {
    }
    

    然后将ModuleData注入

    val appModule = module {
            //里面添加各种注入对象
            ...        
            factory { 
                ModuleData(get())
            }
        }
    

    其他都一样,然后我创建了一个LibModuleActivity界面,在里面获取LibData和ModuleData对象,并且打印出LibData

    class LibModuleActivity : AppCompatActivity() {
    
        val libData by inject<LibData>()
        val moduleData by inject<ModuleData>()
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_lib_module)
            CSKoinLog.I("直接依赖的libData:"+libData.hashCode().toString())
            CSKoinLog.I("moduleData中的libData:"+moduleData.libData.hashCode().toString())
        }
    }
    
    I/caesarLogkoin: 直接依赖的libData:410923983
    I/caesarLogkoin: moduleData中的libData:410923983
    

    是不是符合我们的预想

    8.Scope---作用域的使用

    什么是Scope作用域,这个东西其实跟viewModel有点相似,scope下的对象可以跟一个视图绑定起来,并且该被绑定的对象是单例的模式,其他界面通过scopeId可以获取这个对象.当该视图被销毁的时候,被绑定的对象也会被销毁.其他界面也就获取不到这个scope对象了.

    class ScopeData {
    }
    

    现在我有一个ScopeData这个类.然后在appModule中,用scope方式注入.scope注入方式有2种,先讲第一种

    val appModule = module {
            //里面添加各种注入对象
            ...        
            scope(named("myScope")) {//scope类型的注入方式一,通过标签的方式
                scoped {
                    ScopeData()
                }
            }
    
        }
    

    然后我在ScopeCurentActivity中,将该scope与该视图绑定起来.

    class ScopeCurentActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_scope_curent)
            val scope = getKoin().createScope("scopeId1", named("myScope"))//创建scope方式一
            bindScope(scope)//scope与界面绑定,只有这边创建绑定了之后,其他地方才能获取到这个作用域
            val scopeData = scope.get<ScopeData>()//获取作用域下的类
           CSKoinLog.I("ScopeCurentActivity中的ScopeData是否为空:" + (scopeData == null))
            CSKoinLog.I("ScopeCurentActivity中的ScopeData地址:" + (scopeData.hashCode()))
        }
    }
    

    大家可以看到,通过标签,我创建了一个id为"scopeId1"的作用域,该作用域的标签为myScope,在application中该标签注入的对象为ScopeData.接着,就可以通过get方式来获取这个对象了,注意,这边必须要先通过createScope创建了作用域之后,然后绑定视图,这样你其他的地方才能获取到这个作用域.接着就打印这个对象的地址.

    我在MainActivity中也有个方法,当点击跳转到ScopeCurentActivity时,会调用doScope方法,获取下作用域,进行一个比较,然后等2秒之后Scope跟ScopeCurentActivity绑定了之后,再获取下,下面是MainActivity的代码(注意getScopeOrNull这个方法)

    class MainActivity : AppCompatActivity() {
       ...
    
     fun doScope() {
            val scopeData = getKoin().getScopeOrNull("scopeId1")////注意这边获取一个可空的方法,直接getScope获取到的可能为空,会报空指针
            CSKoinLog.I("MainActivity中获取scope是否为空" + (scopeData == null))
            Thread {
                Thread.sleep(2000)
                val scopeData2 = getKoin().getScopeOrNull("scopeId1")
                val data = scopeData2?.get<ScopeData>()
                CSKoinLog.I("MainActivity中延迟2秒获取的scope是否为空" + (scopeData2 == null))
                CSKoinLog.I("MainActivity中延迟2秒获取的data的地址" + (data.hashCode()))
            }.start()
        }
    
        override fun onResume() {
            super.onResume()
            Thread {
                Thread.sleep(2000)
                val scopeData2 = getKoin().getScopeOrNull("scopeId1")
                CSKoinLog.I("MainActivity的onResume中获取scope是否为空" + (scopeData2 == null))
            }.start()
        }
    }
    
    I/caesarLogkoin: MainActivity的onResume中获取scope是否为空true
    //这边点击跳转之后
    I/caesarLogkoin: MainActivity中获取scope是否为空true
    I/caesarLogkoin: ScopeCurentActivity中的ScopeData是否为空:false
    I/caesarLogkoin: ScopeCurentActivity中的ScopeData地址:380234974
    I/caesarLogkoin: MainActivity中延迟2秒获取的scope是否为空false
    I/caesarLogkoin: MainActivity中延迟2秒获取的data的地址380234974
    //这边ScopeCurentActivity按了返回键之后
    I/caesarLogkoin: MainActivity的onResume中获取scope是否为空true
    

    是不是从日志中,就可以看出结果.然后上面也说过了,scope的获取方式有2种,接着是第二种方式,第二种方式我感觉有点鸡肋,虽然官网上的demo就是这样的写法,大家请往下看.

    class ScopeTypeTwo {
    }
    

    我这边有一个ScopeTypeTwo类,然后我在appModule中,将该类注入一下

    val appModule = module {
            //里面添加各种注入对象
            ...
            scope(named<ScopeCurentActivity>()){
                scoped{
                    ScopeTypeTwo()
                }
            }
    
        }
    

    这种注入方式,可以直接传一个Activity的泛型标签,就是在注入的时候,就已经跟对应的视图绑定起来,这样的话,你在ScopeCurentActivity中,可以直接通过currentScope.inject()的方式,获取到该对象.

    class ScopeCurentActivity : AppCompatActivity() {
    
        val csopeTypeW: ScopeTypeTwo by currentScope.inject()//直接获取了ScopeTypeTwo对象
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_scope_curent)
            ...
             CSKoinLog.I("ScopeCurentActivity中的ScopeTypeTwo的scopeid值:" + currentScope.id)
            CSKoinLog.scopeId = currentScope.id
            CSKoinLog.I("ScopeCurentActivity中的ScopeTypeTwo的地址位" + (csopeTypeW.hashCode()))
        }
    }
    

    不过这种方式,他其实已经帮你创建好了scopeid的值,该scopeid的值就是你当前视图的地址,但是有个问题,就是其他视图要通过scope方式获取对象的话,必须有一个scopeid,如果你ScopeCurentActivity没有运行的话,该地址位是为空的,而且scope对象也没有,所以我这边将ScopeCurentActivity的地址位保存在一个地方.其他视图要用的时候,直接去获取就可以了.

    object CSKoinLog {
       ...
        var scopeId :ScopeID = ""//保存Scopeid
    }
    

    接着我在MainActivity中,再去获取ScopeTypeTwo这个对象

    class MainActivity : AppCompatActivity() {
    
     fun doScope() {
          ...
            val typeScope = getKoin().getScopeOrNull(CSKoinLog.scopeId)
            CSKoinLog.I("MainActivity获取的ScopeTypeTwo是否为空:" + (typeScope == null))
            Thread {
                Thread.sleep(2000)
               ...
                val typeScope2 = getKoin().getScopeOrNull(CSKoinLog.scopeId)
                val typeTwo = typeScope2?.get<ScopeTypeTwo>()
                CSKoinLog.I("MainActivity中延迟2秒获取的ScopeTypeTwo是否为空" + (typeScope2 == null))
                CSKoinLog.I("MainActivity中延迟2秒获取的ScopeTypeTwo地址位:" + (typeTwo.hashCode()))
            }.start()
        }
    }
    
    //点击跳转按钮,到ScopeCurentActivity
    MainActivity获取的ScopeTypeTwo是否为空:true
    ScopeCurentActivity中的ScopeTypeTwo的scopeid值:com.caesar.cskoin.scope.ScopeCurentActivity@703993064
    ScopeCurentActivity中的ScopeTypeTwo的地址位528591061
    //2秒之后
    MainActivity中延迟2秒获取的ScopeTypeTwo是否为空false
    MainActivity中延迟2秒获取的ScopeTypeTwo地址位:528591061
    

    从日志中,就可以看到我们验证的结果.

    9.fragment使用

    在2.1.0-alpha-3版本开始,Koin支持对fragment的注入了(官网说的,但是我调试的时候有问题),但是我调试的时候,发现fragment相关的api在当前最新的版本及之前的版本中都有问题,api没有相关的内容,估计作者还在调试中,但是官网上已经给出了fragment的使用方法,之后的版本应该会修复好.接下来我们来看看怎么使用.(无验证下面的调用,但是可以通过作用域的方式来调用,我demo中调试过ok)

    class MyFragment(val str: String) : Fragment() {
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Inflate the layout for this fragment
            return inflater.inflate(R.layout.fragment_my, container, false)
        }
    }
    

    我创建了一个MyFragment这样的碎片.该构造方法中有一个字符串的参数,我们在application中,需要将该对象注入.

    class MyApp : Application() {
        override fun onCreate() {
            super.onCreate()
            startKoin {
                //开始启动koin
               ...            
    //            fragmentFactory() 暂时2.1.0-alpha-3这个版本之前有问题,引用不到,估计作者还在调试,文档上的用法就是这样的
                modules(appModule, libModule.theLibModule)//这里面传各种被注入的模块对象,支持多模块注入,在2.0.1之后才支持vararg调用
            }
        }
    
     val appModule = module {
            //里面添加各种注入对象
            ...
    
    //        fragment { MyFragment("张三") }//暂时2.1.0-alpha-3这个版本之前有问题,引用不到,估计作者还在调试,文档上的用法就是这样的
        }
    }
    

    首先在startKoin中,我们需要传入fragmentFactory()这个工厂类,然后在appModule中,通过fragment注入类型来注入我们的MyFragment.
    好接下来就是在界面中调用我们的碎片了

    class FragActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            setupKoinFragmentFactory()//要在调用父类的方法之前调用
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_frag)
            supportFragmentManager.beginTransaction()
                .replace(R.id.mvvm_frame, MyFragment::class.java, null, null)
                .commit()
        }
    }
    

    就是这么简单调用.目前fragment的注入方式有问题,我无法验证.官网还有一种碎片跟作用域结合的用法,我这边也一并给大家写出来,也不验证了.

    val appModule = module {
            //里面添加各种注入对象
     scope(named<FragActivity>()){
                scoped {
                   MyFragment("张三")
                }
           }
    }
    
    class FragActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
    //        setupKoinFragmentFactory()//要在调用父类的方法之前调用
    //        setupKoinFragmentFactory(currentScope)//碎片跟作用域的用法
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_frag)
    //        supportFragmentManager.beginTransaction()
    //            .replace(R.id.mvvm_frame, MyFragment::class.java, null, null)
    //            .commit()
        }
    }
    

    就是在作用域中,注入碎片,然后在界面中,用setupKoinFragmentFactory(currentScope)方法绑定界面,其实这种做法直接用我上面讲到的Scope的方式去获取碎片就可以用了(大家都是成熟的程序员,要自己去举一反三了)

    10.1.Properties--调用资产中的数值

    Koin还能直接调用资产文件中的内容.我在assets资产文件夹下,创建了一个koin.properties(默认的名字,你也可以自己命名),里面就一个键值对

    userName = "abc123"
    

    然后在application的初始化中,加上

    class MyApp : Application() {
        override fun onCreate() {
            super.onCreate()
            startKoin {
                //开始启动koin
               ...
                androidFileProperties()//默认名字为koin.properties,你也可以直接重新设置名称
                modules(appModule, libModule.theLibModule)//这里面传各种被注入的模块对象,支持多模块注入,在2.0.1之后才支持vararg调用
            }
        }
    }
    

    调用一下androidFileProperties()就可以了,接着就是如何使用了.

    class ProData(val string: String) {
    }
    

    我创建了一个ProData类,构造函数传一个字符串,然后在application中注入

    val appModule = module {
            //里面添加各种注入对象
          ...
           factory {
               ProData(getProperty("userName"))//该方法可以设置泛型对象,你已经是一个成熟的程序员了,要学会自己举一反三
            }
    }
    

    通过getProperty方法,里面传键的值,接着再调用下就可以了.

    class OtherActivity : AppCompatActivity() {
        val proData :ProData by inject()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_other)
            CSKoinLog.I("通过Property方式获取:"+proData.string)
        }
    }
    
    I/caesarLogkoin: 通过Property方式获取:abc123
    

    10.2.在startkoin之外,加载module

    如果你的项目是用组件化开发的,那这个方式很适合。

    class TimeData(val ProD:ProData) {
    }
    

    我这边有一个TimeData类,该类的构造参数是ProData,上面文章中,我将ProData在appModule中注入了,接下来我再创建一个注入modul,将TimeData在新的module中注入,然后再去加载这个module.

    class MyApp : Application() {
        override fun onCreate() {
            super.onCreate()
            startKoin {
               ...
                modules(appModule, libModule.theLibModule)//这里面传各种被注入的模块对象,支持多模块注入,在2.0.1之后才支持vararg调用
            }
            loadKoinModules(otherModule)
        }
    
         val otherModule = module {
            factory {
                TimeData(get())
            }
    
        }
    

    大家请看,我这边新创了一个otherModule,然后在里面,将TimeData注入,可以看到,参数ProData我传了一个get(),因为ProData在上面中,我已经注入过了,所以通过get()方式,就可以获取到。然后我在OtherActivity中调用。

    class OtherActivity : AppCompatActivity() {
        val proData :ProData by inject()
        val timeData :TimeData by inject()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_other)
            CSKoinLog.I("通过Property方式获取:"+proData.string)
            CSKoinLog.I("timeData里面的属性:"+timeData.ProD.string)
        }
    }
    
    I/caesarLogkoin: 通过Property方式获取:abc123
    I/caesarLogkoin: timeData里面的属性:abc123
    

    从日志中,我们验证了。

    至此,Koin的使用方法我都已经给大家详解了(其实还有几个小东西,但是在实际项目中用不到)(觉得写得还不错的,给个👍)

    转载请标明出处

    相关文章

      网友评论

        本文标题:Koin--适用于Kotlin的超好用依赖注入框架,Dagger

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