Android开发Android Tips

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

2020-02-16  本文已影响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的使用方法我都已经给大家详解了(其实还有几个小东西,但是在实际项目中用不到)(觉得写得还不错的,给个👍)

转载请标明出处

上一篇下一篇

猜你喜欢

热点阅读