android 依赖注入(Hilt, Koin)

λ:

仓库地址: github.com/lzyprime/an…

开发分支dev加入了compose, 图片库由 glide 换为 coil, DataStore代替SharedPreference。 同时剔除掉LiveData, 用Flow代替。

本来想完全用compose完成UI实现。但是目前compose组件贫瘠,与其他库的配合库也都没有稳定。部分场景下实现反倒费力。所以开两个分支:

  • dev: compose 只做部分控件实现,主体仍保留传统库和其他方式。
  • dev_compose: view 层完全用 compose 实现,包括路由导航。删除layout, navgation, menu等文件夹,删除compose 以外的依赖。

hilt 或者 koin 做依赖注入是贯穿全局的。所以得先会这个

android依赖注入 官网文档

hilt 官网

koin 官网

关于文档,还是尽量看英文原版。原版文档本身有一定延迟,而中文文档翻译又会延迟一段。就导致文档里api可能已过时,内容废弃等问题。比如hilt在android官网的文档。中文版本还是alpha版本的,@ViewModelInject接口已经废弃等.

当然中文文档中介绍和原理部分还是可以参考,即使api改变了,这种东西一般也不会变。

共性:

无论是哪个库,原理都是一样的:

  1. Application, Activity, Fragment中开一个容器Container

  2. 所依赖的实例从Container中获得,并以单例存在于Container

Android 手动依赖注入文档

//example
    class MyApplication : Application() {
        val appContainer = AppContainer()
        ...
    }

    class LoginActivity: Activity() {

        private lateinit var loginViewModel: LoginViewModel

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)

            val appContainer = (application as MyApplication).appContainer
            loginViewModel = LoginViewModel(appContainer.userRepository)
        }
    }
复制代码

手动依赖注入问题:

  • 必须自行管理 Container,手动为依赖项创建实例,根据生命周期移除实例
  • 样板代码
  • ViewModel依赖注入,要靠ViewModelProvider.Factory才可以复用Jetpack中的内容. 或者放进容器,自己维护ViewModel生命周期。

依赖注入有什么好处是老生常谈。当然可以不用,通篇object, 直接就单例,到处可以使用。

依赖注入优势,官网给的总结:

  • 重用类以及分离依赖项:更容易换掉依赖项的实现。由于控制反转,代码重用得以改进,并且类不再控制其依赖项的创建方式,而是支持任何配置。
  • 易于重构:依赖项成为 API Surface 的可验证部分,因此可以在创建对象时或编译时进行检查,而不是作为实现详情隐藏。
  • 易于测试:类不管理其依赖项,因此在测试时,您可以传入不同的实现以测试所有不同用例。

Hilt

Hilt做法是改变并拓展基类,比如继承自Application的,通过生成的Hilt_XXApplication,在里边实现容器维护。可以看生成的代码。

开容器

通过@HiltAndroidApp, @AndroidEntryPoint标记,生成对应基类,在基类里实现容器的维护逻辑。

@HiltAndroidApp
class UnsplashApplication : Application()

@AndroidEntryPoint
class MainActivity : AppCompatActivity()
复制代码

@Inject 标记需要注入项

class UnsplashRepository @Inject constructor(private val service: UnsplashService)

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject lateinit var service: XXService
    ...
}
复制代码

注册实例获取方式

创建 @Module, 通过 @Provides@Binds 标记实例获取方式,并通过 @InstallIn 标记实例放在哪个容器里。

@Binds

interface XXService { ... }

class XXServiceImpl @Inject constructor(
  ...
) : XXService { ... }

@Module
@InstallIn(ActivityComponent::class)
abstract class XXModule {

  @Binds
  abstract fun bindAnalyticsService(
    xxServiceImpl: XXServiceImpl
  ): XXService
}
复制代码

@Provides

interface XXService {
    ...
    companion object : XXService {
        ...
    }
}

@Module
@InstallIn(ActivityComponent::class)
object XXModule {
  @Provides
  fun provideXXService(): XXService = XXService
}
复制代码

两种方式都可以标记某个实例的获取方式。@InstallIn(ActivityComponent) 标记存放在Activity的容器Container中。参照表:

component 注入到 create at destroyed at
SingletonComponent Application Application#onCreate() Application#onDestroy()
ActivityRetainedComponent N/A Activity#onCreate() Activity#onDestroy()
ViewModelComponent ViewModel ViewModel created ViewModel destroyed
ActivityComponent Activity Activity#onCreate() Activity#onDestroy()
FragmentComponent Fragment Fragment#onAttach() Fragment#onDestroy()
ViewComponent View View#super() View destroyed
ViewWithFragmentComponent View annotated with @WithFragmentBindings View#super() View destroyed
ServiceComponent Service Service#onCreate() Service#onDestroy()

除此之外,也可以通过标记作用域的方式,标记存放在哪个容器中,如:

@Singleton // 放入Application容器中,相当于全局单例
class UserRepository @Inject constructor(...)
复制代码

对照表:

class component Scope
Application SingletonComponent @Singleton
Activity ActivityRetainedComponent @ActivityRetainedScoped
ViewModel ViewModelComponent @ViewModelScoped
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
View annotated with @WithFragmentBindings ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped

这些花里胡哨的最后影响的不过是实例的生存周期,以及单例范围,如标记了Activity内,则每个Activity都会各自维护一份单例。以此类推

为实例提供多个获取方式

// 限定符
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class DebugService

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ReleaseService
复制代码
// module
@Module
@InstallIn(ApplicationComponent::class)
object NetworkModule {

  @DebugService
  @Provides
  fun provideDebugService(): XXService = XXService(debug_url)

  @ReleaseService
  @Provides
  fun provideReleaseService(): XXService = XXService(release_url)
}
复制代码
// use
class UserRepository @Inject constructor(
    @DebugService service: XXService
)

// or
class Example {
    @Release
    @Inject lateinit var okHttpClient: OkHttpClient
}
复制代码

预设限定符:@ApplicationContext@ActivityContext

class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: AnalyticsService
) { ... }
复制代码

ViewModel

使用@HiltViewModel标记,在Activity中仍然可以通过 by viewModels() 的方式获取到。

@HiltViewModel
class ExampleViewModel @Inject constructor(
  private val savedStateHandle: SavedStateHandle,
  private val repository: ExampleRepository
) : ViewModel() {
  ...
}

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
  private val exampleViewModel: ExampleViewModel by viewModels()
  ...
}
复制代码

要了解如何做到的,首先要知道by viewModel()如何实现。完整的函数是要传ViewModel Factory 获取方式的,缺省值为空,当什么都不穿时,会调用Activity#getDefaultViewModelProviderFactory。会创建个默认工厂。

Hilt实现的注入方式,就是改变并拓展基类。所以在生成的Activity基类里,override掉这个函数,先去Hilt中找,没有匹配的则返回默认行为。

这是KoinHilt明显差别之一。 Koin类似于把手动注入的过程封装一下,所以 by viewModel()koin 库额外实现的一版,与原有同名,是两个不同函数。

Navigation

可以以导航图为单位共享一个 ViewModel

val viewModel: ExampleViewModel by hiltNavGraphViewModels(R.id.my_graph)
复制代码

gradle plugin 生成代码

得益于Hilt通过Gradle Plugin生成代码, 否则我们就要:

@HiltAndroidApp(Application.class)
class FooApplication : Hilt_FooApplication

@AndroidEntryPoint(FragmentActivity.class)
class FooActivity : Hilt_FooActivity

...
复制代码

Navigation SafeArgs 同样也是用 Gradle Plugin 生成代码的库,所以它并不在dependencies{ implementation "xxx" } 而是在 plugins{ id "xxx" }

Koin

当了解了容器的概念,再来看Koin,或者其他依赖注入库就很容易理解。Koin源码在github都有,可以自己扒。或者猜测一下实现,看库是不是与自己一样。或者觉得库哪些内容实现不优雅不好,有无更好的方案或写法。

开容器

Koin 通过 startKoin

class MainApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        startKoin {
            // Koin Android logger
            androidLogger()
            //inject Android context
            androidContext(this@MainApplication)
            // use modules
            modules(myAppModules)
        }
        
    }
}
复制代码

标记需要注入项

by inject()

class MySimpleActivity : AppCompatActivity() {
    val firstPresenter: MySimplePresenter by inject()
}
复制代码

get()

class MySimpleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
           val firstPresenter: MySimplePresenter = get()
    }
}
复制代码

注册实例获取方式

val appModule = module {

    // single instance of HelloRepository
    single<HelloRepository> { HelloRepositoryImpl() }

    // Simple Presenter Factory
    factory { MySimplePresenter(get()) }
}
复制代码
startKoin {
    ...
    // use modules
    modules(myAppModules)
}
复制代码

module 有详细文档。至于实例的生命周期和作用域,肯定靠modulestartKoin注册方式维护。

ViewModel

class MyViewModel(val repo : HelloRepository) : ViewModel() {...}
复制代码
val appModule = module {
    // single instance of HelloRepository
    single<HelloRepository> { HelloRepositoryImpl() }

    // MyViewModel ViewModel
    viewModel { MyViewModel(get()) }
}
复制代码

由于注入方式相当于手动维护容器,所以ViewModel也需要注册获取方式。

class MyViewModelActivity : AppCompatActivity() {   
    val myViewModel: MyViewModel by viewModel()
}
复制代码

调用Koin库里的viewModel函数, 获取实例。

~λ:

其他详细内容可看文档和源码。两个库各有优劣,相比Koin, Hilt拓展基类,生成代码的方式可能更舒服。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享