依赖注入框架——Koin


在这里插入图片描述

一、带着问题去学习

1.1 什么是依赖注入

初学的小伙伴可能会疑惑:唉,什么是依赖注入?
这里做个名词剖析:依赖指的是高层模块需要使用(依赖)到底层模块的功能。在编程语言中可以通过继承,创建底层模块类的实例等方式来具体体现。注入指的是可以将底层模块的实例自动提供给高层模块进行使用。
这么做的目的说白了就是为了降低跟类对象之间的耦合,实现设计模式中的依赖倒置原则(不知道这个设计模式的可以参考我前面写过的博客:设计模式六大原则详解)。即当需要修改类对象的时候,能不修改被依赖的实例。其实我们平常就用到了很多的依赖注入,比如一个类的构造函数有一个参数,你new该类时要传一个参数,这就是一个注入(通过构造函数的方式注入),又比如类中常用的set()方法,这也是一个注入。

1.2 为什么要引入第三方的注入控件

这时就会有同学问了:那既然我们平常都有用到,为什么还要引用第三方的注入控件?
那如果某一天,该类的构造函数中,需要再加一个参数怎么办,少量该类的实例修改比较方便,如果你持有该类的实例有100个呢,那如果一个个去修改地话,就非常不现实。又或者几个月后,该类的构造参数又增加了一个怎么办?所以我们就引用了依赖注入控件。

1.3 什么是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!
Kotlin开发人员实用的轻量级依赖注入框架。纯Kotlin编写,仅使用函数解析:无代理,无代码生成,无反射!
简单来说:Koin是帮助我们更好的管理依赖注入的框架,能够实现自动化依赖注入

二、如何使用Koin

Koin 进行依赖注入的基本流程:

  1. 添加 Koin 依赖:在项目的 build.gradle 文件中添加 Koin 的依赖。这通常包括 Koin 的核心库和可能需要的扩展库,如 Android 相关的库。
  2. 定义 Koin 模块
  • 创建一个或多个 Koin 模块(Module)用于配置依赖注入。这些模块通常定义在一个单独的文件中,并使用 Koin 的 DSL 语法来定义需要注入的类和它们之间的关系。
  • 在模块中,可以使用 single、factory、viewModel 等关键字来定义如何注入不同类型的对象。例如,single 用于注入单例对象,factory 用于注入每次请求时都会创建新实例的对象,viewModel 用于注入与 Android ViewModel 生命周期绑定的对象。
  1. 初始化 Koin
  • 在应用程序的入口点(如 Android 的 Application 类或在 JVM 应用的 main 函数中)初始化 Koin。
  • 调用 startKoin 方法,并传入一个或多个 Koin 模块。在这个方法中,还可以进行其他配置,如开启日志记录、绑定 Android 上下文等。
  1. 注入依赖
  • 在需要依赖注入的地方,使用 Koin 提供的扩展函数或 API 来获取依赖对象。
  • 对于 Android 应用,可以使用 by viewModel() 或 by inject() 等扩展函数在 Activity、Fragment 或 ViewModel 中注入依赖。
  • 对于非 Android 应用,可以使用 Koin.get() 方法来获取依赖对象。
  1. 使用依赖
  • 在获取到依赖对象后,就可以像使用普通对象一样使用它们了。
  • 注意在适当的时候停止 Koin(如在 Android 应用的 onDestroy 方法中),但通常对于简单的应用,这一步可能不是必需的。
  1. (可选)清理 Koin:在应用不再需要 Koin 时,可以调用 stopKoin() 方法来清理 Koin 的资源。但在大多数应用中,这一步可能不是必需的,因为 Koin 会自动管理其生命周期。

2.1 添加 Koin 依赖

在你的 app/build.gradle 文件中,添加 KoinAndroid 相关依赖

dependencies {
    // Koin AndroidX Scope features  
    implementation "io.insert-koin:koin-androidx-scope:X.Y.Z" // 替换 X.Y.Z 为最新版本  
      
    // Koin AndroidX ViewModel features  
    implementation "io.insert-koin:koin-androidx-viewmodel:X.Y.Z" // 替换 X.Y.Z 为最新版本  
      
    // Koin AndroidX Ext  
    implementation "io.insert-koin:koin-androidx-ext:X.Y.Z" // 替换 X.Y.Z 为最新版本  
  
    // Koin core features  
    implementation "io.insert-koin:koin-core:X.Y.Z" // 替换 X.Y.Z 为最新版本  
}

2.2 定义Koin模块

在使用 Module 之前,先介绍一下它拥有哪几种方法:

方法作用
factory{ }普通注入,以工厂的方式定义(每次都会构造一个新对象)
single{ }单例注入(全局只有一个对象)
viewModel{ }viewModel注入,这是Koin专门给ViewModel提供的
scope{ }定义作用域
scoped{ }在scope{}里使用
get()自动获取依赖对象
named()定义命名,用于限定符

在 Module 文件里可以编写需要注入的对象。

创建一个名为 AppModule.kt 的文件,用于定义Koin 模块

// AppModule.kt  
package com.example.myapp.di  
  
import com.example.myapp.repository.GreetingRepository  
import com.example.myapp.service.GreetingService  
import org.koin.dsl.module.module  
  
val appModule = module {  
    single { GreetingRepository() }  
    single { GreetingService(get()) }  
}

2.3 初始化Koin

在你的 Android Application 类的 onCreate 方法中初始化 Koin:

// MyApplication.kt  
package com.example.myapp  
  
import android.app.Application  
import com.example.myapp.di.appModule  
import org.koin.android.ext.koin.androidContext  
import org.koin.android.ext.koin.androidLogger  
import org.koin.core.context.startKoin  
  
class MyApplication : Application() {  
    override fun onCreate() {  
        super.onCreate()  
  
        // 初始化 Koin  
        startKoin {  
            androidLogger() // (可选)启用 Koin 日志  
            androidContext(this@MyApplication) // 绑定 Android 上下文  
            modules(listOf(appModule)) // 加载定义的模块  
        }  
    }  
}

2.4 注入和使用依赖

2.4.1 在 ViewModel 中注入依赖

(可选)如果你在使用 ViewModel,可以这样注入依赖:

// GreetingViewModel.kt  
package com.example.myapp.viewmodel  
  
import androidx.lifecycle.ViewModel  
import androidx.lifecycle.ViewModelProvider  
import com.example.myapp.service.GreetingService  
import org.koin.androidx.viewmodel.ext.android.viewModel  
  
class GreetingViewModel(private val greetingService: GreetingService) : ViewModel() {  
    // ... ViewModel 的其他代码 ...  
}  
  
// 创建 ViewModel 的工厂  
class GreetingViewModelFactory(private val greetingService: GreetingService) : ViewModelProvider.Factory {  
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {  
        return GreetingViewModel(greetingService) as T  
    }  
}  
  
// 在你的 Activity 或 Fragment 中获取 ViewModel  
val viewModel: GreetingViewModel by viewModels {  
    // 提供一个 lambda 来获取 GreetingService 的实例  
    GreetingViewModelFactory(get())  
}

注意:如果你使用 Koin 的 viewModel() 函数,你不需要自己实现 ViewModelFactory。你可以直接在 ViewModel 中使用 by viewModels() 注入 ViewModel。

2.4.2 在 Activity 或 Fragment 中注入和使用依赖
  1. 在 Activity 中注入和使用依赖
// MainActivity.kt  
package com.example.myapp.ui.main  
  
import android.os.Bundle  
import androidx.appcompat.app.AppCompatActivity  
import com.example.myapp.service.GreetingService  
import org.koin.androidx.scope.lifecycleScope  
import org.koin.androidx.viewmodel.ext.android.viewModel  
import org.koin.core.KoinComponent  
import org.koin.core.inject  
  
class MainActivity : AppCompatActivity(), KoinComponent {  
  
    private val greetingService: GreetingService by inject() // 注入 GreetingService  
  
    override fun onCreate(savedInstanceState: Bundle?) {  
        super.onCreate(savedInstanceState)  
        setContentView(R.layout.activity_main)  
  
        // 使用 greetingService  
        greetingService.greet()  
    }  
}

请注意,如果你使用了 KoinComponent 接口,则可以在任何类中直接使用 by inject() 来注入依赖,而无需在 startKoin 调用中指定该类的实例。但是,对于 Activity 和 Fragment,更常见的做法是使用 lifecycleScope(在 Fragment 中)来确保依赖项的生命周期与组件的生命周期保持一致。
2. 在 Fragment 中注入和使用依赖

// MyFragment.kt  
package com.example.myapp.ui.fragment  
  
import android.os.Bundle  
import androidx.fragment.app.Fragment  
import androidx.fragment.app.viewModels  
import androidx.lifecycle.ViewModelProvider  
import com.example.myapp.service.GreetingService  
import com.example.myapp.viewmodel.GreetingViewModel  
import org.koin.androidx.scope.lifecycleScope  
import org.koin.androidx.viewmodel.ext.android.viewModel  
import org.koin.core.KoinComponent  
import org.koin.core.inject  
  
class MyFragment : Fragment(), KoinComponent {  
  
    private val greetingService: GreetingService by inject() // 注入 GreetingService  
  
    // 或者使用 ViewModel  
    private val viewModel: GreetingViewModel by viewModels()  
  
    override fun onCreateView(  
        inflater: LayoutInflater,  
        container: ViewGroup?,  
        savedInstanceState: Bundle?  
    ): View? {  
        // ...  
  
        // 使用 greetingService 或 viewModel  
        greetingService.greet()  
  
        // ...  
  
        return view  
    }  
  
    // 如果你在 Fragment 中使用自定义的 ViewModelFactory  
    // 你可能需要像这样提供它  
    override fun getViewModelStore(): ViewModelStore {  
        return ViewModelStore(lifecycleScope.lifecycleOwner)  
    }  
  
    override fun onViewModelStoreCleared() {  
        super.onViewModelStoreCleared()  
        // 清理资源(如果需要)  
    }  
}

在 Fragment 中,通常还会使用 viewModels() 函数来注入 ViewModel,因为它会自动处理 ViewModel 的生命周期。但是,如果你需要自定义 ViewModel 的创建过程(例如,使用自定义的 ViewModelFactory),你可能需要重写 getViewModelStore() 和 onViewModelStoreCleared() 方法。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐