运用 koin 作为 Android 注入东西,真香

koin 为 Android 供应了简略易用的 API 接口,让你简略轻松地接入 koin 结构

使用 koin 作为 Android 注入东西,真香

[koin 在 Android 中的 gradle 配备]

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

1.Application 类中 startKoin

从您的类中,您可以运用该函数并注入 Android 上下文,如下所示:

ApplicationstartKoinandroidContext
classMainApplication:Application(){
overridefunonCreate(){
super.onCreate()
startKoin{
//LogKoinintoAndroidlogger
androidLogger()
//ReferenceAndroidcontext
androidContext(this@MainApplication)
//Loadmodules
modules(myAppModules)
}
}
}

假设您需求从另一个 Android 类发动 Koin,您可以运用该函数为您的 Android 实例供应如下:startKoin Context

startKoin{
//injectAndroidcontext
androidContext(/*yourandroidcontext*/)
//...
}

2. 额外配备

从您的 Koin 配备(在块代码中),您还可以配备 Koin 的多个部分。startKoin { }

2.1 Koin Logging for Android

koin 供应了 log 的 Android 完结。

startKoin{
//useAndroidlogger-Level.INFObydefault
androidLogger()
//...
}

2.2 加载特征

您可以在文件中运用 Koin 特征来存储键/值:assets/koin.properties

startKoin{
//...
//usepropertiesfromassets/koin.properties
androidFileProperties()
}

3. Android 中注入方针实例

3.1 为 Android 类做准备

koin 供应了KoinComponents 扩展,Android 组件都具有这种扩展,这些组件包含 Activity Fragment Service ComponentCallbacks

您可以通过如下办法访问 Kotlin 扩展:

by inject()– 来自 Koin 容器的延迟核算实例

get() – 从 Koin 容器中获取实例

我们可以将一个特征声明为慵懒注入:

module{
//definitionofPresenter
factory{Presenter()}
}
classDetailActivity:AppCompatActivity(){
//LazyinjectPresenter
overridevalpresenter:Presenterbyinject()
overridefunonCreate(savedInstanceState:Bundle?){
//...
}
}

或者我们可以直接得到一个实例:

overridefunonCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
//RetrieveaPresenterinstance
valpresenter:Presenter=get()
}

留心:假设你的类没有扩展,只需添加 KoinComponent 接口,假设你需求或来自另一个类的实例。inject() get()

3.2 Android Context 运用

classMainApplication:Application(){
overridefunonCreate(){
super.onCreate()
startKoin{
//injectAndroidcontext
androidContext(this@MainApplication)
//...
}
}
}

在你的定义中,下面的函数容许你在 Koin 模块中获取实例,以帮忙你简略地编写需求实例的表达式。androidContext() androidApplication() Context Application

valappModule=module{
//createaPresenterinstancewithinjectionofR.string.mystringresourcesfromAndroid
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 使用程序:

//Asimpleservice
classSimpleServiceImpl():SimpleService
//aPresenter,usingSimpleServiceandcanreceive"id"injectedparam
classFactoryPresenter(valid:String,valservice:SimpleService)
//aViewModelthatcanreceive"id"injectedparam,useSimpleServiceandgetSavedStateHandle
classSimpleViewModel(valid:String,valservice:SimpleService,valhandle:SavedStateHandle):ViewModel()
//ascopedSession,thatcanreceivedlinktotheMyActivity(fromscope)
classSession(valactivity:MyActivity)
//aWorker,usingSimpleServiceandgettingContext&WorkerParameters
classSimpleWorker(
privatevalsimpleService:SimpleService,
appContext:Context,
privatevalparams: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
classComponentA()
classComponentB(valcomponentA:ComponentA)
valmoduleA=module{
//SingletonComponentA
single{ComponentA()}
}
valmoduleB=module{
//SingletonComponentBwithlinkedinstanceComponentA
single{ComponentB(get())}
}

我们只需求在发动 Koin 容器时声明已运用模块的列表:

classMainApplication:Application(){
overridefunonCreate(){
super.onCreate()
startKoin{
//...
//Loadmodules
modules(moduleA,moduleB)
}
}
}

5.2 模块包含

类中供应了一个新函数,它容许您通过以有安排和结构化的办法包含其他模块来组合模块includes() Module

新模块有 2 个杰出特征:

将大型模块拆分为更小、更集中的模块。

在模块化项目中,它容许您更精细地操控模块可见性(请参阅下面的示例)。

它是怎么作业的?让我们采用一些模块,我们将模块包含在:parentModule

//`:feature`module
valchildModule1=module{
/*Otherdefinitionshere.*/
}
valchildModule2=module{
/*Otherdefinitionshere.*/
}
valparentModule=module{
includes(childModule1,childModule2)
}
//`:app`module
startKoin{modules(parentModule)}

请留心,我们不需求显式设置全部模块:通过包含,声明的全部模块将自动加载。

parentModule includes childModule1 childModule2 parentModule childModule1 childModule2

信息:模块加载现在通过优化,可以展平全部模块图,并避免重复的模块定义。

最终,您可以包含多个嵌套或重复的模块,Koin 将扁平化全部包含的模块,删去重复项:

//:featuremodule
valdataModule=module{
/*Otherdefinitionshere.*/
}
valdomainModule=module{
/*Otherdefinitionshere.*/
}
valfeatureModule1=module{
includes(domainModule,dataModule)
}
valfeatureModule2=module{
includes(domainModule,dataModule)
}
//:appmodule
classMainApplication:Application(){
overridefunonCreate(){
super.onCreate()
startKoin{
//...
//Loadmodules
modules(featureModule1,featureModule2)
}
}
}

请留心,全部模块将只包含一次:dataModule domainModule featureModule1 featureModule2

5.3 Android ViewModel 和 Navigation

Gradle 模块引入了一个新的 DSL 关键字,该关键字作为弥补,以帮忙声明 ViewModel 组件并将其绑定到 Android 组件生命周期。关键字也可用容许您运用其结构函数声明 ViewModel。koin-android viewModel singlefactory viewModelOf

valappModule=module{
//ViewModelforDetailView
viewModel{DetailViewModel(get(),get())}
//ordirectlywithconstructor
viewModelOf(::DetailViewModel)
}

声明的组件有必要至少扩展类。您可以指定怎么注入类的结构函数 并运用该函数注入依托项。android.arch.lifecycle.ViewModel get()

留心:关键字有助于声明 ViewModel 的工厂实例。此实例将由内部 ViewModelFactory 处理,并在需求时从头附加 ViewModel 实例。它还将容许注入参数。viewModel viewModelOf

5.4 注入 ViewModel

在 Android 组件中运用 viewModel ,Activity Fragment Service

by viewModel()– 慵懒托付特征,用于将视图模型注入到特征中

getViewModel()– 直接获取视图模型实例

classDetailActivity:AppCompatActivity(){
//LazyinjectViewModel
valdetailViewModel:DetailViewModelbyviewModel()
}

5.5 Activity 同享 ViewModel

一个 ViewModel 实例可以在 Fragment 及其主 Activity 之间同享。

要在运用中注入同享视图模型,请执行以下操作:Fragment

by activityViewModel()– 慵懒托付特征,用于将同享 viewModel 实例注入到特征中

get ActivityViewModel()– 直接获取同享 viewModel 实例

只需声明一次视图模型:

valweatherAppModule=module{
//WeatherViewModeldeclarationforWeatherViewcomponents
viewModel{WeatherViewModel(get(),get())}
}

留心:viewModel 的约束符将作为 viewModel 的符号处理

并在 Activity 和 Fragment 中重复运用它:

classWeatherActivity:AppCompatActivity(){
/*
*DeclareWeatherViewModelwithKoinandallowconstructordependencyinjection
*/
privatevalweatherViewModelbyviewModel<WeatherViewModel>()
}
classWeatherHeaderFragment:Fragment(){
/*
*DeclaresharedWeatherViewModelwithWeatherActivity
*/
privatevalweatherViewModelbyactivityViewModel<WeatherViewModel>()
}
classWeatherListFragment:Fragment(){
/*
*DeclaresharedWeatherViewModelwithWeatherActivity
*/
privatevalweatherViewModelbyactivityViewModel<WeatherViewModel>()
}

5.6 将参数传递给结构函数

向 viewModel 传入参数,示例代码如下:

模块中

valappModule=module{
//ViewModelforDetailViewwithidasparameterinjection
viewModel{parameters->DetailViewModel(id=parameters.get(),get(),get())}
//ViewModelforDetailViewwithidasparameterinjection,resolvedfromgraph
viewModel{DetailViewModel(get(),get(),get())}
//orConstructorDSL
viewModelOf(::DetailViewModel)
}

依托注入点传入参数

classDetailActivity:AppCompatActivity(){
valid:String//idoftheview
//LazyinjectViewModelwithidparameter
valdetailViewModel:DetailViewModelbyviewModel{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()– 直接获取情况视图模型实例

classDetailActivity:AppCompatActivity(){
//MyStateVMviewModelinjectedwithSavedStateHandle
valmyStateVM:MyStateVMbyviewModel()
}

5.8 Navigation 导航图中的 viewModel

您可以将 ViewModel 实例的规模约束为导航图。只需求传入 ID 给by koinNavGraphViewModel()

classNavFragment:Fragment(){
valmainViewModel:NavViewModelbykoinNavGraphViewModel(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 兼容性添加到依托项中:

//JavaCompatibility
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”组件的模块:

valandroidModule=module{
single{MyService()}
}

我们可以像这样获取您的实例:

@Composable
funApp(){
valmyService=get<MyService>()
}

留心:为了在 Jetpack Compose 的功用方面坚持共同,最好的编写办法是将实例直接注入到函数特征中。这种办法容许运用 Koin 进行默许完结,但坚持敞开情况以依据需求注入实例。

@Composable
funApp(myService:MyService=get()){
}

6.2 viewModel @Composable

与访问经典单/工厂实例的办法相同,您可以访问以下 Koin ViewModel API:

getViewModel()或 – 获取实例koinViewModel()

关于声明“MyViewModel”组件的模块:

module{
viewModel{MyViewModel()}
//orconstructorDSL
viewModelOf(::MyViewModel)
}

我们可以像这样获取您的实例:

@Composable
funApp(){
valvm=koinViewModel<MyViewModel>()
}

我们可以在函数参数中获取您的实例:

@Composable
funApp(vm:MyViewModel=koinViewModel()){
}

7. 办理 Android 作用域

Android 组件,如Activity、Fragment、Service都有生命周期,这些组件都是由 System 实例化,组件中有相应的生命周期回调。

正因为 Android 组件具有生命周期特征,所以不能在 koin 中传入组件实例。依照生命周期长短,组件可分为三类:

  • • 长周期组件(Service、database)——由多个屏幕运用,永不丢掉
  • • 中等周期组件(User session)——由多个屏幕运用,有必要在一段时间后删去
  • • 短周期组件(ViewModel) ——仅由一个 Screen 运用,有必要在 Screen 结尾删去

关于长周期组件,我们通常在使用全局运用 single 创建单实例

在 MVP 架构形式下,Presenter 是短周期组件

在 Activity 中创建办法如下

classDetailActivity:AppCompatActivity(){
//injectedPresenter
overridevalpresenter:Presenterbyinject()

我们也可以在 module 中创建

我们运用 factory 作用域创建 Presenter 实例

valandroidModule=module{
//FactoryinstanceofPresenter
factory{Presenter()}
}

生成绑定到作用域的实例 scope

valandroidModule=module{
scope<DetailActivity>{
scoped{Presenter()}
}
}

大多数 Android 内存泄露来自从非 Android 组件引用 UI/Android 组件。体系保存引用在它上面,不能通过垃圾收集完全收回它。

7.1 声明 Android 作用域

要约束 Android 组件上的依托联络,您有必要运用如下所示的块声明一个作用域:scope

classMyPresenter()
classMyAdapter(valpresenter:MyPresenter)
module{
//DeclarescopeforMyActivity
scope<MyActivity>{
//getMyPresenterinstancefromcurrentscope
scoped{MyAdapter(get())}
scoped{MyPresenter()}
}
}

7.2 Android Scope 类

Koin 供应了 Android 生命周期组件相关的 Scope 类ScopeActivity Retained ScopeActivity ScopeFragment

classMyActivity:ScopeActivity(){
//MyPresenterisresolvedfromMyActivity'sscope
valpresenter:MyPresenterbyinject()
}

Android Scope 需求与接口一起运用来完结这样的字段:AndroidScopeComponent scope

abstractclassScopeActivity(
@LayoutRescontentLayoutId:Int=0,
):AppCompatActivity(contentLayoutId),AndroidScopeComponent{
overridevalscope:ScopebyactivityScope()
overridefunonCreate(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

classMyActivity():AppCompatActivity(contentLayoutId),AndroidScopeComponent{
overridevalscope:ScopebyactivityScope()
}

我们还可以运用以下内容设置保存规模(由 ViewModel 生命周期供应支撑):

classMyActivity():AppCompatActivity(contentLayoutId),AndroidScopeComponent{
overridevalscope:ScopebyactivityRetainedScope()
}

假设您不想运用 Android Scope 类,则可以运用自己的类并运用 Scope 创建 API AndroidScopeComponent

7.4 Scope 链接

Scope 链接容许在具有自定义作用域的组件之间同享实例。在更广泛的用法中,您可以跨组件运用实例。例如,假设我们需求同享一个实例。Scope UserSession

首要声明一个规模定义:

module{
//Sharedusersessiondata
scope(named("session")){
scoped{UserSession()}
}
}

当需求开始运用实例时,请为其创建规模:UserSession

valourSession=getKoin().createScope("ourSession",named("session"))
//linkourSessionscopetocurrent`scope`,fromScopeActivityorScopeFragment
scope.linkTo(ourSession)

然后在您需求的任何地方运用它:

classMyActivity1:ScopeActivity(){
funreuseSession(){
valourSession=getKoin().createScope("ourSession",named("session"))
//linkourSessionscopetocurrent`scope`,fromScopeActivityorScopeFragment
scope.linkTo(ourSession)
//willlookatMyActivity1'sScope+ourSessionscopetoresolve
valuserSession=get<UserSession>()
}
}
classMyActivity2:ScopeActivity(){
funreuseSession(){
valourSession=getKoin().createScope("ourSession",named("session"))
//linkourSessionscopetocurrent`scope`,fromScopeActivityorScopeFragment
scope.linkTo(ourSession)
//willlookatMyActivity2'sScope+ourSessionscopetoresolve
valuserSession=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{
//setupaKoinFragmentFactoryinstance
fragmentFactory()
modules(...)
}

8.3 声明并注入 Fragment

声明一个 Fragment 并在 module 中注入

classMyFragment(valmyService:MyService):Fragment(){
}
valappModule=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

valappModule=module{
scope<MyActivity>{
fragment{MyFragment(get())}
}
}

并运用您的 Scope 设置您的 Koin Fragment Factory:setupKoinFragmentFactory(lifecycleScope)

classMyActivity:AppCompatActivity(){
overridefunonCreate(savedInstanceState:Bundle?){
//KoinFragmentFactory
setupKoinFragmentFactory(lifecycleScope)
super.onCreate(savedInstanceState)
//...
}
}

9. WorkManager 的 Koin 注入

koin 为 WorkManager 供应单独的组件包 koin-androidx-workmanager

首要,在 KoinApplication 声明中,运用关键字来设置自定义 WorkManager 实例:workManagerFactory()

classMainApplication:Application(),KoinComponent{
overridefunonCreate(){
super.onCreate()
startKoin{
//setupaWorkManagerinstance
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

valappModule=module{
single{MyService()}
worker{MyListenableWorker(get())}
}

9.2 创建额外的 WorkManagerFactory

classMainApplication:Application(),KoinComponent{
overridefunonCreate(){
super.onCreate()
startKoin{
workManagerFactory(workFactory1,workFactory2)
...
}
setupWorkManagerFactory()
}
}

假设 Koin 和 workFactory1 供应的 WorkManagerFactory 都可以实例化 ListenableWorker,则 Koin 供应的工厂将是运用的工厂。

9.3 更改 koin lib 本身的清单

假设 koin-androidx-workmanager 中的默许 Factory 被禁用,而使用程序开发人员不初始化 koin 的作业办理器根底架构,他最终将没有可用的作业办理器工厂。

针对上面的情况,我们做如下 DSL 改善:

valworkerFactoryModule=module{
factory<WorkFactory>{WorkFactory1()}
factory<WorkFactory>{WorkFactory2()}
}

然后让 koin 内部做相似的事情

funApplication.setupWorkManagerFactory(
//novarargforWorkerFactory
){
...
getKoin().getAll<WorkerFactory>()
.forEach{
delegatingWorkerFactory.addFactory(it)
}
}

参考链接

insert-koin.io/

引荐阅览

  • kotlin依托注入结构之koin(一)
  • kotlin依托注入结构之koin(二)
  • kotlin依托注入结构之koin(三)

欢迎重视我的大众号“虎哥LoveDroid”,原创技术文章第一时间推送。