今年呆在家中實(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)上也寫的很清楚获讳。
如果你是支持AndroidX的,那就使用
這邊對(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
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())
}
}
通過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()
}
}
從打印的日志中,我們可以看到,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())
}
}
}
在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))
}
}
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)明出處