Hilt以Android专属DI框架的身份继续完善了Jetpack的布局。它在前辈Dagger2的基础上做了诸多改善,同时又存在很多限制,本文将逐一回答。
Hilt的由来
Hilt provides a standard way to incorporate Dagger dependency injection into an Android application.To simplify Dagger-related infrastructure for Android apps. To create a standard set of components and scopes to ease setup, readability/understanding, and code sharing between apps. To provide an easy way to provision different bindings to various build types (e.g. testing, debug, or release).正如描述的那样,Hilt是在Dagger(Dagger2)的基础上专为Android App打造的依赖注入方案。它在保留Dagger2的编译时注入的性能优势前提下,简化了注解的使用。同时针对Android框架类进行了优化。在展开Hilt的讲述之前先来简单回顾下依赖注入的各个角色和流程。依赖注入流程
- 依赖的需求方,通过构造参数或字段依赖其他实例的角色,一般使用@Inject描述这种需求
- 依赖的提供方,对被依赖的实例提供实现的角色,比如使用@Provides描述这种来源
- 依赖的注入方,将提供方的实现注入到需求方的角色,比如使用@Component描述这种注入组件
Hilt的改善
定义应用组件
给Application添加@HiltAndroidApp注解即可告知Hilt生成应用级别的组件,自动实现了依赖注入的起点,免去了Dagger2的手动调用。@HiltAndroidApp
class MyApplication : Application() {...}
定义Android框架类组件
@AndroidEntryPoint注解用来为Activity,Fragment,Service等Android框架类生成Hilt组件,省去了定义相应SubComponent的模板处理。@AndroidEntryPoint
open class BaseActivity() : AppCompatActivity() {...}
绑定生命周期
@InstallIn注解可以告知Hilt每个模块将用在或绑定到哪个Android类中。比如指定的value为ApplicationComponent的话将表明该模块在整个应用周期内只会实例化一份,即单例。其他的还有绑定到Activity生命周期的ActivityComponent。@Module
@InstallIn(ApplicationComponent::class)
class NetworkModule {...}
预设作用域
@Singleton和@ActivityRetainedScoped等注解用以声明该注入的作用范围。比如Activity因为Configuration Change重绘了但@ActivityRetainedScoped注释的依赖的并不会重新创建。@ActivityRetainedScoped
class MovieAdapter @Inject constructor() { ... }
@AndroidEntryPoint
class DemoActivity : AppCompatActivity() {
@Inject lateinit var movieAdapter: MovieAdapter
...
}
注入Context
通过@ApplicationContext 和@ActivityContext注解等可以快速注入Context实例,省得我们自己提供Context的实现。class MovieAdapter @Inject constructor(@ActivityContext private val context: Context)
: RecyclerView.Adapter<RecyclerView.ViewHolder>() {...}
Jetpack组件的支持
Hilt实现了一些扩展帮助我们注入ViewModel和WorkManager的依赖。比如@ViewModelInject注解就可以告知Hilt此处需要注入ViewModel实例。class MovieViewModel @ViewModelInject constructor(private val repository: Repository,
var movieAdapter: MovieAdapter
) : ViewModel() {...}
Hilt和Dagger2一样支持@Qualifier定义多类型注入的注解。@Inject,@Provides以及@Binds的使用也没有差别,不再赘述。感兴趣的可以查询官方文档获得更详尽的介绍。https://developer.android.google.cn/training/dependency-injection/hilt-android实战DEMO
总体上通过@ViewModelInject向ViewModel注入Repository,Repository依赖RemoteData和LocalData。- RemoteData通过NetworkModule提供单例的Retrofit接口,向OMDB发出搜索电影的请求
- LocalData依赖AnalysisModule提供的AnalysisService接口将选中的电影记录进RoomModule提供的Room Database中
地址
https://github.com/ellisonchan/JetpackDemo框图 & 截图
Hilt的限制
Hilt的改善必然伴随着一些限制,遵照了这些限制Hilt才能发挥它的优势。1. 框架类存在依附类的话同样需要添加@AndroidEntryPoint假使只给Framgent添加了@AndroidEntryPoint但所属Activity没有添加的话,启动Fragment的时候会发生如下异常。Hilt Fragments must be attached to an @AndroidEntryPoint Activity.原理在于Fragment在attach的时候后会确保Fragment依附的Activity实现了GeneratedComponentManager接口,即是否添加了@AndroidEntryPoint注解。2. 仅支持扩展自ComponentActivity的Activity如果@AndroidEntryPoint注释的Activity并非ComponentActivity的子类,那么在编译阶段就无法通过。Activities annotated with @AndroidEntryPoint must be a subclass of androidx.activity.ComponentActivity.@AndroidEntryPoint注释的Activity是支持ViewModel注入的,而ViewModel的实现完全依赖于ComponentActivity,所以作此限制很有必要。毕竟都已经用Jetpack全家桶了,Activity这么重要的组件还用老的也太没决心了。3. 仅支持扩展自androidx.Fragment包的Fragment和Activity的限制一样,采用AOSP的Fragment的话,编译阶段就不会让你通过。@AndroidEntryPoint base class must extend ComponentActivity, (support) Fragment, View, Service, or BroadcastReceiver.事实上AOSP的Fragment自Android 9 就已Deprecated,在于其缺乏很多特性,包括Lifecycle,ViewMode等。进而无法和ComponentActivity搭配使用。调用setRetainInstance(true)的Fragment就是Retained Fragment。意味着Configuration Change导致持有的view销毁了但Fragment本身没有销毁。如果不小心将Retained Fragment添加了@AndroidEntryPoint,那么在横竖屏切换导致画面重绘的时候可能会发生如下异常。onAttach called multiple times with different Context! Hilt Fragments should not be retained.Configuration Change导致Activity重建,但Retained Fragment实例保留了下来。意味着同一个Fragment实例要被重复注入依赖,这并不合理。所以Hilt在第一次注入Fragment前会依据依附的Activity创建一个Context实例,后续将检查这个实例是否为空来确保每次注入的Fragment都是新的。5. 向Activity等框架类注入实例的话需要采用字段注入Application、Activity等Android特有类的实例由系统创建,无法通过构造函数注入,只能采用字段注入的方式。不小心将注入的字段声明成private也没关系,编译期会向你发出提醒。Execution failed for task ':app:kaptDebugKotlin'. A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptExecution.7. 框架类的基类可统一添加@AndroidEntryPoint,但抽象类则不需要框架类的基类一次性添加了这个注解各Hilt便可生成统一的组件向各子类注入依赖,每个子类不用额外添加。但基类是抽象类的话则不可以使用该注解,每个子类仍然需要各自添加。否则会发生编译错误。8. 无法为BrocastReceiver生成独立的Component组件不同于Activity和Fragment,Hilt直接从ApplicationComponent组件向BrocastReceiver注入依赖。从原理上讲BrocastReceiver实例的创建和回调都是由ApplicationThread直接调度的,所以设计成这样?9. ActivityRetainedComponent组件在第一次调用onCreate()时创建,在最后一次调用Activity#onDestroy()时销毁ActivityRetainedComponent组件在Configuration Change导致Activity重绘后仍然存在,生命周期长于ActivityComponent组件。可以添加@ActivityRetainedScope注解来绑定这个组件。需要提醒的一点是如果采用@ViewModelInject提供ViewModel的依赖,那么无需再使用@ActivityRetainedScope来注释依赖的实例,因为它已经被自动绑定到了ActivityRetainedComponent组件。基于内存开销的考虑,默认情况下都绑定都没有限定作用域,即注入每个依赖的地方都会提供一个新的实例。如果被依赖的实例具有明确的使用场景或范围,可以为这个注入指定作用域,比如整个应用周期内保留一份实例的@Singleton作用域。11. View里进行的字段注入默认将绑定到ActivityComponent组件如果View来自于Fragment,在@AndroidEntryPoint以外可以添加@WithFragmentBindings将注入精准地绑定到FragmentComponent组件。12. ContentProvider不能直接使用Hilt注解四大组件中只有ContentProvider实例的创建先于Application,可是整个应用的注入起点是Application组件,所以无法直接为ContentProvider提供注入的支持。但如果ContentProvider确有注入需求的话,需要自行定义@EntryPoint注释的接口并通过@InstallIn指定依赖项需要绑定到的组件,具体的使用过程和跟Dagger2很类似。13. 使用@ViewModelInject向ViewModel注入依赖的特别注意- 如果使用viewModels()的KTX获取ViewModel实例的话,可能会遇到找不到该KTX的问题,这时候注意下gradle文件有没有导入fragment-ktx的依赖
- 如果运行失败并提示viewmodel不包含默认构造函数,一定记得检查下hilt-compiler的注释处理器有没有在gradle文件里声明
- 如果编译发生如下的错误,需记得在gradle的kotlinOptions里声明jvm的版本为1.8
Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option.结语
相较于Dagger2的改善足以窥见Hilt的诸多优点。- 针对Jetpack组件的支持
当我们需要在Android App上导入DI的话可以优先考虑它。
但Hilt也不是完美的,除了上述罗列的一堆限制以外,还存在天生的劣势,比如无法应用在动态功能模块项目当中。依据实际需要做出抉择,是小而美的Hilt,还是大而强的Dagger2。本文DMEO
https://github.com/ellisonchan/JetpackDemo参考资料
https://developer.android.google.cn/training/dependency-injection/hilt-android
https://developer.android.google.cn/training/dependency-injection/hilt-jetpack
https://mp.weixin.qq.com/s/VKyyNqAPFnlclGKnIbisAw
https://guolin.blog.csdn.net/article/details/109787732
推荐阅读
Dagger2和它在SystemUI上的应用
除了SQLite一定要试试Room