今年呆在家中实在无聊,外面太危险了,还是在家学习比较安全可持续。
过年期间,我又复习了几遍依赖注入控件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这个库,官网上也写的很清楚。
如果你是支持AndroidX的,那就使用
这边对这些地址大致介绍下,里面有个坑
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
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()//方法二
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_factory)
val person3 = get()//方法三
person.speak()
person2.speak()
person3.speak()
CSKoinLog.I(person.hashCode().toString())
CSKoinLog.I(person2.hashCode().toString())
CSKoinLog.I(person3.hashCode().toString())
}
}
通过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()
}
}
从打印的日志中,我们可以看到,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()
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
在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()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_normal)
CSKoinLog.I("application是否为空:" + (appData.mApp == null))
}
}
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 factory(
qualifier: Qualifier? = null,
override: Boolean = false,
noinline definition: Definition
): BeanDefinition {
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 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())
}
factory(named("appData")){
//该限定符定义构造方法中有AppData的
NormalData(get())
}
}
注入方式就是这样,通过named方法添加标签.其中,有2个构造函数需要各传一个参数,其中一个是application对象,另一个构造函数需要传AppData这个对象,这个对象在上面我们已经是有注入过的.(如果没注入过怎么办,我们下面会说).所以可以通过get()方式获取,但是获取哪一个呢,在get()中,我们有个泛型,通过泛型,就能够判断需要传哪一个(get这个方法也有个限定符named功能,后续会讲到).好了,注入完成之后,我们来看一下在NormalActivity中如何调用并且结果咋么样.
class NormalActivity : AppCompatActivity() {
val appData by inject()
////
val norData1 by inject(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(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())
}
factory(named("appData")) {
//该限定符定义构造方法中有AppData的
NormalData(get())
}
factory(named("wea_name")) {
WeatherData(get(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(named("wea_name"))
val weatherData2 by inject(named("wea_app"))
val weatherData3 by inject(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 { 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()//懒加载模式
val appD2 = get()//非懒加载模式
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()
val moduleData by inject()
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()//获取作用域下的类
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()
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()){
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()
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()){
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的使用方法我都已经给大家详解了(其实还有几个小东西,但是在实际项目中用不到)(觉得写得还不错的,给个)
转载请标明出处