Koin--適用于Kotlin的超好用依賴注入框架,Dagger替代者,Koin史上最詳細(xì)解說,一篇就夠了,媽媽再也不用擔(dān)心我不會(huì)依賴注入了

今年呆在家中實(shí)在無聊抑堡,外面太危險(xiǎn)了摆出,還是在家學(xué)習(xí)比較安全可持續(xù)。

過年期間首妖,我又復(fù)習(xí)了幾遍依賴注入控件Dagger.

誒,什么是依賴注入?

說白了就是降低跟類對(duì)象之間的耦合,當(dāng)需要修改類對(duì)象的時(shí)候,能不修改被依賴的實(shí)例.其實(shí)我們平常就用到了很多的依賴注入,比如一個(gè)類的構(gòu)造函數(shù)有一個(gè)參數(shù),你new該類時(shí)要傳一個(gè)參數(shù),這就是一個(gè)注入,又比如類中常用的set()方法,這也是一個(gè)注入.

這時(shí)就會(huì)有同學(xué)問了:那既然我們平常都有用到,為什么還要引用第三方的注入控件?

那如果某一天,該類的構(gòu)造函數(shù)中,需要再加一個(gè)參數(shù)怎么辦,少量該類的實(shí)例修改比較方便,如果你持有該類的實(shí)例有100個(gè)呢,那如果一個(gè)個(gè)去修改地話,就非常不現(xiàn)實(shí).又或者幾個(gè)月后,該類的構(gòu)造參數(shù)又增加了一個(gè)怎么辦?所以我們就引用了依賴注入控件.

說回Dagger,了解過Dagger的同學(xué)都知道,其實(shí)Dagger本身并不難(java適用部分)(狗頭)偎漫,Dagger當(dāng)初就是為了方便Java開發(fā)而設(shè)計(jì)的,但是在Android中的適用就不太友善了有缆,后來為了更加符合Android的風(fēng)格象踊,又有了DaggerAndroid部分的api(主要新增了很多標(biāo)簽),這兩者結(jié)合妒貌,學(xué)習(xí)起來就很難通危,而且成本高,而且難灌曙。我廢了九牛二虎之力終于弄懂Dagger之后(狗頭)菊碟,無意間發(fā)現(xiàn)了Koin這個(gè)框架,我的天在刺,感覺像打開了新世界的大門,那么多天Dagger的東西都白學(xué)了逆害。

好了廢話不多說,開始進(jìn)入主題蚣驼。代替Dagger的注入框架-----Koin(點(diǎn)看官網(wǎng)地址)

什么是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!

(這年頭沒點(diǎn)英文也配叫專業(yè)(狗頭))

Koin是適用于Kotlin的輕量級(jí)注入工具魄幕,它的特點(diǎn)是:無代理,無代碼生成颖杏,無反射纯陨。(Dagger時(shí)不時(shí)就要build,而且會(huì)產(chǎn)生大量復(fù)雜的代碼)

(我一開始以為koin是Kotlin團(tuán)隊(duì)開發(fā)的留储,一看koin跟Kotlin這么像(狗頭))

那接下來翼抠,怎么使用koin這個(gè)庫,官網(wǎng)上也寫的很清楚获讳。

pic1.png

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


pic2.png

這邊對(duì)這些地址大致介紹下,里面有個(gè)坑

koin-android:這個(gè)是核心
koin-androidx-scope:這個(gè)是作用域相關(guān),通俗點(diǎn)講就是數(shù)據(jù)D在A界面有用,在B界面無效
koin-androidx-viewmodel:這個(gè)是viewmodel相關(guān)的
koin-androidx-fragment:fragment相關(guān)的,不過這邊有點(diǎn)問題,官網(wǎng)上說在2.1.0-alpha-3才加入這個(gè)庫,但是我依賴之后發(fā)現(xiàn)該庫報(bào)錯(cuò)

接下來再附上我demo的github地址和圖片,東西很簡單,一條條列出來,大家可以看得全面清晰點(diǎn).

https://github.com/CaesarShao/CSKoin

pic3.png

1.初始化

2.Factory用法

3.Single用法

4.viewModel用法

5.帶參的構(gòu)造函數(shù)使用

--5.1帶多個(gè)參數(shù)的常規(guī)使用

--5.2 qualifier--限定符使用

--5.3 get()中使用qualifier

--5.4聲明進(jìn)樣參數(shù)---構(gòu)造參數(shù)從外面?zhèn)魅?/h3>

6.KoinComponent接口,普通類中怎么使用注入對(duì)象

7.跨Module模塊注入依賴

8.Scope--作用域的使用

9.fragment模式地使用

10.其他

--10.1Properties--調(diào)用資產(chǎn)中的數(shù)值

--10.2在startkoin之外阴颖,加載module(適用于組件化開發(fā))

1.在Application中初始化

開始,接下來我們來看一下用法.在調(diào)用之前,我們需要在Application中,初始化一下koin.

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            //開始啟動(dòng)koin
            androidContext(this@MyApp)//這邊傳Application對(duì)象,這樣你注入的類中,需要app對(duì)象的時(shí)候,可以直接使用
            modules(appModule)//這里面?zhèn)鞲鞣N被注入的模塊對(duì)象,支持多模塊注入
        }
    }
    val appModule = module {//里面添加各種注入對(duì)象
    }
}

通過調(diào)用startKoin來啟動(dòng)koin,里面填注入對(duì)象.

接下來,我們來看一下,在實(shí)際項(xiàng)目中怎么使用,主要分為3大類,Factory,Single,viewmodel,我們來一個(gè)個(gè)了解

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

Factory注入方式跟普通new一個(gè)對(duì)象一毛一樣.

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

首先我們創(chuàng)建一個(gè)Person類,然后在我們的MyApp中的appModule中,將該P(yáng)erson類注入一下

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

大家可以看到就很像new了一個(gè)新的對(duì)象一樣,好,注入完了之后,就可以在Activity中調(diào)用了,我在FactoryActivity中調(diào)用它.

class FactoryActivity : AppCompatActivity() {
    //調(diào)用方式有大致下面幾種,后面會(huì)再說到
    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<>()這幾種方法就可以獲取被注入的對(duì)象,從日志上就可以看到,每次調(diào)用之后,都會(huì)生成一個(gè)新的對(duì)象,是不是很簡單

3.單例模式--Single用法

Koin支持調(diào)用單例的方法,而且調(diào)用起來非常簡單,也是在MyApp中的appModule中注入,不過這次注入方式為single

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

我新建了一個(gè)UserData類,該類中有幾個(gè)屬性,和一個(gè)輸出所有屬性的方法,在application的appModule中,將該類注入一下.

val appModule = module {//里面添加各種注入對(duì)象
        ...
        single {//單例的注入方式
            UserData()
        }
    }

然后我們?cè)赟ingleActivity中調(diào)用

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 = "關(guān)羽"
        userData2.age = 18
        userData.info()
    }
}
pic5.png

從打印的日志中,我們可以看到,2個(gè)UserDate的對(duì)象的地址位是同一個(gè),然后一個(gè)屬性改變之后,另一個(gè)也對(duì)應(yīng)地改變.

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.(時(shí)不時(shí)來句英文,盡顯專業(yè))

koin還對(duì)MVVM模式中的viewModel封裝,調(diào)用起來更加方便了,你的viewModel要繼承l(wèi)ifecycle的ViewModel,然后可以通過get()方法,調(diào)用其他的注入對(duì)象(這個(gè)后面詳細(xì)解說)

class MyViewModel:ViewModel() {
    var NumData :Int = 0
    override fun onCleared() {
        super.onCleared()
        CSKoinLog.I("調(diào)用了銷毀方法")
    }
}

我這邊創(chuàng)建了一個(gè)MyViewModel類,該類繼承l(wèi)ifecycle的ViewModel,在Application中的appModule中,將該類注冊(cè)下:

val appModule = module {//里面添加各種注入對(duì)象
        ...
        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個(gè)viewModel對(duì)象,里面寫了個(gè)按鈕,點(diǎn)擊可以改變viewModel中的屬性,大家可以看到,這2個(gè)viewModel都是同一個(gè),然后我修改了里面的值,接著我將手機(jī)屏幕變成橫屏(這個(gè)看實(shí)際操作),再變成豎屏,Activity重新調(diào)用生命周期,但是viewModel仍舊是那個(gè)viewModel(地址不變),接著退出該界面,發(fā)現(xiàn)viewModel中的clear回調(diào)被調(diào)用了.
好了到此,Koin的三種注入方式都講完了,這時(shí),就會(huì)有同學(xué)問:哎呀,古誠欺啊,如果我的構(gòu)造函數(shù)中有參數(shù),那應(yīng)該如何調(diào)用呢?別急,請(qǐng)繼續(xù)往下看

5.1帶參數(shù)的構(gòu)造函數(shù)使用

class AppData(var mApp:Application)

大家請(qǐng)看,我現(xiàn)在有一個(gè)AppData類,該類有一個(gè)Application屬性,那么該如何去注入這個(gè)類

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

這時(shí),就會(huì)有同學(xué)疑問,這個(gè)get()又是個(gè)啥?

還記得最開始初始化Koin的時(shí)候,不是傳了一個(gè)androidContext(this@MyApp),將appLication對(duì)象傳了進(jìn)去,Koin中,就已經(jīng)記錄了這個(gè)application,所以在你需要用到application對(duì)象的時(shí)候,直接通過get()方法調(diào)用就可以了.接著在NormalActivity中調(diào)用一下,看一下日志結(jié)果.

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通過限定符標(biāo)記構(gòu)造方法--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 + "http:///userName:" + userName + "http:///application是否為空:" + (mApp == null) + "http:///appData是否為空:" + (appData == null))
    }
}

現(xiàn)在我有一個(gè)NormalData類,里面是有三個(gè)構(gòu)造函數(shù),而且需要的參數(shù)都不同,那這種情況下應(yīng)該怎么辦,單獨(dú)地注入方式已經(jīng)無法滿足我們了,這個(gè)時(shí)候,就需要用到qualifier限定符了.

那什么是qualifier限定符,你可以理解為標(biāo)簽,就是給一個(gè)注入的構(gòu)造函數(shù)貼個(gè)標(biāo)簽,用的時(shí)候,通過標(biāo)簽獲取.首先我們來看一下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
    }

(時(shí)不時(shí)來點(diǎn)源碼,專業(yè)無疑)

大家可以看到,factory注入方式中有一個(gè)Qualifier參數(shù)(single和viewModel這2個(gè)注入方式用法跟factory都一樣),默認(rèn)為null,這個(gè)就是要填寫的限定符,然后我們?cè)賮砜碤ualifier的源碼.

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)

就是一個(gè)接口,通過named(string)的方法來標(biāo)記.哦!原來里面填一個(gè)字符串啊,那so easy.

好我們來看一下實(shí)際的用法,該類中有3個(gè)不同的構(gòu)造函數(shù),然后有一個(gè)方法,能夠打印出該類中所有的屬性

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

這3種不同的構(gòu)造函數(shù),在application中分別注入

val appModule = module {
        //里面添加各種注入對(duì)象
       ...
        factory {
            AppData(get())
        }

        factory(named("nameAnum")) {
            //該限定符的構(gòu)造方法中包含字符串和數(shù)字
            NormalData("曹老板", 12)
        }
        factory(named("app")) {
            //該限定符定義構(gòu)造方法中有appliaction的
            NormalData(get<Application>())
        }
        factory(named("appData")){
            //該限定符定義構(gòu)造方法中有AppData的
            NormalData(get<AppData>())
        }
    }

注入方式就是這樣,通過named方法添加標(biāo)簽.其中,有2個(gè)構(gòu)造函數(shù)需要各傳一個(gè)參數(shù),其中一個(gè)是application對(duì)象,另一個(gè)構(gòu)造函數(shù)需要傳AppData這個(gè)對(duì)象,這個(gè)對(duì)象在上面我們已經(jīng)是有注入過的.(如果沒注入過怎么辦,我們下面會(huì)說).所以可以通過get()方式獲取,但是獲取哪一個(gè)呢,在get()中,我們有個(gè)泛型,通過泛型,就能夠判斷需要傳哪一個(gè)(get這個(gè)方法也有個(gè)限定符named功能,后續(xù)會(huì)講到).好了,注入完成之后,我們來看一下在NormalActivity中如何調(diào)用并且結(jié)果咋么樣.

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

從打印的結(jié)果看,是不是就是我們所需要的,'nameAnum'標(biāo)簽的類中,只有字符串和數(shù)字,'app'標(biāo)簽的類中,只有application對(duì)象,'appData'標(biāo)簽的類中,只有AppData,是不是就是我們所預(yù)期的.

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

我上面也說過,在注入的時(shí)候,如果你的構(gòu)造方法中有參數(shù),通過get()方法可以直接傳你注入過的對(duì)象,但是如果對(duì)象有多種的話(例如我上面的NormalData),那你可以通過在get()中,加入Qualifier限定符來獲取你所需要的對(duì)象

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

現(xiàn)在有一個(gè)WeatherData類,該類的構(gòu)造方法需要傳上面的NormalData,里面有一個(gè)輸出傳入的NormalData的屬性的方法.然后我們看application類中的注入方式.

val appModule = module {
        //里面添加各種注入對(duì)象
        ...
        factory(named("nameAnum")) {
            //該限定符的構(gòu)造方法中包含字符串和數(shù)字
            NormalData("曹老板", 12)
        }
        factory(named("app")) {
            //該限定符定義構(gòu)造方法中有appliaction的
            NormalData(get<Application>())
        }
        factory(named("appData")) {
            //該限定符定義構(gòu)造方法中有AppData的
            NormalData(get<AppData>())
        }
        factory(named("wea_name")) {
            WeatherData(get<NormalData>(named("nameAnum")))
            //這邊get方法中有一個(gè)泛型,可以指定傳入的對(duì)象的類型,因?yàn)槲覙?gòu)造函數(shù)只有一個(gè),所以會(huì)智能輸入,可以省略掉
        }
        factory(named("wea_app")) {
            WeatherData(get(named("app")))//這邊就智能省略掉泛型了
        }
        factory(named("wea_appData")) {
            WeatherData(get(named("appData")))
        }
    }

大家可以看下面的3個(gè)注入,在get的方法中,我傳了限定符標(biāo)簽,便簽里的值是上面的normalData注入的限定符的值,然后我在NormalTwoActivity中,分別獲取這3個(gè)對(duì)象,看打印的日志

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

是不是也是符合我們所需要的,根據(jù)限定符的不同,獲取不同的對(duì)象.

5.4聲明進(jìn)樣參數(shù)---構(gòu)造參數(shù)從外面?zhèn)魅?/h3>

這時(shí)聰明的同學(xué)會(huì)問:哎呀,古誠欺啊,如果構(gòu)造函數(shù)的參數(shù),我想要自己從外面?zhèn)魅?比如說構(gòu)造函數(shù)的參數(shù)是一個(gè)View對(duì)象,那我怎么調(diào)用呢?

比如我有一個(gè)類ViewData,它的構(gòu)造參數(shù)是View,里面有一個(gè)方法輸出id

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

像比如這種類的構(gòu)造函數(shù),我們無法通過注入獲取,只能通過外部方式來傳入?yún)?shù).如何注入與調(diào)用.首先我們來看Application中的appModule

val appModule = module {
        //里面添加各種注入對(duì)象
         ...
         factory {
                (view: View) -> ViewData(view)//外部調(diào)用的方式,如果是多參數(shù)也一樣,聰明的同學(xué)么應(yīng)該要學(xué)會(huì)舉一反三了
        }
    }

然后我們?cè)贜ormalTwoActivity中這樣調(diào)用:

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的調(diào)用之前,否則會(huì)報(bào)空指針.koin的注入是懶加載模式的,只有在調(diào)用對(duì)象的時(shí)候,才會(huì)實(shí)例化對(duì)象
        viewData.prinId()
        CSKoinLog.I("這個(gè)是直接獲取按鈕id" + btnShow?.id.toString())
    }
}

通過parametersOf()函數(shù)來傳參,打印的日志如下

I/caesarLogkoin: 獲取ViewData的按鈕id2131165254
I/caesarLogkoin: 這個(gè)是直接獲取按鈕id2131165254

大家可以看到,ViewData中我們成功獲取到了View視圖對(duì)象.

6實(shí)現(xiàn)KoinComponent,普通類中使用注入對(duì)象

在一般的類中,我們?nèi)绾我蕾囎⑷?

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

我們創(chuàng)建一個(gè)CompontData,該類實(shí)現(xiàn)了KoinComponent,在該類中,我們就可以通過by inject和get來過去被注入過的對(duì)象了.

class NormalTwoActivity : AppCompatActivity() {
   ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_normal_two)
       ...

        CompontData().priInfo()//這邊直接new對(duì)象,看里面注入的對(duì)象信息
    }
}

在NormalTwoActivity中,我這邊new了一個(gè)NormalTwoActivity對(duì)象,然后直接打印里面的屬性,結(jié)果OK

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

7.跨Module模塊注入依賴

大家的項(xiàng)目中肯定是有多個(gè)Module依賴,那么如何調(diào)用其他Module中注入的對(duì)象.

我在項(xiàng)目中創(chuàng)建了一個(gè)mylibrary模塊,該模塊中有一個(gè)LibData這個(gè)空類

class LibData {
}
object libModule {
    val theLibModule = module {
        //koin支持多個(gè)module注入
        single { LibData() }//這邊用single方式注入
    }
}

然后又有一個(gè)libModule的object類,里面有一個(gè)module的方法,這個(gè)方法是不是跟我們application中的appModule是不是很像,其實(shí)一毛一樣.
然后我們?cè)赼pplication中,將這個(gè)theLibModule添加進(jìn)去.

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            //開始啟動(dòng)koin
            androidContext(this@MyApp)//這邊傳Application對(duì)象,這樣你注入的類中,需要app對(duì)象的時(shí)候,可以直接使用
            modules(appModule,libModule.theLibModule)//這里面?zhèn)鞲鞣N被注入的模塊對(duì)象,支持多模塊注入,在2.0.1之后才支持vararg調(diào)用,之前可以使用集合來調(diào)用
        }
    }

大家可以看到,我添加了一個(gè)libModule.theLibModule對(duì)象,好,這樣之后,我們就可以使用libModule.theLibModule下被注入的對(duì)象了,使用方式跟上面的一樣.

然后我的app模塊中,有一個(gè)ModuleData類,該構(gòu)造函數(shù)需要傳一個(gè)LibData對(duì)象

class ModuleData(val libData:LibData) {
}

然后將ModuleData注入

val appModule = module {
        //里面添加各種注入對(duì)象
        ...        
        factory { 
            ModuleData(get())
        }
    }

其他都一樣,然后我創(chuàng)建了一個(gè)LibModuleActivity界面,在里面獲取LibData和ModuleData對(duì)象,并且打印出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

是不是符合我們的預(yù)想

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

什么是Scope作用域,這個(gè)東西其實(shí)跟viewModel有點(diǎn)相似,scope下的對(duì)象可以跟一個(gè)視圖綁定起來,并且該被綁定的對(duì)象是單例的模式,其他界面通過scopeId可以獲取這個(gè)對(duì)象.當(dāng)該視圖被銷毀的時(shí)候,被綁定的對(duì)象也會(huì)被銷毀.其他界面也就獲取不到這個(gè)scope對(duì)象了.

class ScopeData {
}

現(xiàn)在我有一個(gè)ScopeData這個(gè)類.然后在appModule中,用scope方式注入.scope注入方式有2種,先講第一種

val appModule = module {
        //里面添加各種注入對(duì)象
        ...        
        scope(named("myScope")) {//scope類型的注入方式一,通過標(biāo)簽的方式
            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"))//創(chuàng)建scope方式一
        bindScope(scope)//scope與界面綁定,只有這邊創(chuàng)建綁定了之后,其他地方才能獲取到這個(gè)作用域
        val scopeData = scope.get<ScopeData>()//獲取作用域下的類
       CSKoinLog.I("ScopeCurentActivity中的ScopeData是否為空:" + (scopeData == null))
        CSKoinLog.I("ScopeCurentActivity中的ScopeData地址:" + (scopeData.hashCode()))
    }
}

大家可以看到,通過標(biāo)簽,我創(chuàng)建了一個(gè)id為"scopeId1"的作用域,該作用域的標(biāo)簽為myScope,在application中該標(biāo)簽注入的對(duì)象為ScopeData.接著,就可以通過get方式來獲取這個(gè)對(duì)象了,注意,這邊必須要先通過createScope創(chuàng)建了作用域之后,然后綁定視圖,這樣你其他的地方才能獲取到這個(gè)作用域.接著就打印這個(gè)對(duì)象的地址.

我在MainActivity中也有個(gè)方法,當(dāng)點(diǎn)擊跳轉(zhuǎn)到ScopeCurentActivity時(shí),會(huì)調(diào)用doScope方法,獲取下作用域,進(jìn)行一個(gè)比較,然后等2秒之后Scope跟ScopeCurentActivity綁定了之后,再獲取下,下面是MainActivity的代碼(注意getScopeOrNull這個(gè)方法)

class MainActivity : AppCompatActivity() {
   ...

 fun doScope() {
        val scopeData = getKoin().getScopeOrNull("scopeId1")////注意這邊獲取一個(gè)可空的方法,直接getScope獲取到的可能為空,會(huì)報(bào)空指針
        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
//這邊點(diǎn)擊跳轉(zhuǎn)之后
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

是不是從日志中,就可以看出結(jié)果.然后上面也說過了,scope的獲取方式有2種,接著是第二種方式,第二種方式我感覺有點(diǎn)雞肋,雖然官網(wǎng)上的demo就是這樣的寫法,大家請(qǐng)往下看.

class ScopeTypeTwo {
}

我這邊有一個(gè)ScopeTypeTwo類,然后我在appModule中,將該類注入一下

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

    }

這種注入方式,可以直接傳一個(gè)Activity的泛型標(biāo)簽,就是在注入的時(shí)候,就已經(jīng)跟對(duì)應(yīng)的視圖綁定起來,這樣的話,你在ScopeCurentActivity中,可以直接通過currentScope.inject()的方式,獲取到該對(duì)象.

class ScopeCurentActivity : AppCompatActivity() {

    val csopeTypeW: ScopeTypeTwo by currentScope.inject()//直接獲取了ScopeTypeTwo對(duì)象

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

不過這種方式,他其實(shí)已經(jīng)幫你創(chuàng)建好了scopeid的值,該scopeid的值就是你當(dāng)前視圖的地址,但是有個(gè)問題,就是其他視圖要通過scope方式獲取對(duì)象的話,必須有一個(gè)scopeid,如果你ScopeCurentActivity沒有運(yùn)行的話,該地址位是為空的,而且scope對(duì)象也沒有,所以我這邊將ScopeCurentActivity的地址位保存在一個(gè)地方.其他視圖要用的時(shí)候,直接去獲取就可以了.

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

接著我在MainActivity中,再去獲取ScopeTypeTwo這個(gè)對(duì)象

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()
    }
}
//點(diǎn)擊跳轉(zhuǎn)按鈕,到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

從日志中,就可以看到我們驗(yàn)證的結(jié)果.

9.fragment使用

在2.1.0-alpha-3版本開始,Koin支持對(duì)fragment的注入了(官網(wǎng)說的,但是我調(diào)試的時(shí)候有問題),但是我調(diào)試的時(shí)候,發(fā)現(xiàn)fragment相關(guān)的api在當(dāng)前最新的版本及之前的版本中都有問題,api沒有相關(guān)的內(nèi)容,估計(jì)作者還在調(diào)試中,但是官網(wǎng)上已經(jīng)給出了fragment的使用方法,之后的版本應(yīng)該會(huì)修復(fù)好.接下來我們來看看怎么使用.(無驗(yàn)證下面的調(diào)用,但是可以通過作用域的方式來調(diào)用,我demo中調(diào)試過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)
    }
}

我創(chuàng)建了一個(gè)MyFragment這樣的碎片.該構(gòu)造方法中有一個(gè)字符串的參數(shù),我們?cè)赼pplication中,需要將該對(duì)象注入.

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            //開始啟動(dòng)koin
           ...            
//            fragmentFactory() 暫時(shí)2.1.0-alpha-3這個(gè)版本之前有問題,引用不到,估計(jì)作者還在調(diào)試,文檔上的用法就是這樣的
            modules(appModule, libModule.theLibModule)//這里面?zhèn)鞲鞣N被注入的模塊對(duì)象,支持多模塊注入,在2.0.1之后才支持vararg調(diào)用
        }
    }

 val appModule = module {
        //里面添加各種注入對(duì)象
        ...

//        fragment { MyFragment("張三") }//暫時(shí)2.1.0-alpha-3這個(gè)版本之前有問題,引用不到,估計(jì)作者還在調(diào)試,文檔上的用法就是這樣的
    }
}

首先在startKoin中,我們需要傳入fragmentFactory()這個(gè)工廠類,然后在appModule中,通過fragment注入類型來注入我們的MyFragment.
好接下來就是在界面中調(diào)用我們的碎片了

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

就是這么簡單調(diào)用.目前fragment的注入方式有問題,我無法驗(yàn)證.官網(wǎng)還有一種碎片跟作用域結(jié)合的用法,我這邊也一并給大家寫出來,也不驗(yàn)證了.

val appModule = module {
        //里面添加各種注入對(duì)象
 scope(named<FragActivity>()){
            scoped {
               MyFragment("張三")
            }
       }
}
class FragActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
//        setupKoinFragmentFactory()//要在調(diào)用父類的方法之前調(diào)用
//        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)方法綁定界面,其實(shí)這種做法直接用我上面講到的Scope的方式去獲取碎片就可以用了(大家都是成熟的程序員,要自己去舉一反三了)

10.1.Properties--調(diào)用資產(chǎn)中的數(shù)值

Koin還能直接調(diào)用資產(chǎn)文件中的內(nèi)容.我在assets資產(chǎn)文件夾下,創(chuàng)建了一個(gè)koin.properties(默認(rèn)的名字,你也可以自己命名),里面就一個(gè)鍵值對(duì)

userName = "abc123"

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

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            //開始啟動(dòng)koin
           ...
            androidFileProperties()//默認(rèn)名字為koin.properties,你也可以直接重新設(shè)置名稱
            modules(appModule, libModule.theLibModule)//這里面?zhèn)鞲鞣N被注入的模塊對(duì)象,支持多模塊注入,在2.0.1之后才支持vararg調(diào)用
        }
    }
}

調(diào)用一下androidFileProperties()就可以了,接著就是如何使用了.

class ProData(val string: String) {
}

我創(chuàng)建了一個(gè)ProData類,構(gòu)造函數(shù)傳一個(gè)字符串,然后在application中注入

val appModule = module {
        //里面添加各種注入對(duì)象
      ...
       factory {
           ProData(getProperty("userName"))//該方法可以設(shè)置泛型對(duì)象,你已經(jīng)是一個(gè)成熟的程序員了,要學(xué)會(huì)自己舉一反三
        }
}

通過getProperty方法,里面?zhèn)麈I的值,接著再調(diào)用下就可以了.

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

如果你的項(xiàng)目是用組件化開發(fā)的丐膝,那這個(gè)方式很適合量愧。

class TimeData(val ProD:ProData) {
}

我這邊有一個(gè)TimeData類,該類的構(gòu)造參數(shù)是ProData帅矗,上面文章中偎肃,我將ProData在appModule中注入了,接下來我再創(chuàng)建一個(gè)注入modul,將TimeData在新的module中注入,然后再去加載這個(gè)module.

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
           ...
            modules(appModule, libModule.theLibModule)//這里面?zhèn)鞲鞣N被注入的模塊對(duì)象,支持多模塊注入,在2.0.1之后才支持vararg調(diào)用
        }
        loadKoinModules(otherModule)
    }

     val otherModule = module {
        factory {
            TimeData(get())
        }

    }

大家請(qǐng)看浑此,我這邊新創(chuàng)了一個(gè)otherModule软棺,然后在里面,將TimeData注入尤勋,可以看到喘落,參數(shù)ProData我傳了一個(gè)get(),因?yàn)镻roData在上面中茵宪,我已經(jīng)注入過了,所以通過get()方式瘦棋,就可以獲取到稀火。然后我在OtherActivity中調(diào)用。

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

從日志中赌朋,我們驗(yàn)證了凰狞。

至此,Koin的使用方法我都已經(jīng)給大家詳解了(其實(shí)還有幾個(gè)小東西,但是在實(shí)際項(xiàng)目中用不到)(覺得寫得還不錯(cuò)的,給個(gè)??)

轉(zhuǎn)載請(qǐng)標(biāo)明出處

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市沛慢,隨后出現(xiàn)的幾起案子赡若,更是在濱河造成了極大的恐慌,老刑警劉巖团甲,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逾冬,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡躺苦,警方通過查閱死者的電腦和手機(jī)身腻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匹厘,“玉大人嘀趟,你說我怎么就攤上這事∮希” “怎么了她按?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長炕柔。 經(jīng)常有香客問我酌泰,道長,這世上最難降的妖魔是什么汗唱? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮丈攒,結(jié)果婚禮上哩罪,老公的妹妹穿的比我還像新娘。我一直安慰自己巡验,他們只是感情好际插,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著显设,像睡著了一般框弛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捕捂,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天瑟枫,我揣著相機(jī)與錄音斗搞,去河邊找鬼。 笑死慷妙,一個(gè)胖子當(dāng)著我的面吹牛僻焚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播膝擂,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼虑啤,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了架馋?” 一聲冷哼從身側(cè)響起狞山,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎叉寂,沒想到半個(gè)月后萍启,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沙峻,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡金刁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了兽愤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孕蝉。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡屡律,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出降淮,到底是詐尸還是另有隱情超埋,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布佳鳖,位于F島的核電站霍殴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏系吩。R本人自食惡果不足惜来庭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望穿挨。 院中可真熱鬧月弛,春花似錦、人聲如沸科盛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贞绵。三九已至厉萝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谴垫。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工章母, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人弹渔。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓胳施,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肢专。 傳聞我的和親對(duì)象是個(gè)殘疾皇子舞肆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345