运用 koin 作为 Android 注入东西,真香
koin 为 Android 供应了简略易用的 API 接口,让你简略轻松地接入 koin 结构。
[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”,原创技术文章第一时间推送。