Android使用Koin依赖项注入 二
  FyeYl0ESQHUh 2023年11月02日 39 0


Android Studio环境为 Android Studio Flamingo | 2022.2.1

Koin的最新版本为3.4.0

在前面一章《Android使用Koin依赖注入一》中,我们知道了如何简单的去使用Koin来完成依赖注入,本章会接着介绍Kolin的作用域相关知识。

作用域Scope

在Koin中,Scope是用于管理依赖项的对象。它可以定义特定Scope内的依赖关系,并在该Scope中跟踪和重用这些依赖关系,在指定的Scope中,只会生成唯一的一个实例。Koin已经默认帮我们定义了三个常用的Scope类,如ScopeActivityRetainedScopeActivityScopeFragment,它们都是实现了AndroidScopeComponent接口。

  • ScopeActivity保证在当前的Activity中只会注入同一个相关的实例,但是在旋转屏幕时,它会重新生成一个实例;
  • RetainedScopeActivity保证在当前的Activity中只会注入同一个相关的实力,并且在旋转屏幕时,注入的还是之前的那个实例,Koin在这种情况下也是通过ViewModel来实现的;
  • ScopeFragment的作用同ScopeActivity

下面我们来看看如何使用这些作用域组件。

ScopeActivity

首先我们定义个一个Activity,继承自ScopeActivity,并在内部注入两个ScopeTest对象,看看这两个对象是否为同一个实例

class MainActivity : ScopeActivity() {
  	// 注入第一个对象
    private val scopeTest by inject<ScopeTest>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        scopeTest.test()

        // 再次注入这个对象
        val test: ScopeTest = scope.get()
        test.test()
    }

    override fun onCloseScope() {
        super.onCloseScope()
        Log.d(TAG, "onCloseScope")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy")
    }
}

// 为测试注入对象
class ScopeTest {

    fun test() {
        Log.d(TAG, "ScopeTest test: ${this.hashCode()}")
    }
}

val scopeModule = module {
  	// 使用scope和scoped来注入ScopeTest对象,并且作用域为MainActivity
    scope<MainActivity> {
        scoped {
            ScopeTest()
        }
    }
}

运行App看一下两个对象输出的HashCode是否相同:

ScopeTest test: 36979086
ScopeTest test: 36979086

从日志中可以看出,只要在MainActivity中,无论何时在何处注入ScopeTest对象,都是为同一个实例,这就是scope的作用,保证在我们定义的作用域范围内只会注入一个相同的实例。

在使用scope来限定作用域时,我们传入的作用域对用的类必须是实现了AndroidScopeComponent接口,此接口内部就一个scope对象和onCloseScope()方法

interface AndroidScopeComponent {

    //TODO Make scope nullable with var?

    /**
     * Current Scope in use by the component
     */
    val scope: Scope

    /**
     * Called before closing a scope, on onDestroy
     */
    fun onCloseScope(){}
}

scope是为了帮助我们注入对象和绑定当前作用域生命周期而存在,onCloseScope()方法是回调当前作用域关闭的作用。具体后面我们分析下ScopeActivity的实现,看看它是如何定义的。

RetainedScopeActivity

Retained的字面意思就是保存、保留的意思,它的作用就是在当前Activity人为或者意外被迫销毁重建时,还可以保留当前注入的实例。

class RetainActivity : RetainedScopeActivity() {
  	// 依旧是注入ScopeTest实例
    private val scopeTest by inject<ScopeTest>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        scopeTest.test()
      	// 第二次注入ScopeTest实例
        val test: ScopeTest = scope.get()
        test.test()
    }
}

# 注入方法还是和ScopeActivity一样
val scopeModule = module {
  	// 只是此处传入的作用域对象变成了RetainActivity
    scope<RetainActivity> {
        scoped { ScopeTest() }
    }
}

日志部分我们为两部分看,第一步看看第一次打开此界面时,两次日志是否输出同一个实例

ScopeTest test: 48843391
ScopeTest test: 48843391
onDisplayChanged: 横屏

在横屏的时候,两次日志输出的HashCode确实是相同的,证明了只要在此界面中,注入的ScopeTest都是同一个实例,此时RetainedScopeActivityScopeActivity是相同的作用,接下来我们再把屏幕旋转成竖屏看看日志。

ScopeTest test: 48843391
ScopeTest test: 48843391
onDisplayChanged: 横屏
ScopeTest test: 48843391
ScopeTest test: 48843391
onDisplayChanged: 竖屏

日志输出和我们预期的是同一个结果,无论是横屏还是竖屏的情况下,ScopeTest被注入的都是同一个实例,在文章开头的地方我们就提到过,它是通过创建一个ViewModel来保证旋转屏幕之后还是注入之前的实例,具体源码本次就不详细介绍了,可以通过/org/koin/androidx/scope/ComponentActivityExt.kt:83来了解一下。

ScopeActivity实现

ScopeActivity源码还是比较简单的,直接看代码

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)
    }
}
  • 实现了AndroidScopeComponent接口
  • 覆写了scope对象,通过懒加载的形式初始化

下面直接进入activityScope()方法,看看它是如何创建Scope对象的

// 通过懒加载调用createActivityScope()方法
fun ComponentActivity.activityScope() = lazy { createActivityScope() }

fun ComponentActivity.createActivityScope(): Scope {
    if (this !is AndroidScopeComponent) {
        error("Activity should implement AndroidScopeComponent")
    }
  	// 通过createScopeForCurrentLifecycle()创建一个绑定生命周期的Scope对象
    return getKoin().getScopeOrNull(getScopeId()) ?: createScopeForCurrentLifecycle(this)
}

internal fun ComponentCallbacks.createScopeForCurrentLifecycle(owner: LifecycleOwner): Scope {
    val scope = getKoin().createScope(getScopeId(), getScopeName(), this)
    scope.registerCallback(object : ScopeCallback{
        override fun onScopeClose(scope: Scope) {
            (owner as AndroidScopeComponent).onCloseScope()
        }
    })
    owner.registerScopeForLifecycle(scope)
    return scope
}

最终还是通过createScopeForCurrentLifecycle()方法来具体创建Scope对象。

此方法主要就是为了绑定Activity的生命周期,并且在registerCallback回调中感知ActivityonDestroy()时机,同时在界面销毁时清除作用域的绑定关系。

到这为止,Koin的一些日常使用已经呈现出来了,大家可以自行体验下Koin依赖注入使用的简单和便捷!

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

Android使用Koin依赖项注入 二_Android


相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  P3nxyT0LRuwj   2023年11月19日   24   0   0 数据库ide配置文件
  oIa1edJoFmXP   2023年11月22日   25   0   0 ide数据List
  YKMEHzdP8aoh   2023年12月11日   63   0   0 DNSidePod
  YKMEHzdP8aoh   2023年11月24日   36   0   0 ide重定向Rust
FyeYl0ESQHUh