【译】理解Koin Scope

1,838 阅读5分钟

Understanding Android Scopes with Koin
作者:Arnaud Giuliani 译者:不想翻身的鱼

原文链接

组件的生命周期一直是Android开发者最大的痛点。当我们再加上带有作用域管理的依赖注入复杂度...你会进入一个非常复杂和混乱的状况😭

giphy.gif

什么是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这个属性实例也会重新创建。

1_piizzFVkQqO74-IlPI8PNQ.jpeg

让我们用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) 让我们通过scopeKoin的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。

1_piizzFVkQqO74-IlPI8PNQ (1).jpeg

ViewModel:游戏颠覆者

自从2017年,Android 架构组件推荐ViewModel很明确就是为了解决这类生命周期的问题。这个心的组件会服从Activity/Fragment的生命周,并且在配置变更的时候也会保存下来。它仅仅通过一个onCleard()的生命周期的方法来简化生命周期的管理。

ViewModel在Koin里面声明非常简单:

module {
  viewModel { MyViewModel() }
}
  
class MyActivity : AppCompatActivity() {
    val myViewModel : MyViewModel by viewModel()
}

通过一个Android的内部的工厂,ViewModel可以在配置发生变更的时候实例仍能保存下来。

1_SU4WhIz8v88hX7M0Iy293A.jpeg

跟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的实例来持有实例对象,

1_K7I5nhUgGz5lNbrhd-IwdA.jpeg

每个scope都会结束的

控制scope的实例需要你负责来关闭他们。Koin的Scope API会帮你自动处理Android的scope:

  • AndroidScopeComponent接口通过activityScopeactivityRetainedScope委托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非常建设性的反馈。