Koin 框架

关键函数符号

startKoin

startKoin 函数是启动koin框架的入口,传入一个定义在KoinApplication上的一个扩展lamda类型KoinAppDeclaration来配置koin。

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

 //开始启动koin   
 startKoin {
            androidLogger(Level.ERROR)//目前已知bug,除了level.error外,使用androidlogger会导致崩溃
             //context
            androidContext(this@MyApp)//这边传Application对象,这样你注入的类中,需要app对象的时候,可以直接使用
           //assets 资源数据
              androidFileProperties()//默认名字为koin.properties,你也可以直接重新设置名称
              androidFileProperties("ass.file")//默认取值assets下koin.properties文件内的属性配置,可自定义


  //加载需要的module           
 modules(appModule, libModule.theLibModule)//这里面传各种被注入的模块对象,支持多模块注入,在2.0.1之后才支持vararg调用
        }
       //外部加载 module
        loadKoinModules(otherModule)

        

module

创建Koin module,使用该module函数声明模块

module 是一个顶层函数,接受一个定义在Module上的扩展lamda类型ModuleDeclaration来配置这个module

Factory注入注入方式

factory是新建对象 的scope.工厂对象,每次依赖产生一个新对象

factory每次Activity需要一个实例时都会创建一个新实例。

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

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

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

factory 相当于工厂模式,每次注入一个新的对象

  val appmodule = module  {//声明一个module
   
    single {//单例对象,多次依赖只产生一个对象
        Constructor()
    }
    factory {//工厂对象,每次依赖产生一个新对象
        Constructor()
    }
      factory {//普通的注入方式
            Person()
        }

    //viewModel
    viewModel { DashboardViewModel() }
    viewModel { HomeViewModel() }


}
 
class Person {
    fun speak() {
        CSKoinLog.I("武汉加油,中国加油")
    }
}
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())
    }
}

 //输出日志  三个不同的对象

Koin 框架_第1张图片

single单例模式  注入方式

single 区别在于其提供的实例是单例的。单例对象,多次依赖只产生一个对象

single 是Module的一个成员方法,它接受一个定义在Scope上的扩展lamda类型Definition来配置在Scope(这里是rootScope)中生成一个具体的对象。single方法定义的对象在该scope中会缓存起来,多个地方依赖同一个对象

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

}
class SingleActivity: BaseMvvMActivity() {


    val userData1: UserData by inject()
    val userData2: UserData by inject()

    override fun initViewBinding(): ActivityFactoryBinding {
      return   ActivityFactoryBinding.inflate(layoutInflater)
    }

    override fun initData(savedInstanceState: Bundle?) {
      userData1.userName="方"
        userData1.age =11
        userData1.info()
        userData2.userName="明"
        userData2.age =12
        userData2.info()
        val userData3 = get()//方法三
        userData3.userName="飞"
        userData3.age =13
        userData3.info()
        //创建3个不同的对象
        LogUtil.e("koin",userData1.hashCode().toString())
        LogUtil.e("koin",userData2.hashCode().toString())
        LogUtil.e("koin",userData3.hashCode().toString())

    }



}

  //输出的日志 只有一个对象

 用户名:方年龄:11
用户名:明年龄:12
 用户名:飞年龄:13
XJX-koin: │ 118120586
XJX-koin: │ 118120586
XJX-koin: │ 118120586


 viewModel注入方式

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

创建了一个KoinViewModel类,该类继承lifecycle的ViewModel

class KoinViewModel:BaseViewModel() {
     var     numData:Int=0
      var   koinLiveData = MutableLiveData()

}

这2个viewModel都是同一个,然后我修改了里面的值,接着我将手机屏幕变成横屏(这个看实际操作),再变成竖屏,Activity重新调用生命周期,但是viewModel仍旧是那个viewModel(地址不变),接着退出该界面,发现viewModel中的clear回调被调用了.

class KoinViewModelActivity: BaseMvvMActivity() {

    val koinViewModel1 :KoinViewModel by viewModel() //注意引用否则报错
    val koinViewModel2   by viewModel()



    override fun initViewBinding(): ActivityViewModelKoinBinding {
      return   ActivityViewModelKoinBinding.inflate(layoutInflater)
    }
    var flag = 1
    override fun initData(savedInstanceState: Bundle?) {


         mBinding.btnChange.onClick {
             koinViewModel1.numData=flag++
            LogUtil.e("koin","koinViewModel1的数值=${koinViewModel1.numData},  koinViewModel2的数值=${koinViewModel2.numData}")

              val userData  =  UserData()
             userData.userName="方明飞"
             userData.age  = 34
             koinViewModel1.koinLiveData.postValue(userData)

             koinViewModel1.koinLiveData.observe(this@KoinViewModelActivity, Observer {
                 mBinding.tvKoin.text ="${it.userName}, ${it.age}"  //方明飞,34
              })

         }

        LogUtil.e("koin",koinViewModel1.hashCode().toString())
        LogUtil.e("koin",koinViewModel2.hashCode().toString())


    }



}

  //输出日志   koinViewModel1 koinViewModel2  都指向同一个对象 KoinViewModel

XJX-koin: │ 101259763
XJX-koin: │ 101259763

qualifier限定符

qualifier限定符用来标记不同的构造方法

什么是qualifier限定符,你可以理解为标签,就是给每一个注入的构造函数贴个标签,用的时候,通过这个标签获取对应的构造函数对象.

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

1.AppData类的构造函数有一个Application属性,那么该如何去注入这个类

  给AppData类传入字段属性数据

 第一个参数:get() 表示获取application对象 context

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

第二个参数:字符串 "你好" 
//里面添加各种注入对象
val  appModule:Module = module {
   factory {
        //普通的注入方式 无参注入
        Person()
    }

 factory {
     AppData(get(),"你好")
 }




}
class AppData(var mApp:Application,var str:String = "你好")

2.  NormalData类,里面是有三个构造函数,而且需要的参数都不同,如果我们单独地注入方式已经无法满足我们需求了,无法区分注入的到底是哪个构造函数,所以我们要使用qualifier限定符(标签),给注入的每一个构造函数贴个标签,通过获取qualifier限定符(标签),来区分每一个注入的不同的构造函数。

2.1这时我们就要给factory注入函数的第一个参数qualifier限定符 (默认为null)传入不同的限定符数值,以示区别

(single注入函数  viewModel注入函数的第一个参数(默认为null) 也可以传入不同的qualifier限定符参数,以示区别 )

inline fun  factory( qualifier: Qualifier? = null,override: Boolean = false,noinline definition: Definition

inline fun single(qualifier: Qualifier? = null, createdAtStart: Boolean = false,override: Boolean = false,noinline definition: Definition)

inline fun Module.viewModel(qualifier: Qualifier? = null, override: Boolean = false,noinline definition: Definition)

2.2 我们也不能傻乎乎的直接给factory(qualifier: Qualifier)传入qualifier限定符参数,factory("haha1"),factory("haha2"),这样肯定是错误的,

 所以我们要研究研究qualifier: Qualifier的源码,是应该怎么传qualifier限定符参数呢?

通过分析Qualifier的源码我们知道,Qualifier 是一个接口,通过named(name: String)方法来标记传入不同的限定符参数,factory(named("haha1")),factory(named("haha2")),factory(named("haha3"))

interface Qualifier {val value: QualifierValue}

fun named(name: String) = StringQualifier(name)

inline fun named() = TypeQualifier(T::class)

2.3  示例代码

      

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

  

val  appModule:Module = module {

  factory {
     AppData(get(),"你好")
 }


factory(named("userNameAge")) {
    //该限定符的构造方法中包含字符串和数字
    NormalData("方明飞",34)
}

 factory(named("application")){
     //该限定符定义构造方法中有appliaction的
     NormalData(get())
 }


 factory (named("appData")){
     NormalData(get())
 }



}

class QualifierActivity: BaseMvvMActivity() {
   //注入 AppData
   val  appData by inject()

  override fun initViewBinding(): ActivityQualifierBinding {
      return   ActivityQualifierBinding.inflate(layoutInflater)
    }

    override fun initData(savedInstanceState: Bundle?) {
    LogUtil.e("AppData","${appData.mApp} "+" ${appData.str}") // com.ihawkeye.cn.App@6b28bd5  你好

        val normalData1 by inject(named("userNameAge"))
        normalData1.printInfo("normalData1") //normalData1的信息    age:34 userName:方明飞 application是否为空:true appData是否为空:true


        val normalData2:NormalData by inject(named("application"))
        normalData2.printInfo("normalData2") // normalData2的信息    age:0 userName: application是否为空:false appData是否为空:true


        val norData3 = get(named("appData"))
        norData3.printInfo("norData3") //norData3的信息    age:0 userName: application是否为空:true appData是否为空:false
    }



}

2.4 get()什么鬼?

  通过以上我们发现 给NormalData类的不同构造函数传递参数时,会传入get(),怎么去理解为什么要传入get()到构造函数里去呢?

我是这么理解的 如果你的NormalData类的不同的构造函数要传入的参数,如果这个参数(可能是个字符串,一个实体类,上下文)在之前已经被我们注入过了,那么此koin框架就会有记录,那么我们就可以直接通过get()来表示此已经注入的参数对象,

AppData(var mApp:Application,var str:String )

factory {AppData(get(),"你好")}

NormalData(appData: AppData)

factory (named("appData")){NormalData(get())}

就是 NormalData(appData: AppData)的 构造函数的参数AppData 之前已经被我们给注入过了,所以 这里直接通过get() 获取AppData 注入对象

 
  

2.5  如果我们要使用多个get()方法 ,就需要使用Qualifier限定符(标签)来区分,来获取对应的注入对象。

  额 ,显然我们知道,NormalData类的各个不同构造函数对象,已经被注入过了,可以直接通过get()来获取不同的注入对象,

  但是 有几个不同的NormalData的构造对象注入对象,那怎么区分获取对应的不同的NormalData构造对象呢? 给get() 加Qualifier限定符(标签)就可以获取各个不同的构造注入NormalData对象 

 如下: 
//获取NormalData类 的  constructor(userName: String, age: Int)构造函数对象
val normalData12:NormalData  =get(named("userNameAge"))
//获取NormalData类 的  constructor(mApp: Application)构造函数对象
val normalData22:NormalData  =get(named("application"))

//获取NormalData类 的constructor(appData: AppData)构造函数对象
val norData32:NormalData  = get(named("appData"))

  示例demo:

class WeatherData(val normalData: NormalData) {
    fun printNormalData(string: String) {
        normalData.printInfo(string)
    }
}
//里面添加各种注入对象
val  appModule:Module = module {

    single {//单例的注入方式
        UserData()
    }
    viewModel { //viewModel注入方式      //注意引用否则报错
        KoinViewModel()
    }

    factory {
        //普通的注入方式 无参注入
        Person()
    }

 factory {
     val application:Application  = get()
     AppData(application,"你好")
 }


factory(named("userNameAge")) {
    //该限定符的构造方法中包含字符串和数字
    NormalData("方明飞",34)
}

 factory(named("application")){
     //该限定符定义构造方法中有appliaction的
     val application:Application  =  get()
     NormalData(application)
 }

 
 factory (named("appData")){
    val appData: AppData=get()
     NormalData(appData)
 }



 factory(named("weather_name")){
     //获取NormalData类 的  constructor(userName: String, age: Int)构造函数对象
     val normalData_userNameAge:NormalData = get(named("userNameAge"))
         WeatherData(normalData_userNameAge)
 }


  factory(named("weather_application")){
      //获取NormalData类 的  constructor(mApp: Application)构造函数对象
      val normalData_application:NormalData =  get(named("application"))
      WeatherData(normalData_application)
  }

    factory(named("weather_appData")){
        //获取NormalData类 的constructor(appData: AppData)构造函数对象
        val normalData_appData:NormalData =  get(named("appData"))
        WeatherData(normalData_appData)
   }


}

class QualifierActivity: BaseMvvMActivity() {
   //注入 AppData
   val  appData by inject()

  override fun initViewBinding(): ActivityQualifierBinding {
      return   ActivityQualifierBinding.inflate(layoutInflater)
    }

    override fun initData(savedInstanceState: Bundle?) {
    LogUtil.e("AppData","${appData.mApp} "+" ${appData.str}") // com.ihawkeye.cn.App@6b28bd5  你好

        val normalData11 by inject(named("userNameAge"))
        normalData11.printInfo("normalData11") //normalData11的信息    age:34 userName:方明飞 application是否为空:true appData是否为空:true
        val normalData12:NormalData  =get(named("userNameAge"))
        normalData12.printInfo("normalData12") //normalData12的信息    age:34 userName:方明飞 application是否为空:true appData是否为空:true

        val normalData21:NormalData by inject(named("application"))
        normalData21.printInfo("normalData21") // normalData21的信息    age:0 userName: application是否为空:false appData是否为空:true

        val normalData22:NormalData  =get(named("application"))
        normalData22.printInfo("normalData22") //normalData22的信息    age:0 userName: application是否为空:false appData是否为空:true


       val norData31:NormalData  by inject(named("appData"))
        norData31.printInfo("norData31") //norData31的信息    age:0 userName: application是否为空:true appData是否为空:false

        val norData32:NormalData  = get(named("appData"))
        norData32.printInfo("norData32") //norData32的信息    age:0 userName: application是否为空:true appData是否为空:false


       //========================================================
       val   weatherData1 :WeatherData by inject(named("weather_name"))
        weatherData1.printNormalData("weather1") //weather1的信息    age:34 userName:方明飞 application是否为空:true appData是否为空:true


       val weatherData2:WeatherData by inject(named("weather_application"))
        weatherData2.printNormalData("weatherData2") // weatherData2的信息    age:0 userName: application是否为空:false appData是否为空:true


         val weatherData3:WeatherData by inject(named("weather_appData"))
        weatherData3.printNormalData("weatherData3") //weatherData3的信息    age:0 userName: application是否为空:true appData是否为空:false

    }



}

 

如何给自定义view进行注入,和给自定义view传递外来参数

如果构造函数的参数是个自定义view,我们无法通过注入获取,只能通过外部方式来传入自定义view参数来进行获取

 通过parametersOf()函数来传实参自定义view

  案例:

class ViewData(var view: View, val str:String) {

   fun printId(){
         LogUtil.e( "${(view as Button).text}   $str")
   }
}
factory {
       //外来传入的形参  
       (view: View,str:String)-> ViewData(view,str)  
   }

 //通过parametersOf()函数传入实参自定义view
       val viewData by inject { parametersOf(mBinding.btnShow,"哈哈")}
        viewData.printId() //View视图的注入    哈哈

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

class CompontData : KoinComponent {
    val appD1:AppData by inject()//懒加载模式
    val appD2:AppData = get()//非懒加载模式
    fun priInfo(str1:String ,str2:String) {
        CSKoinLog.I("CompontData中appD1:" + str1+ " appD2:" + str2)
    }
}

         CompontData().priInfo(CompontData().appD1.str,CompontData().appD2.str) // CompontData中appD1:你好 appD2:你好 

    跨Module模块注入依赖

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

      1.创建一个baselibrary模块,该模块创建一个LibData类,把LibData类进行注入,在主项目模块获取该模块里的LibData注入对象

   

class LibData {
  fun   printLibData(){
     LogUtil.e("输出base模块下注入对象LibData的数据信息: $ 哈哈")
 }
}

2.在主项目的App里,对baselibrary模块的LibData类对象进行注入。

 startKoin {
            androidLogger(Level.ERROR)
            androidContext(this@App)
            //将声明的组件注册到koin
            //调用其他模块libModule.theLibModule下被注入的对象
            modules(arrayListOf(demoServiceModule,appModule,LibModule.mLibModule)) //这里面传各种被注入的模块对象,支持多模块注入,
        }

3.在baselibrary模块的创建LibModule类,书写注入逻辑

object LibModule {
    val mLibModule = module {
        //koin支持多个module注入
        single { LibData() }
    }
}

4.在主项目LibModuleActivity里获取 baselibrary模块下的注入对象LibData

class LibModuleActivity:BaseMvvMActivity() {
    //获取 baseLib模块下的LibData被注入对象
    val libData: LibData by inject()
    //获取ModuleData注入对象
    val  moduleData : ModuleData by inject()


    override fun initViewBinding(): ActivityFactoryBinding {
        return   ActivityFactoryBinding.inflate(layoutInflater)
    }
    override fun initData(savedInstanceState: Bundle?) {
        libData.printLibData() // 输出base模块下注入对象LibData的数据信息: $ 哈哈
        moduleData.libData.printLibData()  // 输出base模块下注入对象LibData的数据信息: $ 哈哈

    }


}

Scope—作用域的使用(略)

Scope作用域,这个东西其实跟viewModel有点相似。

scope下的对象可以跟一个视图绑定起来,并且该被绑定的对象是单例的模式

其他界面通过scopeId可以获取这个对象.当该视图被销毁的时候,被绑定的对象也会被销毁.其他界面也就获取不到这个scope对象了.

Properties–调用资产中的数值

我们可以在assets资产文件夹下,创建一个资产文件koin.properties,对此文件进行注入,获取里面的数据

1. koin.properties文件
  userName = "方明飞哈"
 passWord="654321

 2.

val  appModule:Module = module {
factory {
   var userName:String  = getProperty("userName")
  var passWord:String   = getProperty("passWord" )
      PropertyData(userName,passWord)
}

}

3.

 class PropertyActivity : BaseMvvMActivity() {

     val  propertyData:PropertyData  by inject()
  
    override fun initViewBinding(): ActivityPropertyBinding {
         return  ActivityPropertyBinding.inflate(layoutInflater)
    }
    override fun initData(savedInstanceState: Bundle?) {
        //输出注入对象PropertyData的数据: 方明飞  654321
       LogUtil.e("输出注入对象PropertyData的数据: ${propertyData.userName }  ${propertyData.password}")
   }


}

在startkoin之外,加载module

 1.使用loadKoinModules()注入



startKoin {
    androidLogger(Level.ERROR)
    androidContext(this@App)
    androidFileProperties("koin.properties")//默认名字为koin.properties,你也可以直接重新设置名称
    //将声明的组件注册到koin
    //调用其他模块libModule.theLibModule下被注入的对象
    modules(arrayListOf(demoServiceModule,appModule,LibModule.mLibModule)) //这里面传各种被注入的模块对象,支持多模块注入,
    modules(myModule)
    loadKoinModules(otherModule)
}

2.

val   otherModule:Module  =  module{
    factory {
        val propertyData:PropertyData =get()
          OtherData(propertyData)
    }
}

3.

class OtherData(val propertyData: PropertyData) {
}

4.

class PropertyActivity : BaseMvvMActivity() {

     val  propertyData:PropertyData  by inject()
     val otherData: OtherData by inject()




    override fun initViewBinding(): ActivityPropertyBinding {
         return  ActivityPropertyBinding.inflate(layoutInflater)
    }
    override fun initData(savedInstanceState: Bundle?) {
        //输出注入对象PropertyData的数据: 方明飞  654321
       LogUtil.e("输出注入对象PropertyData的数据: ${propertyData.userName }  ${propertyData.password}")
        // 输出OtherData类的参数PropertyData被注入对象数据信息:   方明飞  654321
        LogUtil.e("输出OtherData类的参数PropertyData被注入对象数据信息:  ${otherData.propertyData.userName}  ${otherData.propertyData.password}")

    }


}

  bind的含义和使用方法

为给定的bean定义添加要绑定的类型

  bind 方法  可以让 BindServiceImpl  和 BindService  绑定
 这样你inject  BindServiceImpl 对象   也可以 inject   BindService  对象

 

class BindServiceImpl(private val str:String) :BindService {

    override fun login() {
        LogUtil.e("koin","$str  is login ")
    }

    override fun unLogin() {
       LogUtil.e("koin","$str is unLogin ")
    }
}


interface BindService {
    fun login()
    fun unLogin()
}
val bindServiceModule:Module = module {
     bind 方法  可以让 BindServiceImpl  和 BindService  绑定
    //这样你inject  BindServiceImpl 对象   也可以 inject   BindService  对象
     single { (str: String) -> BindServiceImpl(str) } bind BindService::class

}

 

   val bindServiceImpl:BindServiceImpl by inject{parametersOf("方明飞")}
        bindServiceImpl.login() //方明飞  is login
        bindServiceImpl.unLogin() //方明飞 is unLogin

override:

Koin 不允许同一名称同一类型声明多次,当你需要声明多次时,你可以使用override 

当一个对象存在多个相同的定义时,后面的定义是否覆盖前面的定义

鸣谢:

Koin--适用于Kotlin的超好用依赖注入框架,Dagger替代者,Koin史上最详细解说,一篇就够了,妈妈再也不用担心我不会依赖注入了_Shaojihan的博客-CSDN博客_koin

你可能感兴趣的:(koin,kotlin)