Jetpack Hilt 框架的基本使用

这篇具有很好参考价值的文章主要介绍了Jetpack Hilt 框架的基本使用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

什么是 Hilt?

Hilt 是一个功能强大、用法简单的依赖注入框架,于 2020 年加入到 Jetpack 家族中。它是 Android 团队联系了 Dagger2 团队,一起开发出来的一个专门面向 Android 的依赖注入框架。相比于 Dagger2,Hilt 最明显的特征就是简单,并且提供了 Android 专属的 API。

在项目中引入 Hilt

此部分以使用了 Java 17 的 Jetpack Compose 新项目为例,开发工具使用 Android Studio 2023.1.1 Canary 版本。信息截止 2023 年 5 月。

第一步,打开 gradle/libs.versions.toml 文件,加入 Hilt 的 Gradle 插件相关配置:

[versions]
hilt = "2.46.1"

[plugins]
hiltAndroid = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }

再打开项目的 build.gradle.kts 文件,引入插件:

plugins {
    alias(libs.plugins.hiltAndroid) apply false
}

第二步,在 libs.versions.toml 中加入 Hilt 的插件和依赖库:

[libraries]
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }

由于 Hilt 基于编译时注解实现,需要添加 kotlin-kapt 插件。在 app 的 build.gradle.kts 文件中再加入如下配置:

plugins {
    kotlin("kapt")
}

android {
    compileOptions {
        // 这里设置为 Java 8 或以上即可
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
}

dependencies {
    implementation(libs.hilt.android)
    kapt(libs.hilt.compiler)
}

现在,Hilt 已被成功引入到项目中。

Hilt 的基本用法

准备工作

使用 Hilt 时,必须自定义一个 Application 类,否则 Hilt 将无法正常工作。自定义的 Application 类中可以不写任何代码,但必须要加上 @HiltAndroidApp 注解。

@HiltAndroidApp
class MyApplication : Application() {
}

接下来将 MyApplication 注册到 AndroidManifest.xml 中:

<application
    android:name=".MainApplication">
</application>

准备工作到此已完成,接下来的任务是根据具体的业务逻辑使用 Hilt 进行依赖注入。

入口点

Hilt 简化了 Dagger2 的操作,使我们无需使用 @Component 注解编写桥接层逻辑,同时也限制了注入功能只能从几个 Android 固定的入口点开始:Application、Activity、Fragment、View、Service、BroadcastReceiver。
其中,只有 Application 入口点使用 @HiltAndroidApp 注解声明,其他所有入口点均使用 @AndroidEntryPoint 注解声明。例如,若希望在 Activity 中进行依赖注入,只需这样声明:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {        
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

不带参数的依赖注入

尝试定义一个类,在其构造函数上声明 @Inject 注解,如下:

class MusicPlayer() @Inject constructor() {
    fun init() {
        Log.d("MusicPlayer", "init")
    }
}

在 Activity 中注入,即可成功调用上面编写的 init() 方法:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var musicPlayer: MusicPlayer

    override fun onCreate(savedInstanceState: Bundle?) {        
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        musicPlayer.init()
    }
}

带参数的依赖注入

在上面的 MusicPlayer 类构造函数中加入一个 AudioDriver 参数,代表播放器组件依赖的系统音频驱动,如下所示:

class MusicPlayer() @Inject constructor(val audioDriver: AudioDriver) {
    fun init() {
        Log.d("MusicPlayer", "init, audioDriver=$audioDriver")
    }
}

声明 AudioDriver 类时,也为其构造函数加上 @Inject 注解:

class AudioDriver @Inject constructor() {}

不需要再修改任何代码,即可成功调用 init() 方法,并成功打印 audioDriver 的 hashCode。

接口的依赖注入

定义一个 IDecoder 接口,代表播放音频时必备的音频解码器。接口中有两个待实现方法,分别用于创建解码器和销毁解码器、释放内存:

interface IDecoder {
    fun create()
    fun destroy()
}

实现用于解码 WAV 文件的 WavDecoder,在构造函数中加上 @Inject 注解:

class WavDecoder @Inject constructor() : IDecoder {
    override fun create() {
        Log.d("WavDecoder", "create")
    }
    
    override fun destroy() {
        Log.d("WavDecoder", "destroy")
    }
}

此外,再实现用于解码 MP3 文件的 Mp3Decoder,同样需要声明 @Inject 注解:

class Mp3Decoder @Inject constructor() : IDecoder {
    override fun create() {
        Log.d("Mp3Decoder", "create")
    }
    
    override fun destroy() {
        Log.d("Mp3Decoder", "destroy")
    }
}

新建一个抽象类,命名为 DecoderModule,在这个模块中通过定义抽象函数提供 IDecoder 接口所需要的实例:

@Module
@InstallIn(ActivityComponent::class)
abstract class DecoderModule {
    @Binds
    abstract fun bindDecoder(wavDecoder: WavDecoder): IDecoder
}

修改 MusicPlayer 类中的代码,调用刚刚提供的解码器:

class MusicPlayer() @Inject constructor(val audioDriver: AudioDriver) {
    @Inject
    lateinit var decoder: IDecoder

    fun init() {
        decoder.create()
        Log.d("MusicPlayer", "init, audioDriver=$audioDriver")
        decoder.destroy()
    }
}

此时再调用 init() 方法,即可看到 TAG 为 WavDecoder 的日志。

给相同类型注入不同实例

@Qualifer 接口用于给相同类型的类或接口注入不同的实例。分别定义两个注解,如下:

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindWavDecoder

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindMp3Decoder

回到 DecoderModule 中,定义两个抽象函数,将刚才定义的两个注解分别添加到两个函数上方:

@Module
@InstallIn(ActivityComponent::class)
abstract class DecoderModule {
    @BindWavDecoder
    @Binds
    abstract fun bindWavDecoder(wavDecoder: WavDecoder): IDecoder
    
    @BindMp3Decoder
    @Binds
    abstract fun bindMp3Decoder(mp3Decoder: WavDecoder): IDecoder
}

回到 MusicPlayer 类,此时就可以让这个播放器同时支持两种格式的解码:

class MusicPlayer() @Inject constructor(val audioDriver: AudioDriver) {
    @BindWavDecoder
    @Inject
    lateinit var wavDecoder: IDecoder
    
    @BindMp3Decoder
    @Inject
    lateinit var mp3Decoder: IDecoder

    fun init() {
        wavDecoder.create()
        mp3Decoder.create()
        Log.d("MusicPlayer", "init, audioDriver=$audioDriver")
        wavDecoder.destroy()
        mp3Decoder.destroy()
    }
}

第三方类的依赖注入

假如我们想在 MainActivity 中注入 OkHttpClient,该类由 OkHttp 提供,我们无法为其构造函数加上 @Inject 注解。这种情况下,需要借助 @Module 注解定义一个非抽象类,此处命名为 NetworkModule。
在该类中定义一个方法,加上 @Provides 注解,在函数体中提供一个 OkHttpClient 的实例,如下:

@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
                    .connectTimeout(20, TimeUnit.SECONDS)
                    .readTimeout(20, TimeUnit.SECONDS)
                    .writeTimeout(20, TimeUnit.SECONDS)
                    .build()
    }
}

回到 MainActivity,使用 @Inject 注入 OkHttpClient,即可成功运行:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var okHttpClient: OkHttpClient
}

为了方便开发者使用,我们在 NetworkModule 再给 Retrofit 类型提供实例,编写如下代码:

@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
                            .addConverterFactory(GsonConverterFactory.create())
                            .baseUrl("http://example.com/")
                            .client(okHttpClient)
                            .build()
        }
}

方法 provideRetrofit() 中的 okHttpClient 参数则会由 Hilt 自动使用 provideOkHttpClient() 方法进行创建。此时在 MainActivity 中再次尝试注入 Retrofit,也可以正常运行:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var retrofit: Retrofit
}

Hilt 内置组件

使用 @Module 注入的类,需要使用 @InstallIn 注解指定注入的范围。Hilt 一共提供了 7 种组件类型,分别用于注入到不同的场景:

组件名 注入范围
ApplicationComponent Application
ActivityRetainedComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent 使用 @WithFragmentBindings 定义的 View
ServiceComponent Service

若希望上方定义的 NetworkModule 可以在全项目中使用,只需这样修改:

@Module
@InstallIn(ApplicationComponent::class)
class NetworkModule {
}

Hilt 组件作用域

Hilt 默认会为每次的依赖注入行为都创建不同的实例。对应前面的 7 个内置组件,Hilt 也提供了 7 种组件作用域注解,如下所示:

组件作用域 对应内置组件
@Singleton ApplicationComponent
@ActivityRetainedScope ActivityRetainedComponent
@ActivityScoped ActivityComponent
@FragmentScoped FragmentComponent
@ViewScoped ViewComponent
@ViewScoped ViewWithFragmentComponent
@ServiceScoped ServiceComponent

若希望 NetworkModule 中提供的 Retrofit 和 OkHttpClient 实例在全局只创建一份,只需加上 @Singleton 注解:

@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
    @Singleton
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
                    .connectTimeout(20, TimeUnit.SECONDS)
                    .readTimeout(20, TimeUnit.SECONDS)
                    .writeTimeout(20, TimeUnit.SECONDS)
                    .build()
    }

    @Singleton
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
                            .addConverterFactory(GsonConverterFactory.create())
                            .baseUrl("http://example.com/")
                            .client(okHttpClient)
                            .build()
        }
}

作用域注解也可以被直接声明到任何可注入类的上方,例如前面添加的 AudioDriver 类:

@Singleton
class AudioDriver @Inject constructor() {
}

这就表示 AudioDriver 在全局范围内都会共享同一个实例,且全局都可以对 AudioDriver 类进行依赖注入。

Jetpack Hilt 框架的基本使用

如上图所示,对某个类声明了某种作用域注解后,该注解的箭头所能指到的地方,都可以对该类进行依赖注入,同时在该范围内共享同一个实例。

预置 Qualifier

若前面定义的 AudioDriver 类需要一个 Context 参数,需要在该参数前加上一个 @ApplicationContext 注解,Hilt 会提供一个 Application 类型的 Context 给到 AudioDriver 类当中,代码即可编译通过:

@Singleton
class AudioDriver @Inject constructor(@ApplicationContext val context: Context) {
}

如果需要 Activity 或其他类型的 Context,使用 Hilt 预置的另外一种 Qualifier 即可:

@Singleton
class AudioDriver @Inject constructor(@ActivityContext val context: Context) {
}

此时编译代码会报错,因为 AudioDriver 类是 Singleton 的,与 Qualifier 的范围不匹配。
对于 Activity 和 Application 这两个类型,Hilt 为它们预置好了注入功能。如果某个类依赖于 Activity 或 Application,不需要添加任何注解,Hilt 可以自动识别,如下:

class AudioDriver @Inject constructor(val application: Application) {
}

class AudioDriver @Inject constructor(val activity: Activity) {
}

注意必须是 Application 和 Activity 这两个类型,即使声明它们的子类型,编译都无法通过。

HiltViewModel 的使用

先在 libs.versions.toml 中声明相关依赖,如下:

[versions]
hilt-lifecycle-viewmodel = "1.0.0-alpha03"

[libraries]
androidx-hilt-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "hilt-lifecycle-viewmodel" }

在 build.gradle.kts 中添加依赖:

dependencies {
    kapt(libs.androidx.hilt.compiler)
}

通过 @HiltViewModel 注解提供一个 ViewModel:

@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
}

然后,带有 @AndroidEntryPoint 注解的 Activity 或 Fragment 即可使用 ViewModelProvider 或 by viewModels() 扩展照常获取 ViewModel 实例:文章来源地址https://www.toymoban.com/news/detail-465647.html

class MainActivity : AppCompatActivity() {
    private val mainViewModel: MainViewModel by viewModels()
}

到了这里,关于Jetpack Hilt 框架的基本使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • android jetpack Room的基本使用(java)

    添加依赖 创建表 @Entity表示根据实体类创建数据表,如果有多个主键要使用primaryKeys = {} @ColumnInfo 表示在数据表中的名字 @Ignore 表示不在数据表创建此字段 @PrimaryKey 主键 创建DAO 每一个表都对应一个dao。 创建数据库 创建一个抽象类,设置要创建的数据表,数据版本,数据库名

    2024年02月07日
    浏览(39)
  • android jetpack databinding的基本使用(java)

    开启databing 修改布局文件 为布局文件添加layout标签。 实例化布局文件 向布局文件传递数据 创建一个Sentence 类,实例化。传给布局并显示。 5. 在布局中引用静态类 在sentence类中添加属性collect ,collect 等于1表示已收藏,0表示收藏。 建立工具类CollectUtil 通过import导入到布局文

    2024年02月10日
    浏览(38)
  • 使用 Jetpack Compose 实现一个计算器APP

    在上一篇文章中,我们说到打算使用 compose 实现一个计算器 APP,最开始打算做一个经典的 LCD 基础计算器,后来觉得好像没啥特色,最终决定还是改成仿微软计算器。 不过,微软计算器的功能太多了,仿制的工程量不小,所以我打算只仿我认为最核心的两个模式:标准模式和

    2024年02月05日
    浏览(62)
  • 一个功能强大、好看的vue表格组件

    今天给大家推荐一个好用、强大的Vue表格扩展组件。 这是支持Vue 3/Vue 2的一个表格组件,支持表格增删改、虚拟表格、复杂表格、树形表格、数据校验、懒加载、分页、弹窗、单元格样式设置、按钮自定义样式、表头样式、单元格合等功能。 组件兼容各大浏览器、高效整洁的

    2024年02月16日
    浏览(42)
  • Postman,一个功能强大的API开发和测试工具

    最近有粉丝在群里说在找 postman 的使用教程,案例等文章。 那么今天我就来写一个。 Postman 是一个功能强大的 API 开发和测试工具,它提供了丰富的功能,帮助开发人员更好地管理、测试和文档化 API。无论是单独开发还是团队协作,Postman 都可以提高开发效率,并提供可靠的

    2024年04月15日
    浏览(45)
  • Fooocus:一个简单且功能强大的Stable Diffusion webUI

    Stable Diffusion是一个强大的图像生成AI模型,但它通常需要大量调整和提示工程。Fooocus的目标是改变这种状况。 Fooocus的创始人Lvmin Zhang(也是 ControlNet论文的作者)将这个项目描述为对“Stable Diffusion”和“ Midjourney”设计的重新设计。Fooocus就像是Midjourney的免费离线版本,但是

    2024年02月11日
    浏览(43)
  • 一个开源免费功能强大的Chatgpt Web程序 搭建自己的Chatgpt机器人

    ChatGPT Web Midjourney Proxy 是我用过的几款ChatGPT Web程序当中我觉得最好用的一款,还支持midjourney的图片生成 ai换脸和很多的ChatGPT store。 ✅ 原chatgpt web 所有功能 ✅ chatgpt web 支持自定义api key、base_url ✅ midjourney 文生图 ✅ midjourney 垫图+文生图 ✅ midjourney 图变 U1到U4 、 V1到V4、重绘

    2024年03月23日
    浏览(51)
  • 面试需要讲什么功能强大的python包:selenium,【一篇文章搞懂

    在操作测试对象中,send_keys( )中可以传递键盘事件,相当于我们按下一下特殊的按键。 键盘事件 | 键盘事件 | 代码实现 | | — | — | | TAB | send_keys(Keys.TAB) | | ENTER | send_keys(Keys.ENTER) | | BackSpace | send_keys(Keys.BackSpace) | | Space | send_keys(Keys.Space) | | Esc | send_keys(Keys.Esc) | | F1 | send_keys

    2024年04月12日
    浏览(38)
  • .NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2

    很多同学都不愿给电脑设动态壁纸,其中有个重要原因就是嫌它占资源过多。今天大姚分享一个.NET开源、免费(MIT license)的一个小而快并且功能强大的 Windows 动态桌面软件,支持视频和网页动画播放:DreamScene2。 支持视频播放。 支持 URL 和网页文件。 支持启动后自动播放。

    2024年02月19日
    浏览(51)
  • 【Vue框架】Vue2中axios发送请求—安装axios、配置全局域名、传递参数、axios原理之Promise(什么是Promise、使用原因、基本使用、相关API、async与await使用)

    官网: https://www.axios-http.cn/ 1.1.1 安装axios库 安装 axios 通信库: npm install axios -S 1.1.2 在全局中引入axios库 全局在main.js中引入axios: import axios from \\\'axios\\\' 1.1.3 挂在原型对象 Vue.prototype.$axios = axios; 将 axios 挂载到Vue原型对象中,实现数据共享,节约内存空间。 此时在任何页面都可

    2024年02月05日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包