什么是依赖注入
依赖注入(di,dependency injection)是一种广泛的编程技术。把依赖(所需对象)传递给其它对象创建,好处是类的耦合更加松散,遵循依赖倒置的原则。
类获取所需对象
class engine {
fun start() {
println("engine start")
}
}
class car {
private val engine: engine = engine()
fun start(){
engine.start()
}
}
car对engine强依赖,如果需要其它类型的engine,需要增加一个新的engine,必须对car进行改动。
依赖注入获取所需对象
interface engine {
fun start()
}
class vengine : engine{
override fun start() {
println("vengine start")
}
}
class wengine : engine{
override fun start() {
println("wengine start")
}
}
class car(private val engine: engine) {
fun start(){
engine.start()
}
}
在构造函数中接收engine对象作为参数,而不是初始化时构造自己的engine对象,这就叫做依赖注入。
依赖注入还有很多其它的方式,如变量的get/set,就一一不介绍了。
安卓中手动实现依赖注入
手动实现依赖注入,就是依赖注入的原理。依赖注入框架会生成同样功能的样板代码。
假设有个登录场景,流程大概是这样:
loginactivity->loginviewmodel->userrepository
class userrepository(
private val localdatasource: userlocaldatasource,
private val remotedatasource: userremotedatasource
)
class userlocaldatasource()
class userremotedatasource(
private val loginservice: loginretrofitservice
)
在需要的位置手动注入
在需要的地方创建依赖,缺点比较明显:
- 大量的样板代码
- 必须需要按照顺序声明依赖
- 复用困难。
/**
* 在loginactivity的oncreate函数里:
*/
//创建userremotedatasource需要的依赖
val retrofit = retrofit.builder()
.base
.build()
.create(loginservice::class.java)
//创建repository需要的依赖
val remotedatasource = userremotedatasource(retrofit)
val localdatasource = userlocaldatasource()
//创建viewmodel需要的依赖
val userrepository = userrepository(localdatasource, remotedatasource)
//创建viewmodel
loginviewmodel = loginviewmodel(userrepository)
使用容器管理依赖
如果想要复用对象,可以创建一个类来初始化所需的依赖。
class appcontainer {
private val retrofit = retrofit.builder()
.base.build()
.create(loginservice::class.java)
private val remotedatasource = userremotedatasource(retrofit)
private val localdatasource = userlocaldatasource()
val userrepository = userrepository(localdatasource, remotedatasource)
}
如果需要在整个app中使用,可以放到application中:
class myapplication : application(){
val appcontainer = appcontainer()
}
/**
* 在loginactivity的oncreate函数里:
*/
val appcontainer = (application as myapplication).appcontainer
loginviewmodel = loginviewmodel(appcontainer.userrepository)
使用容器来管理依赖还是有样板代码,且需要手动为依赖项创建实例对象。
管理依赖项的声明周期
之前把userrepository的生命周期放在了application,在app被关闭前永远不会被释放。如果数据非常大,会导致内存占用过高。
假如,只有在loginactivity需要依赖,其它位置不需要依赖:
- appcontainer 内部需要新增一个logincontainer,用来存放userrepository。
- 在loginactivity:oncreate时手动创建logincontainer并放到appcontainer,ondestory时把appcontainer设置为null,主动释放引用。
根据生命周期来管理依赖项,这样时比较合理的。
dagger实现依赖注入
什么是dagger
dagger是一个依赖注入的库,通过自动生成代码的方式,实现依赖注入。由于是在编译时生成的代码,性能会高于基于反射的方案。dagger生成的代码和手动实现依赖注入生成的代码相似。
- 每次调用函数都重新创建对象
//@inject 注解会告诉dagger,需要注入.
class userrepository @inject constructor(
private val localdatasource: userlocaldatasource,
private val remotedatasource: userremotedatasource
){
init {
println("userrepository created")
}
}
class userlocaldatasource @inject constructor() {
init {
println("userlocaldatasource created")
}
}
class userremotedatasource @inject constructor(){
init {
println("userremotedatasource created")
}
}
//daggerapplicationgraph.create() 会创建新对象
private val applicationgraph:applicationgraph = daggerapplicationgraph.create()
//applicationgraph.repository() 会创建新对象
private val repository1 = applicationgraph.repository()
private val repository2 = applicationgraph.repository()
/**
输出如下:
userlocaldatasource created
userremotedatasource created
userrepository created
userlocaldatasource created
userremotedatasource created
userrepository created
*/
- 首次创建对象,之后全局复用这个单例对象.
@singleton
class userrepository @inject constructor(
private val localdatasource: userlocaldatasource,
private val remotedatasource: userremotedatasource
){
init {
println("userrepository created")
}
}
class userlocaldatasource @inject constructor() {
init {
println("userlocaldatasource created")
}
}
class userremotedatasource @inject constructor(){
init {
println("userremotedatasource created")
}
}
/**
* @component 注解,用于 interface
* dagger会生成一个对应的类,以dagger开头,applicationgraph就是daggerapplicationgraph
* 调用函数会返回对应的对象, dagger会自动添加依赖
* @singleton 注解,用于标识为全局单例
*/
@singleton
@component
interface applicationgraph {
fun repository(): userrepository
}
@singleton
@component
interface logingraph {
fun repository(): userrepository
}
class one{
//daggerapplicationgraph.create() 会创建新对象
private val applicationgraph:applicationgraph = daggerapplicationgraph.create()
//applicationgraph.repository() 会创建新对象
private val repository1 = applicationgraph.repository()
private val repository2 = applicationgraph.repository()
init {
println("one created")
}
}
class two{
private val logingraph:logingraph = daggerlogingraph.create()
private val repository1 = logingraph.repository()
private val repository2 = logingraph.repository()
init {
println("two created")
}
}
/**
one 和 two 使用同一个对象,因为@singleton注解是全局单例
输出如下:
userlocaldatasource created
userremotedatasource created
userrepository created
one created
userlocaldatasource created
userremotedatasource created
userrepository created
two created
*/
在安卓中使用dagger
- 对activity中的字段进行注入
/**
* 为了演示方便,这里没有继承viewmodel
*/
class loginviewmodel @inject constructor(
private val userrepository: userrepository
) {
init {
println("loginviewmodel created")
}
}
/**
* activity 是用安卓系统实例化的,所以无法被dagger创建
* 初始化的代码需要放在oncreate方法中
* 使用手动调用inject的方式,对字段进行注入,需要被注入的字段必须有@inject注解
*/
class loginactivity : appcompatactivity() {
@inject lateinit var loginviewmodel: loginviewmodel
override fun oncreate(savedinstancestate: bundle?) {
//调用inject,告诉dagger,可以对当前对象的@inject字段进行注入了
(applicationcontext as myapplication).applicationgraph.inject(this)
//调用完成,loginviewmodel可以使用了
super.oncreate(savedinstancestate)
setcontentview(r.layout.activity_login)
}
}
/**
* @component 注解,用于 interface * dagger会生成一个对应的类
* 调用函数会返回对应的对象, dagger会自动添加依赖
* @singleton 注解,用于标识为全局单例
* inject 调用函数手动注入带有@inject注解的字段,函数名称是任意的,参数是需要注入的对象.
*/
@singleton
@component
interface applicationgraph {
fun repository(): userrepository
fun inject(activity: loginactivity)
}
class myapplication : application() {
val applicationgraph: applicationgraph = daggerapplicationgraph.create()
}
- 主动告知如何提供实例
//增加一个参数
class userremotedatasource @inject constructor(private val loginservice: loginservice){
init {
println("userremotedatasource created")
}
fun login(){
loginservice.login()
}
}
//loginservice 只能通过builder.create()创建
interface loginservice {
private class loginserviceimpl : loginservice{
init {
println("loginserviceimpl created")
}
}
fun login() = println("login")
class builder{
fun create(): loginservice {
return loginserviceimpl()
}
}
}
/**
* @disableinstallincheck 用于屏蔽绑定生命周期,这个是hilt的内容.
* @module 是dagger的注解,用来告诉dagger可以提供实例对象.
* @provides 用于@module注解内,提供对应类型的实例对象,函数名任意.也可以添加@singleton注解创建单例.
*/
@disableinstallincheck
@module
class loginmodule {
@provides
fun providerloginservice(): loginservice{
return loginservice.builder().create()
}
}
/**
* @component 注解,用于 interface * dagger会生成一个对应的类,以dagger开头,applicationgraph就是daggerapplicationgraph
* modules 参数用来指定对象该如何提供,必须带有@module注解
* 调用函数会返回对应的对象, dagger会自动添加依赖
* @singleton 注解,用于标识为全局单例
* inject 调用函数手动注入带有@inject注解的字段,函数名称是任意的,参数是需要注入的对象.
*/
@singleton
@component(modules = [loginmodule::class])
interface applicationgraph {
fun repository(): userrepository
fun inject(activity: loginactivity)
}
- 子组件和作用域,限定作用域为生命周期
class myapplication : application() {
val applicationgraph: applicationcomponent = daggerapplicationcomponent.create()
}
@singleton
@component(modules = [loginmodule::class, subcomponent::class])
interface applicationcomponent {
fun logincomponent(): logincomponent.factory
}
@scope
@retention(value = annotationretention.runtime)
annotation class activityscope
@activityscope
class loginviewmodel @inject constructor(
private val userrepository: userrepository,
) {
init {
println("loginviewmodel created")
}
fun login(){
userrepository.login()
}
}
@activityscope
@subcomponent
interface logincomponent {
@subcomponent.factory
interface factory{
fun create(): logincomponent
}
fun inject(activity: loginactivity)
fun inject(fragment: loginfragment)
fun repository(): userrepository
}
@disableinstallincheck
@module
class loginmodule {
@singleton
@provides fun providerloginservice(): loginservice{
return loginservice.builder().create()
}
}
@activityscope
class userrepository @inject constructor(
private val localdatasource: userlocaldatasource,
private val remotedatasource: userremotedatasource
){
init {
println("userrepository created")
}
fun login(){
remotedatasource.login()
}
}
class userremotedatasource @inject constructor(
private val loginservice: loginservice,
private val loginservice2: loginservice,
){
init {
println("userremotedatasource created")
}
fun login(){
loginservice.login()
}
}
class userlocaldatasource @inject constructor() {
init {
println("userlocaldatasource created")
}
}
class loginactivity : appcompatactivity() {
lateinit var logincomponent: logincomponent
@inject lateinit var loginviewmodel: loginviewmodel
override fun oncreate(savedinstancestate: bundle?) {
logincomponent = (application as myapplication).applicationgraph.logincomponent().create()
logincomponent.inject(this)
super.oncreate(savedinstancestate)
setcontentview(r.layout.activity_login)
findviewbyid
通过限定作用域的方式,把依赖注入和生命周期绑定,activity和activity中的fragment使用同一个viewmodel。
dagger虽然可以实现精细的依赖注入,但是使用起来非常繁琐。
hilt实现依赖注入
什么是hilt
hilt是基于dagger构建的用于安卓的依赖注入库,简化在安卓上实现依赖注入。
把依赖项注入安卓类
hilt可以很方便的注入到安卓类中
比如把viewmodel
class userrepository @inject constructor(
private val localdatasource: userlocaldatasource,
private val remotedatasource: userremotedatasource
){
init {
println("userrepository created")
}
fun login(){
remotedatasource.login()
}
}
class userlocaldatasource @inject constructor() {
init {
println("userlocaldatasource created")
}
}
class userremotedatasource @inject constructor(){
init {
println("userremotedatasource created")
}
fun login(){
println("login")
}
}
class loginviewmodel @inject constructor(
private val userrepository: userrepository,
) {
init {
println("loginviewmodel created")
}
fun login(){
userrepository.login()
}
}
/**
* @androidentrypoint 注解,可以被hilt注入
*/
@androidentrypoint
class loginactivity : appcompatactivity() {
@inject lateinit var viewmodel: loginviewmodel
override fun oncreate(savedinstancestate: bundle?) {
super.oncreate(savedinstancestate)
setcontentview(r.layout.activity_login)
findviewbyid
hilt模块
模块的bind
可以被直接构造的接口实现可以通过bind注入
class userremotedatasource @inject constructor(
private val analyticsservice: analyticsservice
){
init {
println("userremotedatasource created")
}
fun login(){
println("login")
analyticsservice.logevent("login")
}
}
interface analyticsservice {
fun logevent(eventname: string)
}
class analyticsserviceimpl @inject constructor(
@applicationcontext val context: context
) : analyticsservice {
override fun logevent(eventname: string) {
println("context: $context logevent: $eventname")
}
}
@module
@installin(activitycomponent::class)
abstract class analyticsservicemodule {
@binds
abstract fun bindanalyticsservice(analyticsservice: analyticsserviceimpl): analyticsservice
}
模块的provider
无法被直接构造的接口实现可以通过provider注入
@module
@installin(activitycomponent::class)
class loginservicemodule {
@provides
fun provideloginservice(): loginservice {
return loginserviceimpl.builder().build()
}
}
interface loginservice {
fun login()
}
class loginserviceimpl private constructor(): loginservice {
class builder {
fun build(): loginservice {
return loginserviceimpl()
}
}
init {
println("loginserviceimpl created")
}
override fun login() {
println("login")
}
}
class userremotedatasource @inject constructor(
private val analyticsservice: analyticsservice,
private val loginservice: loginservice
){
init {
println("userremotedatasource created")
}
fun login(){
loginservice.login()
analyticsservice.logevent("login")
}
}
同一类型提供多个绑定
provides如果不带限定标签,只会返回一种类型的实现。
可以通过限定标签来区分,返回对应的实现。
hilt限定符默认提供了 @applicationcontex 和 @activitycontext,用来提供两种不同类型的context
@qualifier
@retention(annotationretention.binary)
annotation class debuglog
@qualifier
@retention(annotationretention.binary)
annotation class errorlog
@module
@installin(singletoncomponent::class)
object logservicemodule {
@debuglog
@provides
fun providedebuglogger(): logservice {
return logservicedebugimpl()
}
@errorlog
@provides
fun provideerrorlogger(): logservice {
return logserviceerrorimpl()
}
}
@module
@installin(activitycomponent::class)
class loginservicemodule {
@provides
fun provideloginservice(@debuglog logservice: logservice): loginservice {
return loginserviceimpl.builder().build(logservice)
}
}
class loginserviceimpl private constructor(val logservice:logservice): loginservice {
class builder {
fun build(logservice:logservice): loginservice {
return loginserviceimpl(logservice)
}
}
init {
logservice.log("loginserviceimpl created")
}
override fun login() {
logservice.log("login")
}
}
class analyticsserviceimpl @inject constructor(
@applicationcontext val context: context,
@errorlog val logservice: logservice
) : analyticsservice {
override fun logevent(eventname: string) {
logservice.log("context: $context logevent: $eventname")
}
}
hilt为安卓类生成的组件
组件生命周期和作用域
hilt组件 | 创建时机 | 销毁时机 | 作用域 | 备注 |
---|---|---|---|---|
singletoncomponent | application#oncreate() | application被销毁 | @singleton | 相当于是单例的 |
activitycomponent | activity#oncreate() | activity#ondestroy() | @activityscoped | 会随着生命周期注入 |
activityretainedcomponent | 首次activity#oncreate() | 最后一次activity#ondestroy() | @activityretainedscoped | fragment的viewmodel会随着fragment回收,但activityretainedcomponent只会随着activity回收,比viewmodel生命周期更长。 |
viewmodelcomponent | viewmodel 已创建 | viewmodel 已销毁 | @viewmodelscoped | 和viewmodel的生命周期相同 |
viewcomponent | view#super() | view 已销毁 | @viewscoped | 和view的生命周期相同 |
viewwithfragmentcomponent | view#super() | view的拥有者被销毁 | @viewscoped带有 @withfragmentbindings注解的view | 比如fragment导航离开屏幕,fragment还在,但view被销毁时仍然保留。 |
fragmentcomponent | fragment#onattach() | fragment#ondestroy() | @fragmentscoped | 和fragment的生命周期相同 |
servicecomponent | service#oncreate() | service#ondestroy() | @servicescoped | 和service的生命周期相同 |
组件默认绑定
可以使用model安装到默认绑定,实现注入。比如上面提到的"同一类型提供多个绑定"
安卓组件 | 默认绑定 |
---|---|
singletoncomponent | application |
activityretainedcomponent | application |
viewmodelcomponent | savedstatehandle |
activitycomponent | application、activity |
fragmentcomponent | application、activity、fragment |
viewcomponent | application、activity、view |
viewwithfragmentcomponent | application、activity、fragment、view |
servicecomponent | application、service |
在hilt不支持的类中注入依赖项
使用@entrypoint让任意接口可以被注入.
使用@entrypointaccessors获取被注入的对象.
因为是singletoncomponent,所以要使用application的context 。如果是activitycomponent就需要使用activity的context。
class examplecontentprovider : contentprovider() {
@entrypoint
@installin(singletoncomponent::class)
interface examplecontentproviderentrypoint {
fun analyticsservice(): analyticsservice
}
fun dosomething(){
val appcontext = context?.applicationcontext ?: throw illegalstateexception()
val hiltentrypoint = entrypointaccessors.fromapplication(appcontext, examplecontentproviderentrypoint::class.java)
val analyticsservice = hiltentrypoint.analyticsservice()
}
}