使用 koin 作为 Android 注入工具,真香

Posted 嘴巴吃糖了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 koin 作为 Android 注入工具,真香相关的知识,希望对你有一定的参考价值。

koin 为 android 提供了简单易用的 API 接口,让你简单轻松地接入 koin 框架。

[koin 在 Android 中的 gradle 配置]

mp.weixin.qq.com/s/bscC7mO4O…

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)


参考链接

insert-koin.io/

推荐阅读

作者:Calvin873
链接:https://juejin.cn/post/7189917106580750395

最后

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

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


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

全套视频资料:

一、面试合集

二、源码解析合集


三、开源框架合集


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

以上是关于使用 koin 作为 Android 注入工具,真香的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Android/Kotlin App 上通过 Koin 注入在 BaseActivity 中初始化/注入通用 ViewModel

Koin如何注入Android活动/ appcompatactivity之外

Android注解三大框架DaggerHilt和Koin有何不同?

FragmentFactory + Koin 实现Fragment依赖注入

如何使用 Koin 依赖注入 (Kotlin) 检索与给定类型匹配的所有实例

如何在 Room MVVM 架构中实现 Koin 依赖注入