Android使用Koin依赖项注入 二

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开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值