使用 koin 作为 Android 注入工具,真香
koin 为 Android 提供了简单易用的 API 接口,让你简单轻松地接入 koin 框架。
[koin 在 Android 中的 gradle 配置]
1.Application 类中 startKoin
从您的类中,您可以使用该函数并注入 Android 上下文,如下所示:
Application startKoin androidContext
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
// Log Koin into Android logger
androidLogger()
// Reference Android context
androidContext(this@MainApplication)
// Load modules
modules(myAppModules)
}
}
}
如果您需要从另一个 Android 类启动 Koin,您可以使用该函数为您的 Android 实例提供如下:startKoin
Context
startKoin {
//inject Android context
androidContext(/* your android context */)
// ...
}
2. 额外配置
从您的 Koin 配置(在块代码中),您还可以配置 Koin 的多个部分。startKoin { }
2.1 Koin Logging for Android
koin 提供了 log 的 Android 实现。
startKoin {
// use Android logger - Level.INFO by default
androidLogger()
// ...
}
2.2 加载属性
您可以在文件中使用 Koin 属性来存储键/值:assets/koin.properties
startKoin {
// ...
// use properties from assets/koin.properties
androidFileProperties()
}
3. Android 中注入对象实例
3.1 为 Android 类做准备
koin 提供了KoinComponents
扩展,Android 组件都具有这种扩展,这些组件包括 Activity
Fragment Service ComponentCallbacks
您可以通过如下方式访问 Kotlin 扩展:
by inject()
- 来自 Koin 容器的延迟计算实例
get()
- 从 Koin 容器中获取实例
我们可以将一个属性声明为惰性注入:
module {
// definition of Presenter
factory { Presenter() }
}
class DetailActivity : AppCompatActivity() {
// Lazy inject Presenter
override val presenter : Presenter by inject()
override fun onCreate(savedInstanceState: Bundle?) {
//...
}
}
或者我们可以直接得到一个实例:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Retrieve a Presenter instance
val presenter : Presenter = get()
}
注意:如果你的类没有扩展,只需添加 KoinComponent 接口,如果你需要或来自另一个类的实例。inject() get()
3.2 Android Context 使用
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
//inject Android context
androidContext(this@MainApplication)
// ...
}
}
}
在你的定义中,下面的函数允许你在 Koin 模块中获取实例,以帮助你简单地编写需要实例的表达式。androidContext() androidApplication() Context Application
val appModule = module {
// create a Presenter instance with injection of R.string.mystring resources from Android
factory {
MyPresenter(androidContext().resources.getString(R.string.mystring))
}
}
4. 用于 Android 的 DSL 构造函数
4.1 DSL 构造函数
Koin 现在提供了一种新的 DSL 关键字,允许您直接面向类构造函数,并避免在 lambda 表达式中键入您的定义。
对于 Android,这意味着以下新的构造函数 DSL 关键字:
viewModelOf()`- 相当于`viewModel { }
fragmentOf()`- 相当于`fragment { }
workerOf()`- 相当于`worker { }
注意:请务必在类名之前使用,以定位类构造函数::
4.2 Android DSL 函数示例
给定一个具有以下组件的 Android 应用程序:
// A simple service
class SimpleServiceImpl() : SimpleService
// a Presenter, using SimpleService and can receive "id" injected param
class FactoryPresenter(val id: String, val service: SimpleService)
// a ViewModel that can receive "id" injected param, use SimpleService and get SavedStateHandle
class SimpleViewModel(val id: String, val service: SimpleService, val handle: SavedStateHandle) : ViewModel()
// a scoped Session, that can received link to the MyActivity (from scope)
class Session(val activity: MyActivity)
// a Worker, using SimpleService and getting Context & WorkerParameters
class SimpleWorker(
private val simpleService: SimpleService,
appContext: Context,
private val params: WorkerParameters
) : CoroutineWorker(appContext, params)
我们可以这样声明它们:
module {
singleOf(::SimpleServiceImpl){ bind<SimpleService>() }
factoryOf(::FactoryPresenter)
viewModelOf(::SimpleViewModel)
scope<MyActivity>(){
scopedOf(::Session)
}
workerOf(::SimpleWorker)
}
5. Android 中的 koin 多模块使用
通过使用 Koin,您可以描述模块中的定义。在本节中,我们将了解如何声明,组织和链接模块。
5.1 koin 多模块
组件不必位于同一模块中。模块是帮助您组织定义的逻辑空间,并且可以依赖于其他定义 模块。定义是惰性的,然后仅在组件请求它时才解析。
让我们举个例子,链接的组件位于单独的模块中:
// ComponentB <- ComponentA
class ComponentA()
class ComponentB(val componentA : ComponentA)
val moduleA = module {
// Singleton ComponentA
single { ComponentA() }
}
val moduleB = module {
// Singleton ComponentB with linked instance ComponentA
single { ComponentB(get()) }
}
我们只需要在启动 Koin 容器时声明已使用模块的列表:
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
// ...
// Load modules
modules(moduleA, moduleB)
}
}
}
5.2 模块包含
类中提供了一个新函数,它允许您通过以有组织和结构化的方式包含其他模块来组合模块includes() Module
新模块有 2 个突出特点:
将大型模块拆分为更小、更集中的模块。
在模块化项目中,它允许您更精细地控制模块可见性(请参阅下面的示例)。
它是如何工作的?让我们采用一些模块,我们将模块包含在:parentModule
// `:feature` module
val childModule1 = module {
/* Other definitions here. */
}
val childModule2 = module {
/* Other definitions here. */
}
val parentModule = module {
includes(childModule1, childModule2)
}
// `:app` module
startKoin { modules(parentModule) }
请注意,我们不需要显式设置所有模块:通过包含,声明的所有模块将自动加载。
parentModule includes childModule1 childModule2 parentModule childModule1 childModule2
信息:模块加载现在经过优化,可以展平所有模块图,并避免重复的模块定义。
最后,您可以包含多个嵌套或重复的模块,Koin 将扁平化所有包含的模块,删除重复项:
// :feature module
val dataModule = module {
/* Other definitions here. */
}
val domainModule = module {
/* Other definitions here. */
}
val featureModule1 = module {
includes(domainModule, dataModule)
}
val featureModule2 = module {
includes(domainModule, dataModule)
}
// :app module
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
// ...
// Load modules
modules(featureModule1, featureModule2)
}
}
}
请注意,所有模块将只包含一次:dataModule domainModule featureModule1 featureModule2
5.3 Android ViewModel 和 Navigation
Gradle 模块引入了一个新的 DSL 关键字,该关键字作为补充,以帮助声明 ViewModel 组件并将其绑定到 Android 组件生命周期。关键字也可用允许您使用其构造函数声明 ViewModel。koin-android viewModel singlefactory viewModelOf
val appModule = module {
// ViewModel for Detail View
viewModel { DetailViewModel(get(), get()) }
// or directly with constructor
viewModelOf(::DetailViewModel)
}
声明的组件必须至少扩展类。您可以指定如何注入类的构造函数 并使用该函数注入依赖项。android.arch.lifecycle.ViewModel get()
注意:关键字有助于声明 ViewModel 的工厂实例。此实例将由内部 ViewModelFactory 处理,并在需要时重新附加 ViewModel 实例。它还将允许注入参数。viewModel viewModelOf
5.4 注入 ViewModel
在 Android 组件中使用 viewModel ,Activity Fragment Service
by viewModel()
- 惰性委托属性,用于将视图模型注入到属性中
getViewModel()
- 直接获取视图模型实例
class DetailActivity : AppCompatActivity() {
// Lazy inject ViewModel
val detailViewModel: DetailViewModel by viewModel()
}
5.5 Activity 共享 ViewModel
一个 ViewModel 实例可以在 Fragment 及其主 Activity 之间共享。
要在使用中注入共享视图模型,请执行以下操作:Fragment
by activityViewModel()
- 惰性委托属性,用于将共享 viewModel 实例注入到属性中
get ActivityViewModel()
- 直接获取共享 viewModel 实例
只需声明一次视图模型:
val weatherAppModule = module {
// WeatherViewModel declaration for Weather View components
viewModel { WeatherViewModel(get(), get()) }
}
注意:viewModel 的限定符将作为 viewModel 的标记处理
并在 Activity 和 Fragment 中重复使用它:
class WeatherActivity : AppCompatActivity() {
/*
* Declare WeatherViewModel with Koin and allow constructor dependency injection
*/
private val weatherViewModel by viewModel<WeatherViewModel>()
}
class WeatherHeaderFragment : Fragment() {
/*
* Declare shared WeatherViewModel with WeatherActivity
*/
private val weatherViewModel by activityViewModel<WeatherViewModel>()
}
class WeatherListFragment : Fragment() {
/*
* Declare shared WeatherViewModel with WeatherActivity
*/
private val weatherViewModel by activityViewModel<WeatherViewModel>()
}
5.6 将参数传递给构造函数
向 viewModel 传入参数,示例代码如下:
模块中
val appModule = module {
// ViewModel for Detail View with id as parameter injection
viewModel { parameters -> DetailViewModel(id = parameters.get(), get(), get()) }
// ViewModel for Detail View with id as parameter injection, resolved from graph
viewModel { DetailViewModel(get(), get(), get()) }
// or Constructor DSL
viewModelOf(::DetailViewModel)
}
依赖注入点传入参数
class DetailActivity : AppCompatActivity() {
val id : String // id of the view
// Lazy inject ViewModel with id parameter
val detailViewModel: DetailViewModel by viewModel{ parametersOf(id)}
}
5.7 SavedStateHandle 注入
添加键入到构造函数的新属性以处理 ViewModel 状态:SavedStateHandle
class MyStateVM(val handle: SavedStateHandle, val myService : MyService) : ViewModel()
在 Koin 模块中,只需使用或参数解析它:get()
viewModel { MyStateVM(get(), get()) }
或使用构造函数 DSL:
viewModelOf(::MyStateVM)
在 Activity Fragment
by viewModel()
- 惰性委托属性,用于将状态视图模型实例注入属性
getViewModel()
- 直接获取状态视图模型实例
class DetailActivity : AppCompatActivity() {
// MyStateVM viewModel injected with SavedStateHandle
val myStateVM: MyStateVM by viewModel()
}
5.8 Navigation 导航图中的 viewModel
您可以将 ViewModel 实例的范围限定为导航图。只需要传入 ID 给by koinNavGraphViewModel()
class NavFragment : Fragment() {
val mainViewModel: NavViewModel by koinNavGraphViewModel(R.id.my_graph)
}
5.9 viewModel 通用 API
Koin 提供了一些“底层”API 来直接调整您的 ViewModel 实例。viewModelForClass ComponentActivity Fragment
ComponentActivity.viewModelForClass(
clazz: KClass<T>,
qualifier: Qualifier? = null,
owner: ViewModelStoreOwner = this,
state: BundleDefinition? = null,
key: String? = null,
parameters: ParametersDefinition? = null,
): Lazy<T>
还提供了顶级函数:
fun <T : ViewModel> getLazyViewModelForClass(
clazz: KClass<T>,
owner: ViewModelStoreOwner,
scope: Scope = GlobalContext.get().scopeRegistry.rootScope,
qualifier: Qualifier? = null,
state: BundleDefinition? = null,
key: String? = null,
parameters: ParametersDefinition? = null,
): Lazy<T>
5.10 ViewModel API - Java Compat
必须将 Java 兼容性添加到依赖项中:
// Java Compatibility
implementation "io.insert-koin:koin-android-compat:$koin_version"
您可以使用以下函数或静态函数将 ViewModel 实例注入到 Java 代码库中:viewModel() getViewModel() ViewModelCompat
@JvmOverloads
@JvmStatic
@MainThread
fun <T : ViewModel> getViewModel(
owner: ViewModelStoreOwner,
clazz: Class<T>,
qualifier: Qualifier? = null,
parameters: ParametersDefinition? = null
)
6. 在 Jetpack Compose 中注入
请先了解 Jetpack Compose 相关内容:
developer.android.com/jetpack/com…
6.1 注入@Composable
在编写可组合函数时,您可以访问以下 Koin API:
get()
- 从 Koin 容器中获取实例
getKoin()
- 获取当前 Koin 实例
对于声明“MyService”组件的模块:
val androidModule = module {
single { MyService() }
}
我们可以像这样获取您的实例:
@Composable
fun App() {
val myService = get<MyService>()
}
注意:为了在 Jetpack Compose 的功能方面保持一致,最好的编写方法是将实例直接注入到函数属性中。这种方式允许使用 Koin 进行默认实现,但保持开放状态以根据需要注入实例。
@Composable
fun App(myService: MyService = get()) {
}
6.2 viewModel @Composable
与访问经典单/工厂实例的方式相同,您可以访问以下 Koin ViewModel API:
getViewModel()`或 - 获取实例`koinViewModel()
对于声明“MyViewModel”组件的模块:
module {
viewModel { MyViewModel() }
// or constructor DSL
viewModelOf(::MyViewModel)
}
我们可以像这样获取您的实例:
@Composable
fun App() {
val vm = koinViewModel<MyViewModel>()
}
我们可以在函数参数中获取您的实例:
@Composable
fun App(vm : MyViewModel = koinViewModel()) {
}
7. 管理 Android 作用域
Android 组件,如Activity、Fragment、Service
都有生命周期,这些组件都是由 System 实例化,组件中有相应的生命周期回调。
正因为 Android 组件具有生命周期属性,所以不能在 koin 中传入组件实例。按照生命周期长短,组件可分为三类:
• 长周期组件(
Service、database
)——由多个屏幕使用,永不丢弃• 中等周期组件(
User session
)——由多个屏幕使用,必须在一段时间后删除• 短周期组件(
ViewModel)
——仅由一个 Screen 使用,必须在 Screen 末尾删除
对于长周期组件,我们通常在应用全局使用 single 创建单实例
在 MVP 架构模式下,Presenter 是短周期组件
在 Activity 中创建方式如下
class DetailActivity : AppCompatActivity() {
// injected Presenter
override val presenter : Presenter by inject()
我们也可以在 module 中创建
我们使用 factory 作用域创建 Presenter 实例
val androidModule = module {
// Factory instance of Presenter
factory { Presenter() }
}
生成绑定到作用域的实例 scope
val androidModule = module {
scope<DetailActivity> {
scoped { Presenter() }
}
}
大多数 Android 内存泄漏来自从非 Android 组件引用 UI/Android 组件。系统保留引用在它上面,不能通过垃圾收集完全回收它。
7.1 申明 Android 作用域
要限定 Android 组件上的依赖关系,您必须使用如下所示的块声明一个作用域:scope
class MyPresenter()
class MyAdapter(val presenter : MyPresenter)
module {
// Declare scope for MyActivity
scope<MyActivity> {
// get MyPresenter instance from current scope
scoped { MyAdapter(get()) }
scoped { MyPresenter() }
}
}
7.2 Android Scope 类
Koin 提供了 Android 生命周期组件相关的 Scope 类ScopeActivity Retained ScopeActivity ScopeFragment
class MyActivity : ScopeActivity() {
// MyPresenter is resolved from MyActivity's scope
val presenter : MyPresenter by inject()
}
Android Scope 需要与接口一起使用来实现这样的字段:AndroidScopeComponent scope
abstract class ScopeActivity(
@LayoutRes contentLayoutId: Int = 0,
) : AppCompatActivity(contentLayoutId), AndroidScopeComponent {
override val scope: Scope by activityScope()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
checkNotNull(scope)
}
}
我们需要使用接口并实现属性。这将设置类使用的默认 Scope。AndroidScopeComponent scope
7.3 Android Scope 接口
要创建绑定到 Android 组件的 Koin 作用域,只需使用以下函数:
createActivityScope()
- 为当前 Activity 创建 Scope(必须声明 Scope 部分)
createActivityRetainedScope()
- 为当前 Activity 创建 RetainedScope(由 ViewModel Lifecycle 支持)(必须声明 Scope 部分)
createFragmentScope()
- 为当前 Fragment 创建 Scope 并链接到父 Activity Scope 这些函数可作为委托使用,以实现不同类型的作用域:
activityScope()
- 为当前 Activity 创建 Scope(必须声明 Scope 部分)
activityRetainedScope()
- 为当前 Activity 创建 RetainedScope(由 ViewModel Lifecycle 支持)(必须声明 Scope 部分)
fragmentScope()
- 为当前 Fragment 创建 Scope 并链接到父 Activity Scope
class MyActivity() : AppCompatActivity(contentLayoutId), AndroidScopeComponent {
override val scope: Scope by activityScope()
}
我们还可以使用以下内容设置保留范围(由 ViewModel 生命周期提供支持):
class MyActivity() : AppCompatActivity(contentLayoutId), AndroidScopeComponent {
override val scope: Scope by activityRetainedScope()
}
如果您不想使用 Android Scope 类,则可以使用自己的类并使用 Scope 创建 API AndroidScopeComponent
7.4 Scope 链接
Scope 链接允许在具有自定义作用域的组件之间共享实例。在更广泛的用法中,您可以跨组件使用实例。例如,如果我们需要共享一个实例。Scope UserSession
首先声明一个范围定义:
module {
// Shared user session data
scope(named("session")) {
scoped { UserSession() }
}
}
当需要开始使用实例时,请为其创建范围:UserSession
val ourSession = getKoin().createScope("ourSession",named("session"))
// link ourSession scope to current `scope`, from ScopeActivity or ScopeFragment
scope.linkTo(ourSession)
然后在您需要的任何地方使用它:
class MyActivity1 : ScopeActivity() {
fun reuseSession(){
val ourSession = getKoin().createScope("ourSession",named("session"))
// link ourSession scope to current `scope`, from ScopeActivity or ScopeFragment
scope.linkTo(ourSession)
// will look at MyActivity1's Scope + ourSession scope to resolve
val userSession = get<UserSession>()
}
}
class MyActivity2 : ScopeActivity() {
fun reuseSession(){
val ourSession = getKoin().createScope("ourSession",named("session"))
// link ourSession scope to current `scope`, from ScopeActivity or ScopeFragment
scope.linkTo(ourSession)
// will look at MyActivity2's Scope + ourSession scope to resolve
val userSession = get<UserSession>()
}
}
8.Fragment Factory
由于 AndroidX 已经发布了软件包系列以扩展 Android 的功能 androidx.fragment Fragment
developer.android.com/jetpack/and…
8.1 Fragment Factory
自版本以来,已经引入了 ,一个专门用于创建类实例的类:2.1.0-alpha-3 FragmentFactory
Fragment
developer.android.com/reference/k…
Koin 也提供了创建 Fragment 的工厂类 KoinFragmentFactory
Fragment
8.2 设置 Fragment Factory
首先,在 KoinApplication
声明中,使用关键字设置默认实例:fragmentFactory()
KoinFragmentFactory
startKoin {
// setup a KoinFragmentFactory instance
fragmentFactory()
modules(...)
}
8.3 声明并注入 Fragment
声明一个 Fragment 并在 module 中注入
class MyFragment(val myService: MyService) : Fragment() {
}
val appModule = module {
single { MyService() }
fragment { MyFragment(get()) }
}
8.4 获取 Fragment
使用setupKoinFragmentFactory()
设置 FragmentFactory
查询您的 Fragment ,使用supportFragmentManager
supportFragmentManager.beginTransaction()
.replace<MyFragment>(R.id.mvvm_frame)
.commit()
加入可选参数
supportFragmentManager.beginTransaction()
.replace<MyFragment>(
containerViewId = R.id.mvvm_frame,
args = MyBundle(),
tag = MyString()
)
8.5 Fragment Factory & Koin Scopes
如果你想使用 Koin Activity Scope,你必须在你的 Scope 声明你的 Fragment 作为一个定义:scoped
val appModule = module {
scope<MyActivity> {
fragment { MyFragment(get()) }
}
}
并使用您的 Scope 设置您的 Koin Fragment Factory:setupKoinFragmentFactory(lifecycleScope)
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Koin Fragment Factory
setupKoinFragmentFactory(lifecycleScope)
super.onCreate(savedInstanceState)
//...
}
}
9. WorkManager 的 Koin 注入
koin 为 WorkManager 提供单独的组件包 koin-androidx-workmanager
首先,在 KoinApplication 声明中,使用关键字来设置自定义 WorkManager 实例:workManagerFactory()
class MainApplication : Application(), KoinComponent {
override fun onCreate() {
super.onCreate()
startKoin {
// setup a WorkManager instance
workManagerFactory()
modules(...)
}
setupWorkManagerFactory()
}
AndroidManifest.xml 修改,避免使用默认的
<application . . .>
. . .
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove" />
</application>
9.1 声明 ListenableWorker
val appModule = module {
single { MyService() }
worker { MyListenableWorker(get()) }
}
9.2 创建额外的 WorkManagerFactory
class MainApplication : Application(), KoinComponent {
override fun onCreate() {
super.onCreate()
startKoin {
workManagerFactory(workFactory1, workFactory2)
. . .
}
setupWorkManagerFactory()
}
}
如果 Koin 和 workFactory1 提供的 WorkManagerFactory
都可以实例化 ListenableWorker
,则 Koin 提供的工厂将是使用的工厂。
9.3 更改 koin lib 本身的清单
如果 koin-androidx-workmanager
中的默认 Factory 被禁用,而应用程序开发人员不初始化 koin 的工作管理器基础架构,他最终将没有可用的工作管理器工厂。
针对上面的情况,我们做如下 DSL 改进:
val workerFactoryModule = module {
factory<WorkFactory> { WorkFactory1() }
factory<WorkFactory> { WorkFactory2() }
}
然后让 koin 内部做类似的事情
fun Application.setupWorkManagerFactory(
// no vararg for WorkerFactory
) {
. . .
getKoin().getAll<WorkerFactory>()
.forEach {
delegatingWorkerFactory.addFactory(it)
}
}
参考链接
作者:Calvin873
来源:juejin.cn/post/7189917106580750395