Hilt 工作原理 | MAD Skills
Hilt
https://dagger.dev/hilt/
如果您更喜欢通过视频了解此内容,请在此处查看:
所涉主题
多种 Hilt 注解协同工作并生成代码的方式。 当 Hilt 配合 Gradle 使用,Hilt Gradle 插件如何在幕后工作以改善整体体验。
多种 Hilt 注解协同工作并生成代码的方式
@AndroidEntryPoint
https://dagger.dev/api/latest/dagger/hilt/android/AndroidEntryPoint.html@InstallIn
https://dagger.dev/api/latest/dagger/hilt/InstallIn.html@HiltAndroidApp
https://dagger.dev/api/latest/dagger/hilt/android/HiltAndroidApp.html
@AndroidEntryPoint
AndroidEntryPoint 在您的 Android 类中启用字段注入,例如 Activity、Fragment、View 以及 Service。
AndroidEntryPoint
https://dagger.dev/api/latest/dagger/hilt/android/AndroidEntryPoint.html
@AndroidEntryPoint
class PlayActivity : AppCompatActivity() {
@Inject lateinit var player: MusicPlayer
// ...
}
@AndroidEntryPoint(AppCompatActivity::class)
class PlayActivity : Hilt_PlayActivity() {
@Inject lateinit var player: MusicPlayer
// ...
}
现在,我们看到原始基类 AppCompatActivity 是 AndroidEntryPoint 注解的真实入参。PlayActivity 实际上继承了生成的类 Hilt_PlayActivity,该类由 Hilt 注解处理器生成,并包含所有执行注入操作需要的逻辑。针对上述内容生成的基类,其代码简化示例如下:
@Generated("dagger.hilt.AndroidEntryPointProcessor")
class Hilt_PlayActivity : AppCompatActivity {
override fun onCreate() {
inject()
super.onCreate()
}
private fun inject() {
EntryPoints.get(this, PlayActivity_Injector::class).inject(this as PlayActivity);
}
}
AppCompatActivity
https://developer.android.google.cn/reference/androidx/appcompat/app/AppCompatActivity
在示例中,生成的类继承自 AppCompatActivity。然而,通常情况下生成的类会继承传入 AndroidEntryPoint 注解的类。这使得注入操作可以在任何您需要的基类中执行。
生成类的主要目的是处理注入操作。为了避免字段在注入之前被意外访问,有必要尽可能早地执行注入操作。因此,对于 Activity,注入操作在 onCreate 中被执行。
在 inject 方法中,我们首先需要一个注入器的实例——PlayActivity_Injector。在 Hilt 中,对于 Activity,注入器是一个入口点,我们可以使用 EntryPoints 工具类获得一个注入器的实例。
您可能想到了,PlayActivity_Injector 也是由 Hilt 注解处理器生成的。格式如下:
@Generated("dagger.hilt.AndroidEntryPointProcessor")
@EntryPoint
@InstallIn(ActivityComponent::class)
interface PlayActivity_Injector {
fun inject(activity: PlayActivity)
}
@InstallIn
@Module
@InstallIn(SingletonComponent::class)
object MusicDatabaseModule {
// ...
}
InstallIn
https://dagger.dev/api/latest/dagger/hilt/InstallIn.htmlSingletonComponent
https://dagger.dev/api/latest/dagger/hilt/components/SingletonComponent.html
package hilt_metadata
@Generated("dagger.hilt.InstallInProcessor")
@Metadata(my.database.MusicDatabaseModule::class)
class MusicDatabaseModule_Metadata {}
HiltAndroidApp
@HiltAndroidApp
class MusicApp : Application {
@Inject lateinit var store: MusicStore
}
HiltAndroidApp
https://dagger.dev/api/latest/dagger/hilt/android/HiltAndroidApp.html
然而,HiltAndroidApp 还有另外一个重要的作用——生成 Dagger 组件。
当 Hilt 注解处理器遇到 @HiltAndroidApp 注解时,会在包装类中生成一些列组件,该包装类与 Application 类同名,前缀为 HiltComponents_。如果您之前使用过 Dagger,这些组件就是添加了 @Component 和 @Subcomponent 注解的类,而在 Dagger 中通常需要您手动编写。
为了生成这些组件,Hilt 在上述元数据包中查找所有被添加 @InstallIn 注解的类。添加了 @InstallIn 注解的模块被放置在相应组件声明的模块列表中。添加了 @InstallIn 注解的入口点被放置在声明相应组件的父类型的位置。
这是一篇关于 Hilt 的文章,我们就不详细介绍 Dagger 生成的代码了。如果您有兴趣,详情请查阅:
Ron Shapiro 和 David Baker 的演讲: https://www.youtube.com/watch?v=wCvXe2LsN5o
Dagger codegen 101 的备忘单:
https://medium.com/androiddevelopers/dagger-code-generation-cheat-sheets-6b4fa2da4e7a
Hilt Gradle 插件
Hilt Gradle 插件
https://dagger.dev/hilt/gradle-setup#hilt-gradle-plugin
字节码改写
顾名思义,字节码改写就是改写字节码的过程。与注解处理只能生成新代码不同,字节码改写可以修改现有代码。如果谨慎使用,这将是非常强大的功能。
为了说明我们为何在 Hilt 中使用字节码改写,让我们回到 @AndroidEntryPoint。
@AndroidEntryPoint(AppCompatActivity::class)
class PlayActivity : Hilt_PlayActivity {
override fun onCreate(…) {
val welcome = findViewById(R.id.welcome)
}
}
@AndroidEntryPoint
class PlayActivity : AppCompatActivity { // <-- 无需引用生成的基类
override fun onCreate(…) {
val welcome = findViewById(R.id.welcome)
}
}
由于此语法无需引用生成的基类,所以不会引起 IDE 报错。在字节码改写期间,Hilt Gradle 插件会将您的基类替换为 Hilt_PlayActivity。由于此过程直接操作字节码,对开发者是不可见的。
然而,字节码改写仍有一些缺点:
该插件必须修改底层字节码,而不是源代码,这容易出错。
因为在改写操作时字节码已经被编译,所以问题通常出现在运行时而不是编译时。
改写操作使调试变得复杂,因为当出现问题时,源文件可能并不代表当前正在执行的字节码。
由于这些原因,Hilt 尝试尽可能减少依赖字节码改写。
类路径聚合
在本示例中 :app 依赖一个独立的 Gradle 模块 :database,:app 和 :database 都提供了被 InstallIn 注解的模块。
如您所见,Hilt 会在特定的 hilt_metadata 包下生成元数据,在生成组件时,会用它们查找所有被添加 @InstallIn 注解的模块。
不使用类路径聚合的处理对于单层依赖关系仍然可以正常工作,现在让我们看看当添加另一个 Gradle 模块 :cache 作为 :database 的依赖项时会发生什么。
即使使用 implementation,Hilt Gradle 插件也可以自动从 :app 的传递依赖项中聚合所有的类。
此外,与直接使用 api 相比,Hilt Gradle 插件还具有许多优点。
首先,对比在整个应用中手动使用 api 依赖关系,类路径聚合更不容易出错并且不需要维护。您可以像往常一样简单地使用 implementation,其余的将由 Hilt Gradle 插件处理。
其次,Hilt Gradle 插件仅在应用级别聚合类,因此与使用 api 不同,项目中库的编译不受影响。
最后,类路径聚合为您的依赖项提供了更好的封装,因为不可能在源文件中意外引用这些类,并且它们不会出现在代码补全提示中。
总结
欢迎您通过下方二维码向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的支持!
推荐阅读