关键函数符号
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是新建对象 的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())
}
}
//输出日志 三个不同的对象
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限定符,你可以理解为标签,就是给每一个注入的构造函数贴个标签,用的时候,通过这个标签获取对应的构造函数对象.
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
如下:
//获取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视图的注入 哈哈
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模块中注入的对象.
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作用域,这个东西其实跟viewModel有点相似。
scope下的对象可以跟一个视图绑定起来,并且该被绑定的对象是单例的模式
其他界面通过scopeId可以获取这个对象.当该视图被销毁的时候,被绑定的对象也会被销毁.其他界面也就获取不到这个scope对象了.
我们可以在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}")
}
}
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