Understanding Android Scopes with Koin
作者:Arnaud Giuliani 译者:不想翻身的鱼
组件的生命周期一直是Android开发者最大的痛点。当我们再加上带有作用域管理的依赖注入复杂度...你会进入一个非常复杂和混乱的状况😭
什么是Scope?Android Scope到底是啥意思,怎么处理这个Scope? 让我们重新看看这些概念,方便我们更好的理解它们。
⚠️下面的展示的API只在Koin3.0.1&2.3.0的版本可用
⚠️到这里查看最新更新日志
本文受到Manuel Vivo的Scoping in Android and Hilt这篇文章的启发。Manuel已经展示了Scope在Dagger Hilt里面怎么工作的。这里我要演示下在Koin里面是怎么工作的。
什么是Scope?
Scope 是固定的一个时间段或者说是某个对象存在期间方法能被调用到。换句话说,Scope是指向同一个实例的指定关键字的上下文。另一种方式可以理解为某个对象状态保存的时间。当一个scope的上下文结束了,所有绑定到这个scope上面对象都在scope作用域外了,并且无法重新注入其他实例。 ———— 依赖注入 by Dhanji R. Prasanna
简而言之,scope是一个我们在一个特定时间作用域内维护一些实例的地方。那什么是Android的scope呢?我们在一个Android组件生命周期内维护的实例。
Android组件都有自己的生命周期(Activity,Fragment,Service...)这些实例的每个属性都同跟组件的生命周期是一致的。也就是说,属性在Android组件生命周期结束的时候会被GC掉。
让我们看一个简单的例子:
class MyActivity : AppCompatActivity() {
val presenter = MyPresenter()
}
MyPresenter的实例依赖MyActivity实例的生命周期。一旦Activitie Finish了,这个presenter的实例会被从内存中移出。在新的MyActivity实例,presenter这个属性实例也会重新创建。
让我们用Koin来声明
module {
factory { MyPresenter() }
}
class MyActivity : AppCompatActivity() {
val presenter : MyPresenter by inject()
}
你是否需要Scope?
如果我们的属性在生命周期结束的时候就可以丢弃了,为什么我们还要关心scope呢?
有以下几点原因为什么我们需要scope:
- 对于一个scope作用域内的所有组件提供相同的实例
- 在特定的一个时间段范围内保存相同的实例
以下是一些Android 应用里面常见的scope的使用场景:
- Android 生命周期scope——绑定到Android生命周期组件(Activity,Fragment)的实例
- 自动以scope——特定时间范围内保存的实例(不止一个Activity,比如:wallet)
让我们通过
scope
Koin的DSL关键字生命一个MyActivity
的Scope。通过scope<MyActivity>
,我们创建了一个代码块去定义我们的作用域实例。每个被限定作用域的组件都要用scope
来声明。
class MyAdapter(val presenter : MyPresenter)
module {
scope<MyActivity> {
// get MyPresenter instance from current scope
scoped { MyAdapter(get()) }
scoped { MyPresenter() }
}
}
这种方式可以把MyPresenter()
实例分享给另外一个scope的定义。在MyAdapter
实例里面的MyPresenter
实例我们通过get()
关键字来获取。
现在,在MyActivity
类里面实现AndroidScopeComponent
这个接口来激活scope,实现改接口必须覆盖的scope
属性,这个属性通过activityScope()
委托函数来实现,代码如下:
class MyActivity : AppCompatActivity, AndroidScopeComponent {
// get current Activity's scope
override val scope : Scope by activityScope()
// MyPresenter is resolved from MyActivity's scope
val presenter : MyPresenter by inject()
}
使用
AndroidScopeComponent
接口,需要复写scope
属性。这个scope会自动应用在所有默认的API里面不需要你做任何事情(inject,get,viewModel...)
activityScope()
这个委托函数确保确保在Activity生命周期结束的时候将scope的实例给清除掉。
注意Koin还推荐用ScopeActivity
类,来帮助你封装Activity scope的定义。阅读这篇文档了解详细信息。
但是,当配置发生变更的时候,我们还是会丢掉MyPresenter
的实例。在新的MyActivity
实例里面,我们会创建新的Activity Scope。
ViewModel:游戏颠覆者
自从2017年,Android 架构组件推荐ViewModel
很明确就是为了解决这类生命周期的问题。这个心的组件会服从Activity/Fragment的生命周,并且在配置变更的时候也会保存下来。它仅仅通过一个onCleard()
的生命周期的方法来简化生命周期的管理。
ViewModel在Koin里面声明非常简单:
module {
viewModel { MyViewModel() }
}
class MyActivity : AppCompatActivity() {
val myViewModel : MyViewModel by viewModel()
}
通过一个Android的内部的工厂,ViewModel可以在配置发生变更的时候实例仍能保存下来。
跟Dagger和Hilt类似,有一个特别的scope可以在配置发生变化的时候实例能保存下来。
class MyActivity : AppCompatActivity, AndroidScopeComponent {
// use Activity Retained Scope
override val scope : Scope by activityRetainedScope()
val presenter : MyPresenter by inject()
}
activityRetainedScope
这个函数通过ViewModel holder的实例来持有实例对象,
每个scope都会结束的
控制scope的实例需要你负责来关闭他们。Koin的Scope API会帮你自动处理Android的scope:
AndroidScopeComponent
接口通过activityScope
和activityRetainedScope
委托scope
的属性来为你的Android组件设置scope。ScopeActivity
会帮你封装所有的东西
Fragment和Service类都有相同的API。Fragment scope会自动绑定到所属Activity的scope。查看Android Scope文档了解更多详情。
自定义的Scope可以帮你在指定一段时间内维护APP的状态。需要注意合适地设置create/close的顺序。看这篇文档了解scope API更详细的信息:insert-koin.io/docs/refere…
确保你确实需要使用scope实例的时候你才去用它。尽量让事情月简单越好。
以下摘自来自Manuel Vivo的文章:
scope 的开销会比较大,因为哪些对象会一直保存在内存里面直到持有者被销毁。在你项目里面使用scope的对象一定要三思。
非常感谢Manuel Vivo非常建设性的反馈。