Jetpack Hilt有哪些改善又有哪些限制?

简介: Jetpack Hilt有哪些改善又有哪些限制?

Hilt以Android专属DI框架的身份继续完善了Jetpack的布局。它在前辈Dagger2的基础上做了诸多改善,同时又存在很多限制,本文将逐一回答。

Hilt的由来

先来看下官方对于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描述这种注入组件

20200308234636925.jpg

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

照例使用OMDB API演示下Hilt的使用。


总体上通过@ViewModelInject向ViewModel注入Repository,Repository依赖RemoteData和LocalData。


RemoteData通过NetworkModule提供单例的Retrofit接口,向OMDB发出搜索电影的请求

LocalData依赖AnalysisModule提供的AnalysisService接口将选中的电影记录进RoomModule提供的Room Database中

地址

Hilt Demo

框图 & 截图

20200308234636925.jpg

1832b220aa754cd18c504acc7686a560.png

Hilt的限制

Hilt的改善必然伴随着一些限制,遵照了这些限制Hilt才能发挥它的优势。

@AndroidEntryPoint 的限制

1672146946620.png

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搭配使用。

4. 但不支持Retained Fragment

调用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. 框架类的基类可统一添加@AndroidEntryPoint,但抽象类则不需要

框架类的基类一次性添加了这个注解各Hilt便可生成统一的组件向各子类注入依赖,每个子类不用额外添加。但基类是抽象类的话则不可以使用该注解,每个子类仍然需要各自添加。否则会发生编译错误。

Android框架类的注入限制

Android框架类的注入限制 限制内容
注入方式 只能是字段注入
字段的修饰符 不能为private
BrocastReceiver 不会生成单独的组件
View 默认绑定到ActivityComponent
ContentProvider 无法直接使用@AndroidEntryPoint注解

1. 向Activity等框架类注入实例的话需要采用字段注入

Application、Activity等Android特有类的实例由系统创建,无法通过构造函数注入,只能采用字段注入的方式。


2. 注入的字段不能为私有的


不小心将注入的字段声明成private也没关系,编译期会向你发出提醒。


Execution failed for task ‘:app:kaptDebugKotlin’. A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptExecution.


3 无法为BrocastReceiver生成独立的Component组件


不同于Activity和Fragment,Hilt直接从ApplicationComponent组件向BrocastReceiver注入依赖。从原理上讲BrocastReceiver实例的创建和回调都是由ApplicationThread直接调度的,所以设计成这样?


4. View里进行的字段注入默认将绑定到ActivityComponent组件


如果View来自于Fragment,在@AndroidEntryPoint以外可以添加@WithFragmentBindings将注入精准地绑定到FragmentComponent组件。


5. ContentProvider不能直接使用Hilt注解


四大组件中只有ContentProvider实例的创建先于Application,可是整个应用的注入起点是Application组件,所以无法直接为ContentProvider提供注入的支持。


但如果ContentProvider确有注入需求的话,需要自行定义@EntryPoint注释的接口并通过@InstallIn指定依赖项需要绑定到的组件,具体的使用过程和跟Dagger2很类似。

生命周期的注意

生命周期的注意 限制内容
默认的绑定 每次注入都会创建新的实例
ActivityRetainedComponent 最后一次destroy时销毁

1. 默认的绑定在每次注入都会创建新的实例。


基于内存开销的考虑,默认情况下都绑定都没有限定作用域,即注入每个依赖的地方都会提供一个新的实例。如果被依赖的实例具有明确的使用场景或范围,可以为这个注入指定作用域,比如整个应用周期内保留一份实例的@Singleton作用域。


2. ActivityRetainedComponent组件在第一次调用onCreate()时创建,在最后一次调用Activity#onDestroy()时销毁。


ActivityRetainedComponent组件在Configuration Change导致Activity重绘后仍然存在,生命周期长于ActivityComponent组件。可以添加@ActivityRetainedScope注解来绑定这个组件。


需要提醒的一点是如果采用@ViewModelInject提供ViewModel的依赖,那么无需再使用@ActivityRetainedScope来注释依赖的实例,因为它已经被自动绑定到了ActivityRetainedComponent组件。

ViewModel注入的注意点

使用@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的诸多优点。


高度封装了框架类的注入免于手动初始化组件

预设了充分的作用域和方便的Context注入

针对Jetpack组件的支持

当我们需要在Android App上导入DI的话可以优先考虑它。

但Hilt也不是完美的,除了上述罗列的一堆限制以外,还存在天生的劣势,比如无法应用在动态功能模块项目当中。依据实际需要做出抉择,是小而美的Hilt,还是大而强的Dagger2。

本文DMEO

Hilt Demo

参考资料

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

相关文章
Jetpack Compose中ViewModel、Flow、Hilt、Coil的使用
Jetpack Compose中ViewModel、Flow、Hilt、Coil的使用
1163 0
Jetpack Compose中ViewModel、Flow、Hilt、Coil的使用
|
Android开发 开发者 容器
上手指南 | Jetpack Hilt 依赖注入框架
上手指南 | Jetpack Hilt 依赖注入框架
上手指南 | Jetpack Hilt 依赖注入框架
|
测试技术 数据库 Android开发
Android Jetpack 浅析Hilt依赖注入
首先,某个类的成员变量称为依赖,如若此变量想要实例化引用其类的方法,可以通过构造函数传参或者通过某个方法获取对象,此等通过外部方法获取对象实例的称为依赖注入;而依赖注入又可以简单分为`手动注入`和`自动注入`两种方式;`Hilt`就是基于Dagger进行`场景化优化`的一个依赖注入库,Hilt是Google专门为Android平台打造的一个依赖注入库,在使用上极大程度进行啦简化(与dagger相比)
379 1
|
存储 前端开发 Java
Jetpack 系列(10)—— 从 Dagger2 到 Hilt 玩转依赖注入(一)
Jetpack 系列(10)—— 从 Dagger2 到 Hilt 玩转依赖注入(一)
403 0
Jetpack 系列(10)—— 从 Dagger2 到 Hilt 玩转依赖注入(一)
DHL
|
算法 安全 Java
Jetpack 新成员 Hilt 与 Dagger 大不同(三)落地篇
在 Google 的 Hilt 文档中 Dependency injection with Hilt 只是简单的告诉我们 Hilt 是 Android 的依赖注入库,它减少了在项目中进行手动依赖,Hilt 是基于 Dagger 基础上进行开发的,为常见的 Android 类提供容器并自动管理它们的生命周期等等。
DHL
495 0
Jetpack 新成员 Hilt 与 Dagger 大不同(三)落地篇
DHL
|
存储 算法 安全
Jetpack 新成员 Hilt 实践之 App Startup(二)进阶篇
Hilt 是基于 Dagger 基础上进行开发的,如果了解 Dagger 朋友们,应该会感觉它们很像,但是与 Dagger 不同的是, Hilt 集成了 Jetpack 库和 Android 框架类,并删除了大部分模板代码,让开发者只需要关注如何进行绑定,而不需要管理所有 Dagger 配置的问题。
DHL
402 0
Jetpack 新成员 Hilt 实践之 App Startup(二)进阶篇
DHL
|
算法 安全 Java
Jetpack 新成员 Hilt 实践(一)启程过坑记
这篇文章主要来分析一下 Hilt,花了好几天时间梳理了一下 官方 Hilt 文档,Hilt 的知识点有点多,将会分为三篇文章结合实际案例来完成,每篇文章都会有详细的使用的案例。
DHL
383 0
Jetpack 新成员 Hilt 实践(一)启程过坑记
|
3月前
|
存储 安全 Android开发
构建高效的Android应用:Kotlin与Jetpack的结合
【5月更文挑战第31天】 在移动开发的世界中,Android 平台因其开放性和广泛的用户基础而备受开发者青睐。随着技术的进步和用户需求的不断升级,开发一个高效、流畅且易于维护的 Android 应用变得愈发重要。本文将探讨如何通过结合现代编程语言 Kotlin 和 Android Jetpack 组件来提升 Android 应用的性能和可维护性。我们将深入分析 Kotlin 语言的优势,探索 Jetpack 组件的核心功能,并通过实例演示如何在实际项目中应用这些技术。
|
2月前
|
数据管理 API 数据库
探索Android Jetpack:现代安卓开发的利器
Android Jetpack是谷歌为简化和优化安卓应用开发而推出的一套高级组件库。本文深入探讨了Jetpack的主要构成及其在应用开发中的实际运用,展示了如何通过使用这些工具来提升开发效率和应用性能。
|
22天前
|
存储 数据库 Android开发
🔥Android Jetpack全解析!拥抱Google官方库,让你的开发之旅更加顺畅无阻!🚀
【7月更文挑战第28天】在Android开发中追求高效稳定的路径?Android Jetpack作为Google官方库集合,是你的理想选择。它包含多个独立又协同工作的库,覆盖UI到安全性等多个领域,旨在减少样板代码,提高开发效率与应用质量。Jetpack核心组件如LiveData、ViewModel、Room等简化了数据绑定、状态保存及数据库操作。引入Jetpack只需在`build.gradle`中添加依赖。例如,使用Room进行数据库操作变得异常简单,从定义实体到实现CRUD操作,一切尽在掌握之中。拥抱Jetpack,提升开发效率,构建高质量应用!
40 4