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
factoryOf(::FactoryPresenter)
viewModelOf(::SimpleViewModel)
scope
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
}
class WeatherHeaderFragment : Fragment() {
/*
* Declare shared WeatherViewModel with WeatherActivity
*/
private val weatherViewModel by activityViewModel
}
class WeatherListFragment : Fragment() {
/*
* Declare shared WeatherViewModel with WeatherActivity
*/
private val weatherViewModel by activityViewModel
}
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
qualifier: Qualifier? = null,
owner: ViewModelStoreOwner = this,
state: BundleDefinition? = null,
key: String? = null,
parameters: ParametersDefinition? = null,
): Lazy
还提供了顶级函数:
fun
clazz: KClass
owner: ViewModelStoreOwner,
scope: Scope = GlobalContext.get().scopeRegistry.rootScope,
qualifier: Qualifier? = null,
state: BundleDefinition? = null,
key: String? = null,
parameters: ParametersDefinition? = null,
): Lazy
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
owner: ViewModelStoreOwner,
clazz: Class
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
}
注意:为了在 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
}
我们可以在函数参数中获取您的实例:
@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
scoped { Presenter() }
}
}
大多数 Android 内存泄漏来自从非 Android 组件引用 UI/Android 组件。系统保留引用在它上面,不能通过垃圾收集完全回收它。
7.1 申明 Android 作用域
要限定 Android 组件上的依赖关系,您必须使用如下所示的块声明一个作用域:scope
class MyPresenter()
class MyAdapter(val presenter : MyPresenter)
module {
// Declare scope for MyActivity
scope
// 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
}
}
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
}
}
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
.commit()
加入可选参数
supportFragmentManager.beginTransaction()
.replace
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
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 修改,避免使用默认的
. . .
android:name="androidx.work.impl.WorkManagerInitializer" android:authorities="${applicationId}.workmanager-init" tools:node="remove" /> 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 factory } 然后让 koin 内部做类似的事情 fun Application.setupWorkManagerFactory( // no vararg for WorkerFactory ) { . . . getKoin().getAll .forEach { delegatingWorkerFactory.addFactory(it) } } 参考链接 insert-koin.io/ 推荐阅读 kotlin依赖注入框架之koin(一)kotlin依赖注入框架之koin(二)kotlin依赖注入框架之koin(三) 作者:Calvin873 链接:https://juejin.cn/post/7189917106580750395 最后 如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。 如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。 相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。 全套视频资料: 一、面试合集 二、源码解析合集 三、开源框架合集 欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓ 相关阅读
发表评论